diff --git a/Moose Development/Moose/AI/AI_Balancer.lua b/Moose Development/Moose/AI/AI_Balancer.lua new file mode 100644 index 000000000..75a1c5a2c --- /dev/null +++ b/Moose Development/Moose/AI/AI_Balancer.lua @@ -0,0 +1,256 @@ +--- This module contains the AI_BALANCER class. +-- +-- === +-- +-- 1) @{AI.AI_Balancer#AI_BALANCER} class, extends @{Core.Fsm#FSM_SET} +-- =================================================================================== +-- The @{AI.AI_Balancer#AI_BALANCER} class monitors and manages as many AI GROUPS as there are +-- CLIENTS in a SET_CLIENT collection not occupied by players. +-- The AI_BALANCER class manages internally a collection of AI management objects, which govern the behaviour +-- of the underlying AI GROUPS. +-- +-- The parent class @{Core.Fsm#FSM_SET} manages the functionality to control the Finite State Machine (FSM) +-- and calls for each event the state transition methods providing the internal @{Core.Fsm#FSM_SET.Set} object containing the +-- SET_GROUP and additional event parameters provided during the event. +-- +-- 1.1) AI_BALANCER construction method +-- --------------------------------------- +-- Create a new AI_BALANCER object with the @{#AI_BALANCER.New} method: +-- +-- * @{#AI_BALANCER.New}: Creates a new AI_BALANCER object. +-- +-- 1.2) +-- ---- +-- * Add +-- * Remove +-- +-- 1.2) AI_BALANCER returns AI to Airbases +-- ------------------------------------------ +-- You can configure to have the AI to return to: +-- +-- * @{#AI_BALANCER.ReturnToHomeAirbase}: Returns the AI to the home @{Wrapper.Airbase#AIRBASE}. +-- * @{#AI_BALANCER.ReturnToNearestAirbases}: Returns the AI to the nearest friendly @{Wrapper.Airbase#AIRBASE}. +-- -- +-- === +-- +-- **API CHANGE HISTORY** +-- ====================== +-- +-- The underlying change log documents the API changes. Please read this carefully. The following notation is used: +-- +-- * **Added** parts are expressed in bold type face. +-- * _Removed_ parts are expressed in italic type face. +-- +-- Hereby the change log: +-- +-- 2016-08-17: SPAWN:**InitCleanUp**( SpawnCleanUpInterval ) replaces SPAWN:_CleanUp_( SpawnCleanUpInterval ) +-- +-- * Want to ensure that the methods starting with **Init** are the first called methods before any _Spawn_ method is called! +-- * This notation makes it now more clear which methods are initialization methods and which methods are Spawn enablement methods. +-- +-- === +-- +-- AUTHORS and CONTRIBUTIONS +-- ========================= +-- +-- ### Contributions: +-- +-- * **Dutch_Baron (James)**: Who you can search on the Eagle Dynamics Forums. +-- Working together with James has resulted in the creation of the AI_BALANCER class. +-- James has shared his ideas on balancing AI with air units, and together we made a first design which you can use now :-) +-- +-- * **SNAFU**: +-- Had a couple of mails with the guys to validate, if the same concept in the GCI/CAP script could be reworked within MOOSE. +-- None of the script code has been used however within the new AI_BALANCER moose class. +-- +-- ### Authors: +-- +-- * FlightControl: Framework Design & Programming +-- +-- @module AI_Balancer + + + +--- AI_BALANCER class +-- @type AI_BALANCER +-- @field Core.Set#SET_CLIENT SetClient +-- @extends Core.Fsm#FSM_SET +AI_BALANCER = { + ClassName = "AI_BALANCER", + PatrolZones = {}, + AIGroups = {}, +} + +--- Creates a new AI_BALANCER object +-- @param #AI_BALANCER self +-- @param Core.Set#SET_CLIENT SetClient A SET\_CLIENT object that will contain the CLIENT objects to be monitored if they are alive or not (joined by a player). +-- @param Functional.Spawn#SPAWN SpawnAI The default Spawn object to spawn new AI Groups when needed. +-- @return #AI_BALANCER +-- @usage +-- -- Define a new AI_BALANCER Object. +function AI_BALANCER:New( SetClient, SpawnAI ) + + -- Inherits from BASE + local self = BASE:Inherit( self, FSM_SET:New( SET_GROUP:New() ) ) -- Core.Fsm#FSM_SET + + self:SetStartState( "None" ) + self:AddTransition( "*", "Start", "Monitoring" ) + self:AddTransition( "*", "Monitor", "Monitoring" ) + self:AddTransition( "*", "Spawn", "Spawning" ) + self:AddTransition( "Spawning", "Spawned", "Spawned" ) + self:AddTransition( "*", "Destroy", "Destroying" ) + self:AddTransition( "*", "Return", "Returning" ) + self:AddTransition( "*", "End", "End" ) + self:AddTransition( "*", "Dead", "End" ) + + + + self.SetClient = SetClient + self.SpawnAI = SpawnAI + self.ToNearestAirbase = false + self.ToHomeAirbase = false + + self:__Start( 1 ) + + return self +end + +--- Returns the AI to the nearest friendly @{Wrapper.Airbase#AIRBASE}. +-- @param #AI_BALANCER self +-- @param Dcs.DCSTypes#Distance ReturnTresholdRange If there is an enemy @{Wrapper.Client#CLIENT} within the ReturnTresholdRange given in meters, the AI will not return to the nearest @{Wrapper.Airbase#AIRBASE}. +-- @param Core.Set#SET_AIRBASE ReturnAirbaseSet The SET of @{Core.Set#SET_AIRBASE}s to evaluate where to return to. +function AI_BALANCER:ReturnToNearestAirbases( ReturnTresholdRange, ReturnAirbaseSet ) + + self.ToNearestAirbase = true + self.ReturnTresholdRange = ReturnTresholdRange + self.ReturnAirbaseSet = ReturnAirbaseSet +end + +--- Returns the AI to the home @{Wrapper.Airbase#AIRBASE}. +-- @param #AI_BALANCER self +-- @param Dcs.DCSTypes#Distance ReturnTresholdRange If there is an enemy @{Wrapper.Client#CLIENT} within the ReturnTresholdRange given in meters, the AI will not return to the nearest @{Wrapper.Airbase#AIRBASE}. +function AI_BALANCER:ReturnToHomeAirbase( ReturnTresholdRange ) + + self.ToHomeAirbase = true + self.ReturnTresholdRange = ReturnTresholdRange +end + +--- @param #AI_BALANCER self +-- @param Core.Set#SET_GROUP SetGroup +-- @param #string ClientName +-- @param Wrapper.Group#GROUP AIGroup +function AI_BALANCER:onenterSpawning( SetGroup, Event, From, To, ClientName ) + + -- OK, Spawn a new group from the default SpawnAI object provided. + local AIGroup = self.SpawnAI:Spawn() + AIGroup:E( "Spawning new AIGroup" ) + --TODO: need to rework UnitName thing ... + + SetGroup:Add( ClientName, AIGroup ) + + -- Fire the Spawned event. The first parameter is the AIGroup just Spawned. + -- Mission designers can catch this event to bind further actions to the AIGroup. + self:Spawned( AIGroup ) +end + +--- @param #AI_BALANCER self +-- @param Core.Set#SET_GROUP SetGroup +-- @param Wrapper.Group#GROUP AIGroup +function AI_BALANCER:onenterDestroying( SetGroup, Event, From, To, AIGroup ) + + AIGroup:Destroy() +end + +--- @param #AI_BALANCER self +-- @param Core.Set#SET_GROUP SetGroup +-- @param Wrapper.Group#GROUP AIGroup +function AI_BALANCER:onenterReturning( SetGroup, Event, From, To, AIGroup ) + + local AIGroupTemplate = AIGroup:GetTemplate() + if self.ToHomeAirbase == true then + local WayPointCount = #AIGroupTemplate.route.points + local SwitchWayPointCommand = AIGroup:CommandSwitchWayPoint( 1, WayPointCount, 1 ) + AIGroup:SetCommand( SwitchWayPointCommand ) + AIGroup:MessageToRed( "Returning to home base ...", 30 ) + else + -- Okay, we need to send this Group back to the nearest base of the Coalition of the AI. + --TODO: i need to rework the POINT_VEC2 thing. + local PointVec2 = POINT_VEC2:New( AIGroup:GetVec2().x, AIGroup:GetVec2().y ) + local ClosestAirbase = self.ReturnAirbaseSet:FindNearestAirbaseFromPointVec2( PointVec2 ) + self:T( ClosestAirbase.AirbaseName ) + AIGroup:MessageToRed( "Returning to " .. ClosestAirbase:GetName().. " ...", 30 ) + local RTBRoute = AIGroup:RouteReturnToAirbase( ClosestAirbase ) + AIGroupTemplate.route = RTBRoute + AIGroup:Respawn( AIGroupTemplate ) + end + +end + + +--- @param #AI_BALANCER self +function AI_BALANCER:onenterMonitoring( SetGroup ) + + self.SetClient:ForEachClient( + --- @param Wrapper.Client#CLIENT Client + function( Client ) + self:E(Client.ClientName) + + local AIGroup = self.Set:Get( Client.UnitName ) -- Wrapper.Group#GROUP + if Client:IsAlive() then + + if AIGroup and AIGroup:IsAlive() == true then + + if self.ToNearestAirbase == false and self.ToHomeAirbase == false then + self:Destroy( AIGroup ) + else + -- We test if there is no other CLIENT within the self.ReturnTresholdRange of the first unit of the AI group. + -- If there is a CLIENT, the AI stays engaged and will not return. + -- If there is no CLIENT within the self.ReturnTresholdRange, then the unit will return to the Airbase return method selected. + + local PlayerInRange = { Value = false } + local RangeZone = ZONE_RADIUS:New( 'RangeZone', AIGroup:GetVec2(), self.ReturnTresholdRange ) + + self:E( RangeZone ) + + _DATABASE:ForEachPlayer( + --- @param Wrapper.Unit#UNIT RangeTestUnit + function( RangeTestUnit, RangeZone, AIGroup, PlayerInRange ) + self:E( { PlayerInRange, RangeTestUnit.UnitName, RangeZone.ZoneName } ) + if RangeTestUnit:IsInZone( RangeZone ) == true then + self:E( "in zone" ) + if RangeTestUnit:GetCoalition() ~= AIGroup:GetCoalition() then + self:E( "in range" ) + PlayerInRange.Value = true + end + end + end, + + --- @param Core.Zone#ZONE_RADIUS RangeZone + -- @param Wrapper.Group#GROUP AIGroup + function( RangeZone, AIGroup, PlayerInRange ) + if PlayerInRange.Value == false then + self:Return( AIGroup ) + end + end + , RangeZone, AIGroup, PlayerInRange + ) + + end + self.Set:Remove( Client.UnitName ) + end + else + if not AIGroup or not AIGroup:IsAlive() == true then + self:E("client not alive") + self:Spawn( Client.UnitName ) + self:E("text after spawn") + end + end + return true + end + ) + + self:__Monitor( 10 ) +end + + + diff --git a/Moose Development/Moose/AI/AI_Cargo.lua b/Moose Development/Moose/AI/AI_Cargo.lua new file mode 100644 index 000000000..5c3da2180 --- /dev/null +++ b/Moose Development/Moose/AI/AI_Cargo.lua @@ -0,0 +1,1025 @@ +--- Management of logical cargo objects, that can be transported from and to transportation carriers. +-- +-- === +-- +-- Cargo can be of various forms, always are composed out of ONE object ( one unit or one static or one slingload crate ): +-- +-- * AI_CARGO_UNIT, represented by a @{Unit} in a @{Group}: Cargo can be represented by a Unit in a Group. Destruction of the Unit will mean that the cargo is lost. +-- * CARGO_STATIC, represented by a @{Static}: Cargo can be represented by a Static. Destruction of the Static will mean that the cargo is lost. +-- * AI_CARGO_PACKAGE, contained in a @{Unit} of a @{Group}: Cargo can be contained within a Unit of a Group. The cargo can be **delivered** by the @{Unit}. If the Unit is destroyed, the cargo will be destroyed also. +-- * AI_CARGO_PACKAGE, Contained in a @{Static}: Cargo can be contained within a Static. The cargo can be **collected** from the @Static. If the @{Static} is destroyed, the cargo will be destroyed. +-- * CARGO_SLINGLOAD, represented by a @{Cargo} that is transportable: Cargo can be represented by a Cargo object that is transportable. Destruction of the Cargo will mean that the cargo is lost. +-- +-- * AI_CARGO_GROUPED, represented by a Group of CARGO_UNITs. +-- +-- 1) @{AI.AI_Cargo#AI_CARGO} class, extends @{Core.Fsm#FSM_PROCESS} +-- ========================================================================== +-- The @{#AI_CARGO} class defines the core functions that defines a cargo object within MOOSE. +-- A cargo is a logical object defined that is available for transport, and has a life status within a simulation. +-- +-- The AI_CARGO is a state machine: it manages the different events and states of the cargo. +-- All derived classes from AI_CARGO follow the same state machine, expose the same cargo event functions, and provide the same cargo states. +-- +-- ## 1.2.1) AI_CARGO Events: +-- +-- * @{#AI_CARGO.Board}( ToCarrier ): Boards the cargo to a carrier. +-- * @{#AI_CARGO.Load}( ToCarrier ): Loads the cargo into a carrier, regardless of its position. +-- * @{#AI_CARGO.UnBoard}( ToPointVec2 ): UnBoard the cargo from a carrier. This will trigger a movement of the cargo to the option ToPointVec2. +-- * @{#AI_CARGO.UnLoad}( ToPointVec2 ): UnLoads the cargo from a carrier. +-- * @{#AI_CARGO.Dead}( Controllable ): The cargo is dead. The cargo process will be ended. +-- +-- ## 1.2.2) AI_CARGO States: +-- +-- * **UnLoaded**: The cargo is unloaded from a carrier. +-- * **Boarding**: The cargo is currently boarding (= running) into a carrier. +-- * **Loaded**: The cargo is loaded into a carrier. +-- * **UnBoarding**: The cargo is currently unboarding (=running) from a carrier. +-- * **Dead**: The cargo is dead ... +-- * **End**: The process has come to an end. +-- +-- ## 1.2.3) AI_CARGO state transition methods: +-- +-- State transition functions can be set **by the mission designer** customizing or improving the behaviour of the state. +-- There are 2 moments when state transition methods will be called by the state machine: +-- +-- * **Before** the state transition. +-- The state transition method needs to start with the name **OnBefore + the name of the state**. +-- If the state transition method returns false, then the processing of the state transition will not be done! +-- If you want to change the behaviour of the AIControllable at this event, return false, +-- but then you'll need to specify your own logic using the AIControllable! +-- +-- * **After** the state transition. +-- The state transition method needs to start with the name **OnAfter + the name of the state**. +-- These state transition methods need to provide a return value, which is specified at the function description. +-- +-- 2) #AI_CARGO_UNIT class +-- ==================== +-- The AI_CARGO_UNIT class defines a cargo that is represented by a UNIT object within the simulator, and can be transported by a carrier. +-- Use the event functions as described above to Load, UnLoad, Board, UnBoard the AI_CARGO_UNIT objects to and from carriers. +-- +-- 5) #AI_CARGO_GROUPED class +-- ======================= +-- The AI_CARGO_GROUPED class defines a cargo that is represented by a group of UNIT objects within the simulator, and can be transported by a carrier. +-- Use the event functions as described above to Load, UnLoad, Board, UnBoard the AI_CARGO_UNIT objects to and from carriers. +-- +-- This module is still under construction, but is described above works already, and will keep working ... +-- +-- @module Cargo + +-- Events + +-- Board + +--- Boards the cargo to a Carrier. The event will create a movement (= running or driving) of the cargo to the Carrier. +-- The cargo must be in the **UnLoaded** state. +-- @function [parent=#AI_CARGO] Board +-- @param #AI_CARGO self +-- @param Wrapper.Controllable#CONTROLLABLE ToCarrier The Carrier that will hold the cargo. + +--- Boards the cargo to a Carrier. The event will create a movement (= running or driving) of the cargo to the Carrier. +-- The cargo must be in the **UnLoaded** state. +-- @function [parent=#AI_CARGO] __Board +-- @param #AI_CARGO self +-- @param #number DelaySeconds The amount of seconds to delay the action. +-- @param Wrapper.Controllable#CONTROLLABLE ToCarrier The Carrier that will hold the cargo. + + +-- UnBoard + +--- UnBoards the cargo to a Carrier. The event will create a movement (= running or driving) of the cargo from the Carrier. +-- The cargo must be in the **Loaded** state. +-- @function [parent=#AI_CARGO] UnBoard +-- @param #AI_CARGO self +-- @param Core.Point#POINT_VEC2 ToPointVec2 (optional) @{Core.Point#POINT_VEC2) to where the cargo should run after onboarding. If not provided, the cargo will run to 60 meters behind the Carrier location. + +--- UnBoards the cargo to a Carrier. The event will create a movement (= running or driving) of the cargo from the Carrier. +-- The cargo must be in the **Loaded** state. +-- @function [parent=#AI_CARGO] __UnBoard +-- @param #AI_CARGO self +-- @param #number DelaySeconds The amount of seconds to delay the action. +-- @param Core.Point#POINT_VEC2 ToPointVec2 (optional) @{Core.Point#POINT_VEC2) to where the cargo should run after onboarding. If not provided, the cargo will run to 60 meters behind the Carrier location. + + +-- Load + +--- Loads the cargo to a Carrier. The event will load the cargo into the Carrier regardless of its position. There will be no movement simulated of the cargo loading. +-- The cargo must be in the **UnLoaded** state. +-- @function [parent=#AI_CARGO] Load +-- @param #AI_CARGO self +-- @param Wrapper.Controllable#CONTROLLABLE ToCarrier The Carrier that will hold the cargo. + +--- Loads the cargo to a Carrier. The event will load the cargo into the Carrier regardless of its position. There will be no movement simulated of the cargo loading. +-- The cargo must be in the **UnLoaded** state. +-- @function [parent=#AI_CARGO] __Load +-- @param #AI_CARGO self +-- @param #number DelaySeconds The amount of seconds to delay the action. +-- @param Wrapper.Controllable#CONTROLLABLE ToCarrier The Carrier that will hold the cargo. + + +-- UnLoad + +--- UnLoads the cargo to a Carrier. The event will unload the cargo from the Carrier. There will be no movement simulated of the cargo loading. +-- The cargo must be in the **Loaded** state. +-- @function [parent=#AI_CARGO] UnLoad +-- @param #AI_CARGO self +-- @param Core.Point#POINT_VEC2 ToPointVec2 (optional) @{Core.Point#POINT_VEC2) to where the cargo will be placed after unloading. If not provided, the cargo will be placed 60 meters behind the Carrier location. + +--- UnLoads the cargo to a Carrier. The event will unload the cargo from the Carrier. There will be no movement simulated of the cargo loading. +-- The cargo must be in the **Loaded** state. +-- @function [parent=#AI_CARGO] __UnLoad +-- @param #AI_CARGO self +-- @param #number DelaySeconds The amount of seconds to delay the action. +-- @param Core.Point#POINT_VEC2 ToPointVec2 (optional) @{Core.Point#POINT_VEC2) to where the cargo will be placed after unloading. If not provided, the cargo will be placed 60 meters behind the Carrier location. + +-- State Transition Functions + +-- UnLoaded + +--- @function [parent=#AI_CARGO] OnBeforeUnLoaded +-- @param #AI_CARGO self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable +-- @return #boolean + +--- @function [parent=#AI_CARGO] OnAfterUnLoaded +-- @param #AI_CARGO self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable + +-- Loaded + +--- @function [parent=#AI_CARGO] OnBeforeLoaded +-- @param #AI_CARGO self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable +-- @return #boolean + +--- @function [parent=#AI_CARGO] OnAfterLoaded +-- @param #AI_CARGO self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable + +-- Boarding + +--- @function [parent=#AI_CARGO] OnBeforeBoarding +-- @param #AI_CARGO self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable +-- @return #boolean + +--- @function [parent=#AI_CARGO] OnAfterBoarding +-- @param #AI_CARGO self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable + +-- UnBoarding + +--- @function [parent=#AI_CARGO] OnBeforeUnBoarding +-- @param #AI_CARGO self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable +-- @return #boolean + +--- @function [parent=#AI_CARGO] OnAfterUnBoarding +-- @param #AI_CARGO self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable + + +-- TODO: Find all Carrier objects and make the type of the Carriers Wrapper.Unit#UNIT in the documentation. + +CARGOS = {} + +do -- AI_CARGO + + --- @type AI_CARGO + -- @extends Core.Fsm#FSM_PROCESS + -- @field #string Type A string defining the type of the cargo. eg. Engineers, Equipment, Screwdrivers. + -- @field #string Name A string defining the name of the cargo. The name is the unique identifier of the cargo. + -- @field #number Weight A number defining the weight of the cargo. The weight is expressed in kg. + -- @field #number ReportRadius (optional) A number defining the radius in meters when the cargo is signalling or reporting to a Carrier. + -- @field #number NearRadius (optional) A number defining the radius in meters when the cargo is near to a Carrier, so that it can be loaded. + -- @field Wrapper.Controllable#CONTROLLABLE CargoObject The alive DCS object representing the cargo. This value can be nil, meaning, that the cargo is not represented anywhere... + -- @field Wrapper.Controllable#CONTROLLABLE CargoCarrier The alive DCS object carrying the cargo. This value can be nil, meaning, that the cargo is not contained anywhere... + -- @field #boolean Slingloadable This flag defines if the cargo can be slingloaded. + -- @field #boolean Moveable This flag defines if the cargo is moveable. + -- @field #boolean Representable This flag defines if the cargo can be represented by a DCS Unit. + -- @field #boolean Containable This flag defines if the cargo can be contained within a DCS Unit. + AI_CARGO = { + ClassName = "AI_CARGO", + Type = nil, + Name = nil, + Weight = nil, + CargoObject = nil, + CargoCarrier = nil, + Representable = false, + Slingloadable = false, + Moveable = false, + Containable = false, + } + +--- @type AI_CARGO.CargoObjects +-- @map < #string, Wrapper.Positionable#POSITIONABLE > The alive POSITIONABLE objects representing the the cargo. + + +--- AI_CARGO Constructor. This class is an abstract class and should not be instantiated. +-- @param #AI_CARGO self +-- @param #string Type +-- @param #string Name +-- @param #number Weight +-- @param #number ReportRadius (optional) +-- @param #number NearRadius (optional) +-- @return #AI_CARGO +function AI_CARGO:New( Type, Name, Weight, ReportRadius, NearRadius ) + + local self = BASE:Inherit( self, FSM:New() ) -- Core.Fsm#FSM_CONTROLLABLE + self:F( { Type, Name, Weight, ReportRadius, NearRadius } ) + + self:SetStartState( "UnLoaded" ) + self:AddTransition( "UnLoaded", "Board", "Boarding" ) + self:AddTransition( "Boarding", "Boarding", "Boarding" ) + self:AddTransition( "Boarding", "Load", "Loaded" ) + self:AddTransition( "UnLoaded", "Load", "Loaded" ) + self:AddTransition( "Loaded", "UnBoard", "UnBoarding" ) + self:AddTransition( "UnBoarding", "UnBoarding", "UnBoarding" ) + self:AddTransition( "UnBoarding", "UnLoad", "UnLoaded" ) + self:AddTransition( "Loaded", "UnLoad", "UnLoaded" ) + + + self.Type = Type + self.Name = Name + self.Weight = Weight + self.ReportRadius = ReportRadius + self.NearRadius = NearRadius + self.CargoObject = nil + self.CargoCarrier = nil + self.Representable = false + self.Slingloadable = false + self.Moveable = false + self.Containable = false + + + self.CargoScheduler = SCHEDULER:New() + + CARGOS[self.Name] = self + + return self +end + + +--- Template method to spawn a new representation of the AI_CARGO in the simulator. +-- @param #AI_CARGO self +-- @return #AI_CARGO +function AI_CARGO:Spawn( PointVec2 ) + self:F() + +end + + +--- Check if CargoCarrier is near the Cargo to be Loaded. +-- @param #AI_CARGO self +-- @param Core.Point#POINT_VEC2 PointVec2 +-- @return #boolean +function AI_CARGO:IsNear( PointVec2 ) + self:F( { PointVec2 } ) + + local Distance = PointVec2:DistanceFromPointVec2( self.CargoObject:GetPointVec2() ) + self:T( Distance ) + + if Distance <= self.NearRadius then + return true + else + return false + end +end + +end + +do -- AI_CARGO_REPRESENTABLE + + --- @type AI_CARGO_REPRESENTABLE + -- @extends #AI_CARGO + AI_CARGO_REPRESENTABLE = { + ClassName = "AI_CARGO_REPRESENTABLE" + } + +--- AI_CARGO_REPRESENTABLE Constructor. +-- @param #AI_CARGO_REPRESENTABLE self +-- @param Wrapper.Controllable#Controllable CargoObject +-- @param #string Type +-- @param #string Name +-- @param #number Weight +-- @param #number ReportRadius (optional) +-- @param #number NearRadius (optional) +-- @return #AI_CARGO_REPRESENTABLE +function AI_CARGO_REPRESENTABLE:New( CargoObject, Type, Name, Weight, ReportRadius, NearRadius ) + local self = BASE:Inherit( self, AI_CARGO:New( Type, Name, Weight, ReportRadius, NearRadius ) ) -- #AI_CARGO + self:F( { Type, Name, Weight, ReportRadius, NearRadius } ) + + return self +end + +--- Route a cargo unit to a PointVec2. +-- @param #AI_CARGO_REPRESENTABLE self +-- @param Core.Point#POINT_VEC2 ToPointVec2 +-- @param #number Speed +-- @return #AI_CARGO_REPRESENTABLE +function AI_CARGO_REPRESENTABLE:RouteTo( ToPointVec2, Speed ) + self:F2( ToPointVec2 ) + + local Points = {} + + local PointStartVec2 = self.CargoObject:GetPointVec2() + + Points[#Points+1] = PointStartVec2:RoutePointGround( Speed ) + Points[#Points+1] = ToPointVec2:RoutePointGround( Speed ) + + local TaskRoute = self.CargoObject:TaskRoute( Points ) + self.CargoObject:SetTask( TaskRoute, 2 ) + return self +end + +end -- AI_CARGO + +do -- AI_CARGO_UNIT + + --- @type AI_CARGO_UNIT + -- @extends #AI_CARGO_REPRESENTABLE + AI_CARGO_UNIT = { + ClassName = "AI_CARGO_UNIT" + } + +--- AI_CARGO_UNIT Constructor. +-- @param #AI_CARGO_UNIT self +-- @param Wrapper.Unit#UNIT CargoUnit +-- @param #string Type +-- @param #string Name +-- @param #number Weight +-- @param #number ReportRadius (optional) +-- @param #number NearRadius (optional) +-- @return #AI_CARGO_UNIT +function AI_CARGO_UNIT:New( CargoUnit, Type, Name, Weight, ReportRadius, NearRadius ) + local self = BASE:Inherit( self, AI_CARGO_REPRESENTABLE:New( CargoUnit, Type, Name, Weight, ReportRadius, NearRadius ) ) -- #AI_CARGO_UNIT + self:F( { Type, Name, Weight, ReportRadius, NearRadius } ) + + self:T( CargoUnit ) + self.CargoObject = CargoUnit + + self:T( self.ClassName ) + + return self +end + +--- Enter UnBoarding State. +-- @param #AI_CARGO_UNIT self +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Core.Point#POINT_VEC2 ToPointVec2 +function AI_CARGO_UNIT:onenterUnBoarding( Event, From, To, ToPointVec2 ) + self:F() + + local Angle = 180 + local Speed = 10 + local DeployDistance = 5 + local RouteDistance = 60 + + if From == "Loaded" then + + local CargoCarrierPointVec2 = self.CargoCarrier:GetPointVec2() + local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. + local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) + local CargoDeployPointVec2 = CargoCarrierPointVec2:Translate( DeployDistance, CargoDeployHeading ) + local CargoRoutePointVec2 = CargoCarrierPointVec2:Translate( RouteDistance, CargoDeployHeading ) + + -- if there is no ToPointVec2 given, then use the CargoRoutePointVec2 + ToPointVec2 = ToPointVec2 or CargoRoutePointVec2 + + local FromPointVec2 = CargoCarrierPointVec2 + + -- Respawn the group... + if self.CargoObject then + self.CargoObject:ReSpawn( CargoDeployPointVec2:GetVec3(), CargoDeployHeading ) + self.CargoCarrier = nil + + local Points = {} + Points[#Points+1] = FromPointVec2:RoutePointGround( Speed ) + Points[#Points+1] = ToPointVec2:RoutePointGround( Speed ) + + local TaskRoute = self.CargoObject:TaskRoute( Points ) + self.CargoObject:SetTask( TaskRoute, 1 ) + + self:__UnBoarding( 1, ToPointVec2 ) + end + end + +end + +--- Leave UnBoarding State. +-- @param #AI_CARGO_UNIT self +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Core.Point#POINT_VEC2 ToPointVec2 +function AI_CARGO_UNIT:onleaveUnBoarding( Event, From, To, ToPointVec2 ) + self:F( { ToPointVec2, Event, From, To } ) + + local Angle = 180 + local Speed = 10 + local Distance = 5 + + if From == "UnBoarding" then + if self:IsNear( ToPointVec2 ) then + return true + else + self:__UnBoarding( 1, ToPointVec2 ) + end + return false + end + +end + +--- UnBoard Event. +-- @param #AI_CARGO_UNIT self +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Core.Point#POINT_VEC2 ToPointVec2 +function AI_CARGO_UNIT:onafterUnBoarding( Event, From, To, ToPointVec2 ) + self:F( { ToPointVec2, Event, From, To } ) + + self.CargoInAir = self.CargoObject:InAir() + + self:T( self.CargoInAir ) + + -- Only unboard the cargo when the carrier is not in the air. + -- (eg. cargo can be on a oil derrick, moving the cargo on the oil derrick will drop the cargo on the sea). + if not self.CargoInAir then + + end + + self:__UnLoad( 1, ToPointVec2 ) + +end + + + +--- Enter UnLoaded State. +-- @param #AI_CARGO_UNIT self +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Core.Point#POINT_VEC2 +function AI_CARGO_UNIT:onenterUnLoaded( Event, From, To, ToPointVec2 ) + self:F( { ToPointVec2, Event, From, To } ) + + local Angle = 180 + local Speed = 10 + local Distance = 5 + + if From == "Loaded" then + local StartPointVec2 = self.CargoCarrier:GetPointVec2() + local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. + local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) + local CargoDeployPointVec2 = StartPointVec2:Translate( Distance, CargoDeployHeading ) + + ToPointVec2 = ToPointVec2 or POINT_VEC2:New( CargoDeployPointVec2:GetX(), CargoDeployPointVec2:GetY() ) + + -- Respawn the group... + if self.CargoObject then + self.CargoObject:ReSpawn( ToPointVec2:GetVec3(), 0 ) + self.CargoCarrier = nil + end + + end + + if self.OnUnLoadedCallBack then + self.OnUnLoadedCallBack( self, unpack( self.OnUnLoadedParameters ) ) + self.OnUnLoadedCallBack = nil + end + +end + + + +--- Enter Boarding State. +-- @param #AI_CARGO_UNIT self +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Wrapper.Unit#UNIT CargoCarrier +function AI_CARGO_UNIT:onenterBoarding( Event, From, To, CargoCarrier ) + self:F( { CargoCarrier.UnitName, Event, From, To } ) + + local Speed = 10 + local Angle = 180 + local Distance = 5 + + if From == "UnLoaded" then + local CargoCarrierPointVec2 = CargoCarrier:GetPointVec2() + local CargoCarrierHeading = CargoCarrier:GetHeading() -- Get Heading of object in degrees. + local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) + local CargoDeployPointVec2 = CargoCarrierPointVec2:Translate( Distance, CargoDeployHeading ) + + local Points = {} + + local PointStartVec2 = self.CargoObject:GetPointVec2() + + Points[#Points+1] = PointStartVec2:RoutePointGround( Speed ) + Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) + + local TaskRoute = self.CargoObject:TaskRoute( Points ) + self.CargoObject:SetTask( TaskRoute, 2 ) + end + +end + +--- Leave Boarding State. +-- @param #AI_CARGO_UNIT self +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Wrapper.Unit#UNIT CargoCarrier +function AI_CARGO_UNIT:onleaveBoarding( Event, From, To, CargoCarrier ) + self:F( { CargoCarrier.UnitName, Event, From, To } ) + + if self:IsNear( CargoCarrier:GetPointVec2() ) then + self:__Load( 1, CargoCarrier ) + return true + else + self:__Boarding( 1, CargoCarrier ) + end + return false +end + +--- Loaded State. +-- @param #AI_CARGO_UNIT self +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Wrapper.Unit#UNIT CargoCarrier +function AI_CARGO_UNIT:onenterLoaded( Event, From, To, CargoCarrier ) + self:F() + + self.CargoCarrier = CargoCarrier + + -- Only destroy the CargoObject is if there is a CargoObject (packages don't have CargoObjects). + if self.CargoObject then + self:T("Destroying") + self.CargoObject:Destroy() + end +end + + +--- Board Event. +-- @param #AI_CARGO_UNIT self +-- @param #string Event +-- @param #string From +-- @param #string To +function AI_CARGO_UNIT:onafterBoard( Event, From, To, CargoCarrier ) + self:F() + + self.CargoInAir = self.CargoObject:InAir() + + self:T( self.CargoInAir ) + + -- Only move the group to the carrier when the cargo is not in the air + -- (eg. cargo can be on a oil derrick, moving the cargo on the oil derrick will drop the cargo on the sea). + if not self.CargoInAir then + self:Load( CargoCarrier ) + end + +end + +end + +do -- AI_CARGO_PACKAGE + + --- @type AI_CARGO_PACKAGE + -- @extends #AI_CARGO_REPRESENTABLE + AI_CARGO_PACKAGE = { + ClassName = "AI_CARGO_PACKAGE" + } + +--- AI_CARGO_PACKAGE Constructor. +-- @param #AI_CARGO_PACKAGE self +-- @param Wrapper.Unit#UNIT CargoCarrier The UNIT carrying the package. +-- @param #string Type +-- @param #string Name +-- @param #number Weight +-- @param #number ReportRadius (optional) +-- @param #number NearRadius (optional) +-- @return #AI_CARGO_PACKAGE +function AI_CARGO_PACKAGE:New( CargoCarrier, Type, Name, Weight, ReportRadius, NearRadius ) + local self = BASE:Inherit( self, AI_CARGO_REPRESENTABLE:New( CargoCarrier, Type, Name, Weight, ReportRadius, NearRadius ) ) -- #AI_CARGO_PACKAGE + self:F( { Type, Name, Weight, ReportRadius, NearRadius } ) + + self:T( CargoCarrier ) + self.CargoCarrier = CargoCarrier + + return self +end + +--- Board Event. +-- @param #AI_CARGO_PACKAGE self +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Wrapper.Unit#UNIT CargoCarrier +-- @param #number Speed +-- @param #number BoardDistance +-- @param #number Angle +function AI_CARGO_PACKAGE:onafterOnBoard( Event, From, To, CargoCarrier, Speed, BoardDistance, LoadDistance, Angle ) + self:F() + + self.CargoInAir = self.CargoCarrier:InAir() + + self:T( self.CargoInAir ) + + -- Only move the CargoCarrier to the New CargoCarrier when the New CargoCarrier is not in the air. + if not self.CargoInAir then + + local Points = {} + + local StartPointVec2 = self.CargoCarrier:GetPointVec2() + local CargoCarrierHeading = CargoCarrier:GetHeading() -- Get Heading of object in degrees. + local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) + self:T( { CargoCarrierHeading, CargoDeployHeading } ) + local CargoDeployPointVec2 = CargoCarrier:GetPointVec2():Translate( BoardDistance, CargoDeployHeading ) + + Points[#Points+1] = StartPointVec2:RoutePointGround( Speed ) + Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) + + local TaskRoute = self.CargoCarrier:TaskRoute( Points ) + self.CargoCarrier:SetTask( TaskRoute, 1 ) + end + + self:Boarded( CargoCarrier, Speed, BoardDistance, LoadDistance, Angle ) + +end + +--- Check if CargoCarrier is near the Cargo to be Loaded. +-- @param #AI_CARGO_PACKAGE self +-- @param Wrapper.Unit#UNIT CargoCarrier +-- @return #boolean +function AI_CARGO_PACKAGE:IsNear( CargoCarrier ) + self:F() + + local CargoCarrierPoint = CargoCarrier:GetPointVec2() + + local Distance = CargoCarrierPoint:DistanceFromPointVec2( self.CargoCarrier:GetPointVec2() ) + self:T( Distance ) + + if Distance <= self.NearRadius then + return true + else + return false + end +end + +--- Boarded Event. +-- @param #AI_CARGO_PACKAGE self +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Wrapper.Unit#UNIT CargoCarrier +function AI_CARGO_PACKAGE:onafterOnBoarded( Event, From, To, CargoCarrier, Speed, BoardDistance, LoadDistance, Angle ) + self:F() + + if self:IsNear( CargoCarrier ) then + self:__Load( 1, CargoCarrier, Speed, LoadDistance, Angle ) + else + self:__Boarded( 1, CargoCarrier, Speed, BoardDistance, LoadDistance, Angle ) + end +end + +--- UnBoard Event. +-- @param #AI_CARGO_PACKAGE self +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param #number Speed +-- @param #number UnLoadDistance +-- @param #number UnBoardDistance +-- @param #number Radius +-- @param #number Angle +function AI_CARGO_PACKAGE:onafterUnBoard( Event, From, To, CargoCarrier, Speed, UnLoadDistance, UnBoardDistance, Radius, Angle ) + self:F() + + self.CargoInAir = self.CargoCarrier:InAir() + + self:T( self.CargoInAir ) + + -- Only unboard the cargo when the carrier is not in the air. + -- (eg. cargo can be on a oil derrick, moving the cargo on the oil derrick will drop the cargo on the sea). + if not self.CargoInAir then + + self:_Next( self.FsmP.UnLoad, UnLoadDistance, Angle ) + + local Points = {} + + local StartPointVec2 = CargoCarrier:GetPointVec2() + local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. + local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) + self:T( { CargoCarrierHeading, CargoDeployHeading } ) + local CargoDeployPointVec2 = StartPointVec2:Translate( UnBoardDistance, CargoDeployHeading ) + + Points[#Points+1] = StartPointVec2:RoutePointGround( Speed ) + Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) + + local TaskRoute = CargoCarrier:TaskRoute( Points ) + CargoCarrier:SetTask( TaskRoute, 1 ) + end + + self:__UnBoarded( 1 , CargoCarrier, Speed ) + +end + +--- UnBoarded Event. +-- @param #AI_CARGO_PACKAGE self +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Wrapper.Unit#UNIT CargoCarrier +function AI_CARGO_PACKAGE:onafterUnBoarded( Event, From, To, CargoCarrier, Speed ) + self:F() + + if self:IsNear( CargoCarrier ) then + self:__UnLoad( 1, CargoCarrier, Speed ) + else + self:__UnBoarded( 1, CargoCarrier, Speed ) + end +end + +--- Load Event. +-- @param #AI_CARGO_PACKAGE self +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Wrapper.Unit#UNIT CargoCarrier +-- @param #number Speed +-- @param #number LoadDistance +-- @param #number Angle +function AI_CARGO_PACKAGE:onafterLoad( Event, From, To, CargoCarrier, Speed, LoadDistance, Angle ) + self:F() + + self.CargoCarrier = CargoCarrier + + local StartPointVec2 = self.CargoCarrier:GetPointVec2() + local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. + local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) + local CargoDeployPointVec2 = StartPointVec2:Translate( LoadDistance, CargoDeployHeading ) + + local Points = {} + Points[#Points+1] = StartPointVec2:RoutePointGround( Speed ) + Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) + + local TaskRoute = self.CargoCarrier:TaskRoute( Points ) + self.CargoCarrier:SetTask( TaskRoute, 1 ) + +end + +--- UnLoad Event. +-- @param #AI_CARGO_PACKAGE self +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param #number Distance +-- @param #number Angle +function AI_CARGO_PACKAGE:onafterUnLoad( Event, From, To, CargoCarrier, Speed, Distance, Angle ) + self:F() + + local StartPointVec2 = self.CargoCarrier:GetPointVec2() + local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. + local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) + local CargoDeployPointVec2 = StartPointVec2:Translate( Distance, CargoDeployHeading ) + + self.CargoCarrier = CargoCarrier + + local Points = {} + Points[#Points+1] = StartPointVec2:RoutePointGround( Speed ) + Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) + + local TaskRoute = self.CargoCarrier:TaskRoute( Points ) + self.CargoCarrier:SetTask( TaskRoute, 1 ) + +end + + +end + +do -- AI_CARGO_GROUP + + --- @type AI_CARGO_GROUP + -- @extends AI.AI_Cargo#AI_CARGO + -- @field Set#SET_BASE CargoSet A set of cargo objects. + -- @field #string Name A string defining the name of the cargo group. The name is the unique identifier of the cargo. + AI_CARGO_GROUP = { + ClassName = "AI_CARGO_GROUP", + } + +--- AI_CARGO_GROUP constructor. +-- @param #AI_CARGO_GROUP self +-- @param Core.Set#Set_BASE CargoSet +-- @param #string Type +-- @param #string Name +-- @param #number Weight +-- @param #number ReportRadius (optional) +-- @param #number NearRadius (optional) +-- @return #AI_CARGO_GROUP +function AI_CARGO_GROUP:New( CargoSet, Type, Name, ReportRadius, NearRadius ) + local self = BASE:Inherit( self, AI_CARGO:New( Type, Name, 0, ReportRadius, NearRadius ) ) -- #AI_CARGO_GROUP + self:F( { Type, Name, ReportRadius, NearRadius } ) + + self.CargoSet = CargoSet + + + return self +end + +end -- AI_CARGO_GROUP + +do -- AI_CARGO_GROUPED + + --- @type AI_CARGO_GROUPED + -- @extends AI.AI_Cargo#AI_CARGO_GROUP + AI_CARGO_GROUPED = { + ClassName = "AI_CARGO_GROUPED", + } + +--- AI_CARGO_GROUPED constructor. +-- @param #AI_CARGO_GROUPED self +-- @param Core.Set#Set_BASE CargoSet +-- @param #string Type +-- @param #string Name +-- @param #number Weight +-- @param #number ReportRadius (optional) +-- @param #number NearRadius (optional) +-- @return #AI_CARGO_GROUPED +function AI_CARGO_GROUPED:New( CargoSet, Type, Name, ReportRadius, NearRadius ) + local self = BASE:Inherit( self, AI_CARGO_GROUP:New( CargoSet, Type, Name, ReportRadius, NearRadius ) ) -- #AI_CARGO_GROUPED + self:F( { Type, Name, ReportRadius, NearRadius } ) + + return self +end + +--- Enter Boarding State. +-- @param #AI_CARGO_GROUPED self +-- @param Wrapper.Unit#UNIT CargoCarrier +-- @param #string Event +-- @param #string From +-- @param #string To +function AI_CARGO_GROUPED:onenterBoarding( Event, From, To, CargoCarrier ) + self:F( { CargoCarrier.UnitName, Event, From, To } ) + + if From == "UnLoaded" then + + -- For each Cargo object within the AI_CARGO_GROUPED, route each object to the CargoLoadPointVec2 + self.CargoSet:ForEach( + function( Cargo ) + Cargo:__Board( 1, CargoCarrier ) + end + ) + + self:__Boarding( 1, CargoCarrier ) + end + +end + +--- Enter Loaded State. +-- @param #AI_CARGO_GROUPED self +-- @param Wrapper.Unit#UNIT CargoCarrier +-- @param #string Event +-- @param #string From +-- @param #string To +function AI_CARGO_GROUPED:onenterLoaded( Event, From, To, CargoCarrier ) + self:F( { CargoCarrier.UnitName, Event, From, To } ) + + if From == "UnLoaded" then + -- For each Cargo object within the AI_CARGO_GROUPED, load each cargo to the CargoCarrier. + for CargoID, Cargo in pairs( self.CargoSet:GetSet() ) do + Cargo:Load( CargoCarrier ) + end + end +end + +--- Leave Boarding State. +-- @param #AI_CARGO_GROUPED self +-- @param Wrapper.Unit#UNIT CargoCarrier +-- @param #string Event +-- @param #string From +-- @param #string To +function AI_CARGO_GROUPED:onleaveBoarding( Event, From, To, CargoCarrier ) + self:F( { CargoCarrier.UnitName, Event, From, To } ) + + local Boarded = true + + -- For each Cargo object within the AI_CARGO_GROUPED, route each object to the CargoLoadPointVec2 + for CargoID, Cargo in pairs( self.CargoSet:GetSet() ) do + self:T( Cargo.current ) + if not Cargo:is( "Loaded" ) then + Boarded = false + end + end + + if not Boarded then + self:__Boarding( 1, CargoCarrier ) + else + self:__Load( 1, CargoCarrier ) + end + return Boarded +end + +--- Enter UnBoarding State. +-- @param #AI_CARGO_GROUPED self +-- @param Core.Point#POINT_VEC2 ToPointVec2 +-- @param #string Event +-- @param #string From +-- @param #string To +function AI_CARGO_GROUPED:onenterUnBoarding( Event, From, To, ToPointVec2 ) + self:F() + + local Timer = 1 + + if From == "Loaded" then + + -- For each Cargo object within the AI_CARGO_GROUPED, route each object to the CargoLoadPointVec2 + self.CargoSet:ForEach( + function( Cargo ) + Cargo:__UnBoard( Timer, ToPointVec2 ) + Timer = Timer + 10 + end + ) + + self:__UnBoarding( 1, ToPointVec2 ) + end + +end + +--- Leave UnBoarding State. +-- @param #AI_CARGO_GROUPED self +-- @param Core.Point#POINT_VEC2 ToPointVec2 +-- @param #string Event +-- @param #string From +-- @param #string To +function AI_CARGO_GROUPED:onleaveUnBoarding( Event, From, To, ToPointVec2 ) + self:F( { ToPointVec2, Event, From, To } ) + + local Angle = 180 + local Speed = 10 + local Distance = 5 + + if From == "UnBoarding" then + local UnBoarded = true + + -- For each Cargo object within the AI_CARGO_GROUPED, route each object to the CargoLoadPointVec2 + for CargoID, Cargo in pairs( self.CargoSet:GetSet() ) do + self:T( Cargo.current ) + if not Cargo:is( "UnLoaded" ) then + UnBoarded = false + end + end + + if UnBoarded then + return true + else + self:__UnBoarding( 1, ToPointVec2 ) + end + + return false + end + +end + +--- UnBoard Event. +-- @param #AI_CARGO_GROUPED self +-- @param Core.Point#POINT_VEC2 ToPointVec2 +-- @param #string Event +-- @param #string From +-- @param #string To +function AI_CARGO_GROUPED:onafterUnBoarding( Event, From, To, ToPointVec2 ) + self:F( { ToPointVec2, Event, From, To } ) + + self:__UnLoad( 1, ToPointVec2 ) +end + + + +--- Enter UnLoaded State. +-- @param #AI_CARGO_GROUPED self +-- @param Core.Point#POINT_VEC2 +-- @param #string Event +-- @param #string From +-- @param #string To +function AI_CARGO_GROUPED:onenterUnLoaded( Event, From, To, ToPointVec2 ) + self:F( { ToPointVec2, Event, From, To } ) + + if From == "Loaded" then + + -- For each Cargo object within the AI_CARGO_GROUPED, route each object to the CargoLoadPointVec2 + self.CargoSet:ForEach( + function( Cargo ) + Cargo:UnLoad( ToPointVec2 ) + end + ) + + end + +end + +end -- AI_CARGO_GROUPED + + + diff --git a/Moose Development/Moose/AI/AI_Patrol.lua b/Moose Development/Moose/AI/AI_Patrol.lua new file mode 100644 index 000000000..6781fe964 --- /dev/null +++ b/Moose Development/Moose/AI/AI_Patrol.lua @@ -0,0 +1,378 @@ +--- (AI) (FSM) Make AI patrol routes or zones. +-- +-- === +-- +-- 1) @{#AI_PATROLZONE} class, extends @{Core.Fsm#FSM_CONTROLLABLE} +-- ================================================================ +-- The @{#AI_PATROLZONE} class implements the core functions to patrol a @{Zone} by an AIR @{Controllable} @{Group}. +-- The patrol algorithm works that for each airplane patrolling, upon arrival at the patrol zone, +-- a random point is selected as the route point within the 3D space, within the given boundary limits. +-- The airplane will fly towards the random 3D point within the patrol zone, using a random speed within the given altitude and speed limits. +-- Upon arrival at the random 3D point, a new 3D random point will be selected within the patrol zone using the given limits. +-- This cycle will continue until a fuel treshold has been reached by the airplane. +-- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. +-- +-- 1.1) AI_PATROLZONE constructor: +-- ---------------------------- +-- +-- * @{#AI_PATROLZONE.New}(): Creates a new AI_PATROLZONE object. +-- +-- 1.2) AI_PATROLZONE state machine: +-- ---------------------------------- +-- The AI_PATROLZONE is a state machine: it manages the different events and states of the AIControllable it is controlling. +-- +-- ### 1.2.1) AI_PATROLZONE Events: +-- +-- * @{#AI_PATROLZONE.Route}( AIControllable ): A new 3D route point is selected and the AIControllable will fly towards that point with the given speed. +-- * @{#AI_PATROLZONE.Patrol}( AIControllable ): The AIControllable reports it is patrolling. This event is called every 30 seconds. +-- * @{#AI_PATROLZONE.RTB}( AIControllable ): The AIControllable will report return to base. +-- * @{#AI_PATROLZONE.End}( AIControllable ): The end of the AI_PATROLZONE process. +-- * @{#AI_PATROLZONE.Dead}( AIControllable ): The AIControllable is dead. The AI_PATROLZONE process will be ended. +-- +-- ### 1.2.2) AI_PATROLZONE States: +-- +-- * **Route**: A new 3D route point is selected and the AIControllable will fly towards that point with the given speed. +-- * **Patrol**: The AIControllable is patrolling. This state is set every 30 seconds, so every 30 seconds, a state transition method can be used. +-- * **RTB**: The AIControllable reports it wants to return to the base. +-- * **Dead**: The AIControllable is dead ... +-- * **End**: The process has come to an end. +-- +-- ### 1.2.3) AI_PATROLZONE state transition methods: +-- +-- State transition functions can be set **by the mission designer** customizing or improving the behaviour of the state. +-- There are 2 moments when state transition methods will be called by the state machine: +-- +-- * **Before** the state transition. +-- The state transition method needs to start with the name **OnBefore + the name of the state**. +-- If the state transition method returns false, then the processing of the state transition will not be done! +-- If you want to change the behaviour of the AIControllable at this event, return false, +-- but then you'll need to specify your own logic using the AIControllable! +-- +-- * **After** the state transition. +-- The state transition method needs to start with the name **OnAfter + the name of the state**. +-- These state transition methods need to provide a return value, which is specified at the function description. +-- +-- An example how to manage a state transition for an AI_PATROLZONE object **Patrol** for the state **RTB**: +-- +-- local PatrolZoneGroup = GROUP:FindByName( "Patrol Zone" ) +-- local PatrolZone = ZONE_POLYGON:New( "PatrolZone", PatrolZoneGroup ) +-- +-- local PatrolSpawn = SPAWN:New( "Patrol Group" ) +-- local PatrolGroup = PatrolSpawn:Spawn() +-- +-- local Patrol = AI_PATROLZONE:New( PatrolZone, 3000, 6000, 300, 600 ) +-- Patrol:SetControllable( PatrolGroup ) +-- Patrol:ManageFuel( 0.2, 60 ) +-- +-- **OnBefore**RTB( AIGroup ) will be called by the AI_PATROLZONE object when the AIGroup reports RTB, but **before** the RTB default action is processed by the AI_PATROLZONE object. +-- +-- --- State transition function for the AI_PATROLZONE **Patrol** object +-- -- @param #AI_PATROLZONE self +-- -- @param Wrapper.Controllable#CONTROLLABLE AIGroup +-- -- @return #boolean If false is returned, then the OnAfter state transition method will not be called. +-- function Patrol:OnBeforeRTB( AIGroup ) +-- AIGroup:MessageToRed( "Returning to base", 20 ) +-- end +-- +-- **OnAfter**RTB( AIGroup ) will be called by the AI_PATROLZONE object when the AIGroup reports RTB, but **after** the RTB default action was processed by the AI_PATROLZONE object. +-- +-- --- State transition function for the AI_PATROLZONE **Patrol** object +-- -- @param #AI_PATROLZONE self +-- -- @param Wrapper.Controllable#CONTROLLABLE AIGroup +-- -- @return #Wrapper.Controllable#CONTROLLABLE The new AIGroup object that is set to be patrolling the zone. +-- function Patrol:OnAfterRTB( AIGroup ) +-- return PatrolSpawn:Spawn() +-- end +-- +-- 1.3) Manage the AI_PATROLZONE parameters: +-- ------------------------------------------ +-- The following methods are available to modify the parameters of a AI_PATROLZONE object: +-- +-- * @{#AI_PATROLZONE.SetControllable}(): Set the AIControllable. +-- * @{#AI_PATROLZONE.GetControllable}(): Get the AIControllable. +-- * @{#AI_PATROLZONE.SetSpeed}(): Set the patrol speed of the AI, for the next patrol. +-- * @{#AI_PATROLZONE.SetAltitude}(): Set altitude of the AI, for the next patrol. +-- +-- 1.3) Manage the out of fuel in the AI_PATROLZONE: +-- ---------------------------------------------- +-- When the AIControllable is out of fuel, it is required that a new AIControllable is started, before the old AIControllable can return to the home base. +-- Therefore, with a parameter and a calculation of the distance to the home base, the fuel treshold is calculated. +-- When the fuel treshold is reached, the AIControllable will continue for a given time its patrol task in orbit, while a new AIControllable is targetted to the AI_PATROLZONE. +-- Once the time is finished, the old AIControllable will return to the base. +-- Use the method @{#AI_PATROLZONE.ManageFuel}() to have this proces in place. +-- +-- ==== +-- +-- **API CHANGE HISTORY** +-- ====================== +-- +-- The underlying change log documents the API changes. Please read this carefully. The following notation is used: +-- +-- * **Added** parts are expressed in bold type face. +-- * _Removed_ parts are expressed in italic type face. +-- +-- Hereby the change log: +-- +-- 2016-09-01: Initial class and API. +-- +-- === +-- +-- AUTHORS and CONTRIBUTIONS +-- ========================= +-- +-- ### Contributions: +-- +-- * **DutchBaron**: Testing. +-- * **Pikey**: Testing and API concept review. +-- +-- ### Authors: +-- +-- * **FlightControl**: Design & Programming. +-- +-- +-- @module Patrol + +-- State Transition Functions + +--- OnBefore State Transition Function +-- @function [parent=#AI_PATROLZONE] OnBeforeRoute +-- @param #AI_PATROLZONE self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable +-- @return #boolean + +--- OnAfter State Transition Function +-- @function [parent=#AI_PATROLZONE] OnAfterRoute +-- @param #AI_PATROLZONE self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable + + + +--- AI_PATROLZONE class +-- @type AI_PATROLZONE +-- @field Wrapper.Controllable#CONTROLLABLE AIControllable The @{Controllable} patrolling. +-- @field Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed. +-- @field Dcs.DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. +-- @field Dcs.DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. +-- @field Dcs.DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Controllable} in km/h. +-- @field Dcs.DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Controllable} in km/h. +-- @extends Core.Fsm#FSM_CONTROLLABLE +AI_PATROLZONE = { + ClassName = "AI_PATROLZONE", +} + + + +--- Creates a new AI_PATROLZONE object +-- @param #AI_PATROLZONE self +-- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed. +-- @param Dcs.DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. +-- @param Dcs.DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. +-- @param Dcs.DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Controllable} in km/h. +-- @param Dcs.DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Controllable} in km/h. +-- @return #AI_PATROLZONE self +-- @usage +-- -- Define a new AI_PATROLZONE Object. This PatrolArea will patrol an AIControllable within PatrolZone between 3000 and 6000 meters, with a variying speed between 600 and 900 km/h. +-- PatrolZone = ZONE:New( 'PatrolZone' ) +-- PatrolSpawn = SPAWN:New( 'Patrol Group' ) +-- PatrolArea = AI_PATROLZONE:New( PatrolZone, 3000, 6000, 600, 900 ) +function AI_PATROLZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed ) + + -- Inherits from BASE + local self = BASE:Inherit( self, FSM_CONTROLLABLE:New() ) -- Core.Fsm#FSM_CONTROLLABLE + + self:SetStartState( "None" ) + self:AddTransition( "*", "Start", "Route" ) + self:AddTransition( "*", "Route", "Route" ) + self:AddTransition( { "Patrol", "Route" }, "Patrol", "Patrol" ) + self:AddTransition( "Patrol", "RTB", "RTB" ) + self:AddTransition( "*", "End", "End" ) + self:AddTransition( "*", "Dead", "End" ) + + self.PatrolZone = PatrolZone + self.PatrolFloorAltitude = PatrolFloorAltitude + self.PatrolCeilingAltitude = PatrolCeilingAltitude + self.PatrolMinSpeed = PatrolMinSpeed + self.PatrolMaxSpeed = PatrolMaxSpeed + + return self +end + + + + +--- Sets (modifies) the minimum and maximum speed of the patrol. +-- @param #AI_PATROLZONE self +-- @param Dcs.DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Controllable} in km/h. +-- @param Dcs.DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Controllable} in km/h. +-- @return #AI_PATROLZONE self +function AI_PATROLZONE:SetSpeed( PatrolMinSpeed, PatrolMaxSpeed ) + self:F2( { PatrolMinSpeed, PatrolMaxSpeed } ) + + self.PatrolMinSpeed = PatrolMinSpeed + self.PatrolMaxSpeed = PatrolMaxSpeed +end + + + +--- Sets the floor and ceiling altitude of the patrol. +-- @param #AI_PATROLZONE self +-- @param Dcs.DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. +-- @param Dcs.DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. +-- @return #AI_PATROLZONE self +function AI_PATROLZONE:SetAltitude( PatrolFloorAltitude, PatrolCeilingAltitude ) + self:F2( { PatrolFloorAltitude, PatrolCeilingAltitude } ) + + self.PatrolFloorAltitude = PatrolFloorAltitude + self.PatrolCeilingAltitude = PatrolCeilingAltitude +end + + + +--- @param Wrapper.Controllable#CONTROLLABLE AIControllable +function _NewPatrolRoute( AIControllable ) + + AIControllable:T( "NewPatrolRoute" ) + local PatrolZone = AIControllable:GetState( AIControllable, "PatrolZone" ) -- PatrolCore.Zone#AI_PATROLZONE + PatrolZone:__Route( 1 ) +end + + + + +--- When the AIControllable is out of fuel, it is required that a new AIControllable is started, before the old AIControllable can return to the home base. +-- Therefore, with a parameter and a calculation of the distance to the home base, the fuel treshold is calculated. +-- When the fuel treshold is reached, the AIControllable will continue for a given time its patrol task in orbit, while a new AIControllable is targetted to the AI_PATROLZONE. +-- Once the time is finished, the old AIControllable will return to the base. +-- @param #AI_PATROLZONE self +-- @param #number PatrolFuelTresholdPercentage The treshold in percentage (between 0 and 1) when the AIControllable is considered to get out of fuel. +-- @param #number PatrolOutOfFuelOrbitTime The amount of seconds the out of fuel AIControllable will orbit before returning to the base. +-- @return #AI_PATROLZONE self +function AI_PATROLZONE:ManageFuel( PatrolFuelTresholdPercentage, PatrolOutOfFuelOrbitTime ) + + self.PatrolManageFuel = true + self.PatrolFuelTresholdPercentage = PatrolFuelTresholdPercentage + self.PatrolOutOfFuelOrbitTime = PatrolOutOfFuelOrbitTime + + return self +end + +--- Defines a new patrol route using the @{Process_PatrolZone} parameters and settings. +-- @param #AI_PATROLZONE self +-- @return #AI_PATROLZONE self +function AI_PATROLZONE:onenterRoute() + + self:F2() + + local PatrolRoute = {} + + if self.Controllable:IsAlive() then + --- Determine if the AIControllable is within the PatrolZone. + -- If not, make a waypoint within the to that the AIControllable will fly at maximum speed to that point. + +-- --- Calculate the current route point. +-- local CurrentVec2 = self.Controllable:GetVec2() +-- local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude() +-- local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) +-- local CurrentRoutePoint = CurrentPointVec3:RoutePointAir( +-- POINT_VEC3.RoutePointAltType.BARO, +-- POINT_VEC3.RoutePointType.TurningPoint, +-- POINT_VEC3.RoutePointAction.TurningPoint, +-- ToPatrolZoneSpeed, +-- true +-- ) +-- +-- PatrolRoute[#PatrolRoute+1] = CurrentRoutePoint + + self:T2( PatrolRoute ) + + if self.Controllable:IsNotInZone( self.PatrolZone ) then + --- Find a random 2D point in PatrolZone. + local ToPatrolZoneVec2 = self.PatrolZone:GetRandomVec2() + self:T2( ToPatrolZoneVec2 ) + + --- Define Speed and Altitude. + local ToPatrolZoneAltitude = math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ) + local ToPatrolZoneSpeed = self.PatrolMaxSpeed + self:T2( ToPatrolZoneSpeed ) + + --- Obtain a 3D @{Point} from the 2D point + altitude. + local ToPatrolZonePointVec3 = POINT_VEC3:New( ToPatrolZoneVec2.x, ToPatrolZoneAltitude, ToPatrolZoneVec2.y ) + + --- Create a route point of type air. + local ToPatrolZoneRoutePoint = ToPatrolZonePointVec3:RoutePointAir( + POINT_VEC3.RoutePointAltType.BARO, + POINT_VEC3.RoutePointType.TurningPoint, + POINT_VEC3.RoutePointAction.TurningPoint, + ToPatrolZoneSpeed, + true + ) + + PatrolRoute[#PatrolRoute+1] = ToPatrolZoneRoutePoint + + end + + --- Define a random point in the @{Zone}. The AI will fly to that point within the zone. + + --- Find a random 2D point in PatrolZone. + local ToTargetVec2 = self.PatrolZone:GetRandomVec2() + self:T2( ToTargetVec2 ) + + --- Define Speed and Altitude. + local ToTargetAltitude = math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ) + local ToTargetSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed ) + self:T2( { self.PatrolMinSpeed, self.PatrolMaxSpeed, ToTargetSpeed } ) + + --- Obtain a 3D @{Point} from the 2D point + altitude. + local ToTargetPointVec3 = POINT_VEC3:New( ToTargetVec2.x, ToTargetAltitude, ToTargetVec2.y ) + + --- Create a route point of type air. + local ToTargetRoutePoint = ToTargetPointVec3:RoutePointAir( + POINT_VEC3.RoutePointAltType.BARO, + POINT_VEC3.RoutePointType.TurningPoint, + POINT_VEC3.RoutePointAction.TurningPoint, + ToTargetSpeed, + true + ) + + --ToTargetPointVec3:SmokeRed() + + PatrolRoute[#PatrolRoute+1] = ToTargetRoutePoint + + --- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable... + self.Controllable:WayPointInitialize( PatrolRoute ) + + --- Do a trick, link the NewPatrolRoute function of the PATROLGROUP object to the AIControllable in a temporary variable ... + self.Controllable:SetState( self.Controllable, "PatrolZone", self ) + self.Controllable:WayPointFunction( #PatrolRoute, 1, "_NewPatrolRoute" ) + + --- NOW ACT_ROUTE THE GROUP! + self.Controllable:WayPointExecute( 1 ) + + self:__Patrol( 30 ) + end + +end + + +--- @param #AI_PATROLZONE self +function AI_PATROLZONE:onenterPatrol() + self:F2() + + if self.Controllable and self.Controllable:IsAlive() then + + local Fuel = self.Controllable:GetUnit(1):GetFuel() + if Fuel < self.PatrolFuelTresholdPercentage then + local OldAIControllable = self.Controllable + local AIControllableTemplate = self.Controllable:GetTemplate() + + local OrbitTask = OldAIControllable:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed ) + local TimedOrbitTask = OldAIControllable:TaskControlled( OrbitTask, OldAIControllable:TaskCondition(nil,nil,nil,nil,self.PatrolOutOfFuelOrbitTime,nil ) ) + OldAIControllable:SetTask( TimedOrbitTask, 10 ) + + self:RTB() + else + self:__Patrol( 30 ) -- Execute the Patrol event after 30 seconds. + end + end + +end diff --git a/Moose Development/Moose/Actions/Act_Account.lua b/Moose Development/Moose/Actions/Act_Account.lua new file mode 100644 index 000000000..8b8d914f1 --- /dev/null +++ b/Moose Development/Moose/Actions/Act_Account.lua @@ -0,0 +1,266 @@ +--- (SP) (MP) (FSM) Account for (Detect, count and report) DCS events occuring on DCS objects (units). +-- +-- === +-- +-- # @{#ACT_ACCOUNT} FSM class, extends @{Core.Fsm#FSM_PROCESS} +-- +-- ## ACT_ACCOUNT state machine: +-- +-- This class is a state machine: it manages a process that is triggered by events causing state transitions to occur. +-- All derived classes from this class will start with the class name, followed by a \_. See the relevant derived class descriptions below. +-- Each derived class follows exactly the same process, using the same events and following the same state transitions, +-- but will have **different implementation behaviour** upon each event or state transition. +-- +-- ### ACT_ACCOUNT **Events**: +-- +-- These are the events defined in this class: +-- +-- * **Start**: The process is started. The process will go into the Report state. +-- * **Event**: A relevant event has occured that needs to be accounted for. The process will go into the Account state. +-- * **Report**: The process is reporting to the player the accounting status of the DCS events. +-- * **More**: There are more DCS events that need to be accounted for. The process will go back into the Report state. +-- * **NoMore**: There are no more DCS events that need to be accounted for. The process will go into the Success state. +-- +-- ### ACT_ACCOUNT **Event methods**: +-- +-- Event methods are available (dynamically allocated by the state machine), that accomodate for state transitions occurring in the process. +-- There are two types of event methods, which you can use to influence the normal mechanisms in the state machine: +-- +-- * **Immediate**: The event method has exactly the name of the event. +-- * **Delayed**: The event method starts with a __ + the name of the event. The first parameter of the event method is a number value, expressing the delay in seconds when the event will be executed. +-- +-- ### ACT_ACCOUNT **States**: +-- +-- * **Assigned**: The player is assigned to the task. This is the initialization state for the process. +-- * **Waiting**: the process is waiting for a DCS event to occur within the simulator. This state is set automatically. +-- * **Report**: The process is Reporting to the players in the group of the unit. This state is set automatically every 30 seconds. +-- * **Account**: The relevant DCS event has occurred, and is accounted for. +-- * **Success (*)**: All DCS events were accounted for. +-- * **Failed (*)**: The process has failed. +-- +-- (*) End states of the process. +-- +-- ### ACT_ACCOUNT state transition methods: +-- +-- State transition functions can be set **by the mission designer** customizing or improving the behaviour of the state. +-- There are 2 moments when state transition methods will be called by the state machine: +-- +-- * **Before** the state transition. +-- The state transition method needs to start with the name **OnBefore + the name of the state**. +-- If the state transition method returns false, then the processing of the state transition will not be done! +-- If you want to change the behaviour of the AIControllable at this event, return false, +-- but then you'll need to specify your own logic using the AIControllable! +-- +-- * **After** the state transition. +-- The state transition method needs to start with the name **OnAfter + the name of the state**. +-- These state transition methods need to provide a return value, which is specified at the function description. +-- +-- # 1) @{#ACT_ACCOUNT_DEADS} FSM class, extends @{Fsm.Account#ACT_ACCOUNT} +-- +-- The ACT_ACCOUNT_DEADS class accounts (detects, counts and reports) successful kills of DCS units. +-- The process is given a @{Set} of units that will be tracked upon successful destruction. +-- The process will end after each target has been successfully destroyed. +-- Each successful dead will trigger an Account state transition that can be scored, modified or administered. +-- +-- +-- ## ACT_ACCOUNT_DEADS constructor: +-- +-- * @{#ACT_ACCOUNT_DEADS.New}(): Creates a new ACT_ACCOUNT_DEADS object. +-- +-- === +-- +-- @module Account + + +do -- ACT_ACCOUNT + + --- ACT_ACCOUNT class + -- @type ACT_ACCOUNT + -- @field Set#SET_UNIT TargetSetUnit + -- @extends Core.Fsm#FSM_PROCESS + ACT_ACCOUNT = { + ClassName = "ACT_ACCOUNT", + TargetSetUnit = nil, + } + + --- Creates a new DESTROY process. + -- @param #ACT_ACCOUNT self + -- @return #ACT_ACCOUNT + function ACT_ACCOUNT:New() + + -- Inherits from BASE + local self = BASE:Inherit( self, FSM_PROCESS:New() ) -- Core.Fsm#FSM_PROCESS + + self:AddTransition( "Assigned", "Start", "Waiting") + self:AddTransition( "*", "Wait", "Waiting") + self:AddTransition( "*", "Report", "Report") + self:AddTransition( "*", "Event", "Account") + self:AddTransition( "Account", "More", "Wait") + self:AddTransition( "Account", "NoMore", "Accounted") + self:AddTransition( "*", "Fail", "Failed") + + self:AddEndState( "Accounted" ) + self:AddEndState( "Failed" ) + + self:SetStartState( "Assigned" ) + + return self + end + + --- Process Events + + --- StateMachine callback function + -- @param #ACT_ACCOUNT self + -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit + -- @param #string Event + -- @param #string From + -- @param #string To + function ACT_ACCOUNT:onafterStart( ProcessUnit, Event, From, To ) + + self:EventOnDead( self.onfuncEventDead ) + + self:__Wait( 1 ) + end + + + --- StateMachine callback function + -- @param #ACT_ACCOUNT self + -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit + -- @param #string Event + -- @param #string From + -- @param #string To + function ACT_ACCOUNT:onenterWaiting( ProcessUnit, Event, From, To ) + + if self.DisplayCount >= self.DisplayInterval then + self:Report() + self.DisplayCount = 1 + else + self.DisplayCount = self.DisplayCount + 1 + end + + return true -- Process always the event. + end + + --- StateMachine callback function + -- @param #ACT_ACCOUNT self + -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit + -- @param #string Event + -- @param #string From + -- @param #string To + function ACT_ACCOUNT:onafterEvent( ProcessUnit, Event, From, To, Event ) + + self:__NoMore( 1 ) + end + +end -- ACT_ACCOUNT + +do -- ACT_ACCOUNT_DEADS + + --- ACT_ACCOUNT_DEADS class + -- @type ACT_ACCOUNT_DEADS + -- @field Set#SET_UNIT TargetSetUnit + -- @extends #ACT_ACCOUNT + ACT_ACCOUNT_DEADS = { + ClassName = "ACT_ACCOUNT_DEADS", + TargetSetUnit = nil, + } + + + --- Creates a new DESTROY process. + -- @param #ACT_ACCOUNT_DEADS self + -- @param Set#SET_UNIT TargetSetUnit + -- @param #string TaskName + function ACT_ACCOUNT_DEADS:New( TargetSetUnit, TaskName ) + -- Inherits from BASE + local self = BASE:Inherit( self, ACT_ACCOUNT:New() ) -- #ACT_ACCOUNT_DEADS + + self.TargetSetUnit = TargetSetUnit + self.TaskName = TaskName + + self.DisplayInterval = 30 + self.DisplayCount = 30 + self.DisplayMessage = true + self.DisplayTime = 10 -- 10 seconds is the default + self.DisplayCategory = "HQ" -- Targets is the default display category + + return self + end + + function ACT_ACCOUNT_DEADS:Init( FsmAccount ) + + self.TargetSetUnit = FsmAccount.TargetSetUnit + self.TaskName = FsmAccount.TaskName + end + + + + function ACT_ACCOUNT_DEADS:_Destructor() + self:E("_Destructor") + + self:EventRemoveAll() + + end + + --- Process Events + + --- StateMachine callback function + -- @param #ACT_ACCOUNT_DEADS self + -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit + -- @param #string Event + -- @param #string From + -- @param #string To + function ACT_ACCOUNT_DEADS:onenterReport( ProcessUnit, Event, From, To ) + self:E( { ProcessUnit, Event, From, To } ) + + self:Message( "Your group with assigned " .. self.TaskName .. " task has " .. self.TargetSetUnit:GetUnitTypesText() .. " targets left to be destroyed." ) + end + + + --- StateMachine callback function + -- @param #ACT_ACCOUNT_DEADS self + -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit + -- @param #string Event + -- @param #string From + -- @param #string To + function ACT_ACCOUNT_DEADS:onenterAccount( ProcessUnit, Event, From, To, EventData ) + self:T( { ProcessUnit, EventData, Event, From, To } ) + + self:T({self.Controllable}) + + self.TargetSetUnit:Flush() + + if self.TargetSetUnit:FindUnit( EventData.IniUnitName ) then + local TaskGroup = ProcessUnit:GetGroup() + self.TargetSetUnit:RemoveUnitsByName( EventData.IniUnitName ) + self:Message( "You hit a target. Your group with assigned " .. self.TaskName .. " task has " .. self.TargetSetUnit:Count() .. " targets ( " .. self.TargetSetUnit:GetUnitTypesText() .. " ) left to be destroyed." ) + end + end + + --- StateMachine callback function + -- @param #ACT_ACCOUNT_DEADS self + -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit + -- @param #string Event + -- @param #string From + -- @param #string To + function ACT_ACCOUNT_DEADS:onafterEvent( ProcessUnit, Event, From, To, EventData ) + + if self.TargetSetUnit:Count() > 0 then + self:__More( 1 ) + else + self:__NoMore( 1 ) + end + end + + --- DCS Events + + --- @param #ACT_ACCOUNT_DEADS self + -- @param Event#EVENTDATA EventData + function ACT_ACCOUNT_DEADS:onfuncEventDead( EventData ) + self:T( { "EventDead", EventData } ) + + if EventData.IniDCSUnit then + self:__Event( 1, EventData ) + end + end + +end -- ACT_ACCOUNT DEADS diff --git a/Moose Development/Moose/Actions/Act_Assign.lua b/Moose Development/Moose/Actions/Act_Assign.lua new file mode 100644 index 000000000..a1303fcf5 --- /dev/null +++ b/Moose Development/Moose/Actions/Act_Assign.lua @@ -0,0 +1,293 @@ +--- (SP) (MP) (FSM) Accept or reject process for player (task) assignments. +-- +-- === +-- +-- # @{#ACT_ASSIGN} FSM template class, extends @{Core.Fsm#FSM_PROCESS} +-- +-- ## ACT_ASSIGN state machine: +-- +-- This class is a state machine: it manages a process that is triggered by events causing state transitions to occur. +-- All derived classes from this class will start with the class name, followed by a \_. See the relevant derived class descriptions below. +-- Each derived class follows exactly the same process, using the same events and following the same state transitions, +-- but will have **different implementation behaviour** upon each event or state transition. +-- +-- ### ACT_ASSIGN **Events**: +-- +-- These are the events defined in this class: +-- +-- * **Start**: Start the tasking acceptance process. +-- * **Assign**: Assign the task. +-- * **Reject**: Reject the task.. +-- +-- ### ACT_ASSIGN **Event methods**: +-- +-- Event methods are available (dynamically allocated by the state machine), that accomodate for state transitions occurring in the process. +-- There are two types of event methods, which you can use to influence the normal mechanisms in the state machine: +-- +-- * **Immediate**: The event method has exactly the name of the event. +-- * **Delayed**: The event method starts with a __ + the name of the event. The first parameter of the event method is a number value, expressing the delay in seconds when the event will be executed. +-- +-- ### ACT_ASSIGN **States**: +-- +-- * **UnAssigned**: The player has not accepted the task. +-- * **Assigned (*)**: The player has accepted the task. +-- * **Rejected (*)**: The player has not accepted the task. +-- * **Waiting**: The process is awaiting player feedback. +-- * **Failed (*)**: The process has failed. +-- +-- (*) End states of the process. +-- +-- ### ACT_ASSIGN state transition methods: +-- +-- State transition functions can be set **by the mission designer** customizing or improving the behaviour of the state. +-- There are 2 moments when state transition methods will be called by the state machine: +-- +-- * **Before** the state transition. +-- The state transition method needs to start with the name **OnBefore + the name of the state**. +-- If the state transition method returns false, then the processing of the state transition will not be done! +-- If you want to change the behaviour of the AIControllable at this event, return false, +-- but then you'll need to specify your own logic using the AIControllable! +-- +-- * **After** the state transition. +-- The state transition method needs to start with the name **OnAfter + the name of the state**. +-- These state transition methods need to provide a return value, which is specified at the function description. +-- +-- === +-- +-- # 1) @{#ACT_ASSIGN_ACCEPT} class, extends @{Fsm.Assign#ACT_ASSIGN} +-- +-- The ACT_ASSIGN_ACCEPT class accepts by default a task for a player. No player intervention is allowed to reject the task. +-- +-- ## 1.1) ACT_ASSIGN_ACCEPT constructor: +-- +-- * @{#ACT_ASSIGN_ACCEPT.New}(): Creates a new ACT_ASSIGN_ACCEPT object. +-- +-- === +-- +-- # 2) @{#ACT_ASSIGN_MENU_ACCEPT} class, extends @{Fsm.Assign#ACT_ASSIGN} +-- +-- The ACT_ASSIGN_MENU_ACCEPT class accepts a task when the player accepts the task through an added menu option. +-- This assignment type is useful to conditionally allow the player to choose whether or not he would accept the task. +-- The assignment type also allows to reject the task. +-- +-- ## 2.1) ACT_ASSIGN_MENU_ACCEPT constructor: +-- ----------------------------------------- +-- +-- * @{#ACT_ASSIGN_MENU_ACCEPT.New}(): Creates a new ACT_ASSIGN_MENU_ACCEPT object. +-- +-- === +-- +-- @module Assign + + +do -- ACT_ASSIGN + + --- ACT_ASSIGN class + -- @type ACT_ASSIGN + -- @field Tasking.Task#TASK Task + -- @field Wrapper.Unit#UNIT ProcessUnit + -- @field Core.Zone#ZONE_BASE TargetZone + -- @extends Core.Fsm#FSM_PROCESS + ACT_ASSIGN = { + ClassName = "ACT_ASSIGN", + } + + + --- Creates a new task assignment state machine. The process will accept the task by default, no player intervention accepted. + -- @param #ACT_ASSIGN self + -- @return #ACT_ASSIGN The task acceptance process. + function ACT_ASSIGN:New() + + -- Inherits from BASE + local self = BASE:Inherit( self, FSM_PROCESS:New( "ACT_ASSIGN" ) ) -- Core.Fsm#FSM_PROCESS + + self:AddTransition( "UnAssigned", "Start", "Waiting" ) + self:AddTransition( "Waiting", "Assign", "Assigned" ) + self:AddTransition( "Waiting", "Reject", "Rejected" ) + self:AddTransition( "*", "Fail", "Failed" ) + + self:AddEndState( "Assigned" ) + self:AddEndState( "Rejected" ) + self:AddEndState( "Failed" ) + + self:SetStartState( "UnAssigned" ) + + return self + end + +end -- ACT_ASSIGN + + + +do -- ACT_ASSIGN_ACCEPT + + --- ACT_ASSIGN_ACCEPT class + -- @type ACT_ASSIGN_ACCEPT + -- @field Tasking.Task#TASK Task + -- @field Wrapper.Unit#UNIT ProcessUnit + -- @field Core.Zone#ZONE_BASE TargetZone + -- @extends #ACT_ASSIGN + ACT_ASSIGN_ACCEPT = { + ClassName = "ACT_ASSIGN_ACCEPT", + } + + + --- Creates a new task assignment state machine. The process will accept the task by default, no player intervention accepted. + -- @param #ACT_ASSIGN_ACCEPT self + -- @param #string TaskBriefing + function ACT_ASSIGN_ACCEPT:New( TaskBriefing ) + + local self = BASE:Inherit( self, ACT_ASSIGN:New() ) -- #ACT_ASSIGN_ACCEPT + + self.TaskBriefing = TaskBriefing + + return self + end + + function ACT_ASSIGN_ACCEPT:Init( FsmAssign ) + + self.TaskBriefing = FsmAssign.TaskBriefing + end + + --- StateMachine callback function + -- @param #ACT_ASSIGN_ACCEPT self + -- @param Wrapper.Unit#UNIT ProcessUnit + -- @param #string Event + -- @param #string From + -- @param #string To + function ACT_ASSIGN_ACCEPT:onafterStart( ProcessUnit, Event, From, To ) + self:E( { ProcessUnit, Event, From, To } ) + + self:__Assign( 1 ) + end + + --- StateMachine callback function + -- @param #ACT_ASSIGN_ACCEPT self + -- @param Wrapper.Unit#UNIT ProcessUnit + -- @param #string Event + -- @param #string From + -- @param #string To + function ACT_ASSIGN_ACCEPT:onenterAssigned( ProcessUnit, Event, From, To ) + env.info( "in here" ) + self:E( { ProcessUnit, Event, From, To } ) + + local ProcessGroup = ProcessUnit:GetGroup() + + self:Message( "You are assigned to the task " .. self.Task:GetName() ) + + self.Task:Assign() + end + +end -- ACT_ASSIGN_ACCEPT + + +do -- ACT_ASSIGN_MENU_ACCEPT + + --- ACT_ASSIGN_MENU_ACCEPT class + -- @type ACT_ASSIGN_MENU_ACCEPT + -- @field Tasking.Task#TASK Task + -- @field Wrapper.Unit#UNIT ProcessUnit + -- @field Core.Zone#ZONE_BASE TargetZone + -- @extends #ACT_ASSIGN + ACT_ASSIGN_MENU_ACCEPT = { + ClassName = "ACT_ASSIGN_MENU_ACCEPT", + } + + --- Init. + -- @param #ACT_ASSIGN_MENU_ACCEPT self + -- @param #string TaskName + -- @param #string TaskBriefing + -- @return #ACT_ASSIGN_MENU_ACCEPT self + function ACT_ASSIGN_MENU_ACCEPT:New( TaskName, TaskBriefing ) + + -- Inherits from BASE + local self = BASE:Inherit( self, ACT_ASSIGN:New() ) -- #ACT_ASSIGN_MENU_ACCEPT + + self.TaskName = TaskName + self.TaskBriefing = TaskBriefing + + return self + end + + function ACT_ASSIGN_MENU_ACCEPT:Init( FsmAssign ) + + self.TaskName = FsmAssign.TaskName + self.TaskBriefing = FsmAssign.TaskBriefing + end + + + --- Creates a new task assignment state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator. + -- @param #ACT_ASSIGN_MENU_ACCEPT self + -- @param #string TaskName + -- @param #string TaskBriefing + -- @return #ACT_ASSIGN_MENU_ACCEPT self + function ACT_ASSIGN_MENU_ACCEPT:Init( TaskName, TaskBriefing ) + + self.TaskBriefing = TaskBriefing + self.TaskName = TaskName + + return self + end + + --- StateMachine callback function + -- @param #ACT_ASSIGN_MENU_ACCEPT self + -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit + -- @param #string Event + -- @param #string From + -- @param #string To + function ACT_ASSIGN_MENU_ACCEPT:onafterStart( ProcessUnit, Event, From, To ) + self:E( { ProcessUnit, Event, From, To } ) + + self:Message( "Access the radio menu to accept the task. You have 30 seconds or the assignment will be cancelled." ) + + local ProcessGroup = ProcessUnit:GetGroup() + + self.Menu = MENU_GROUP:New( ProcessGroup, "Task " .. self.TaskName .. " acceptance" ) + self.MenuAcceptTask = MENU_GROUP_COMMAND:New( ProcessGroup, "Accept task " .. self.TaskName, self.Menu, self.MenuAssign, self ) + self.MenuRejectTask = MENU_GROUP_COMMAND:New( ProcessGroup, "Reject task " .. self.TaskName, self.Menu, self.MenuReject, self ) + end + + --- Menu function. + -- @param #ACT_ASSIGN_MENU_ACCEPT self + function ACT_ASSIGN_MENU_ACCEPT:MenuAssign() + self:E( ) + + self:__Assign( 1 ) + end + + --- Menu function. + -- @param #ACT_ASSIGN_MENU_ACCEPT self + function ACT_ASSIGN_MENU_ACCEPT:MenuReject() + self:E( ) + + self:__Reject( 1 ) + end + + --- StateMachine callback function + -- @param #ACT_ASSIGN_MENU_ACCEPT self + -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit + -- @param #string Event + -- @param #string From + -- @param #string To + function ACT_ASSIGN_MENU_ACCEPT:onafterAssign( ProcessUnit, Event, From, To ) + self:E( { ProcessUnit.UnitNameEvent, From, To } ) + + self.Menu:Remove() + end + + --- StateMachine callback function + -- @param #ACT_ASSIGN_MENU_ACCEPT self + -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit + -- @param #string Event + -- @param #string From + -- @param #string To + function ACT_ASSIGN_MENU_ACCEPT:onafterReject( ProcessUnit, Event, From, To ) + self:E( { ProcessUnit.UnitName, Event, From, To } ) + + self.Menu:Remove() + --TODO: need to resolve this problem ... it has to do with the events ... + --self.Task:UnAssignFromUnit( ProcessUnit )needs to become a callback funtion call upon the event + ProcessUnit:Destroy() + end + +end -- ACT_ASSIGN_MENU_ACCEPT diff --git a/Moose Development/Moose/Actions/Act_Assist.lua b/Moose Development/Moose/Actions/Act_Assist.lua new file mode 100644 index 000000000..105a22bd4 --- /dev/null +++ b/Moose Development/Moose/Actions/Act_Assist.lua @@ -0,0 +1,206 @@ +--- (SP) (MP) (FSM) Route AI or players through waypoints or to zones. +-- +-- === +-- +-- # @{#ACT_ASSIST} FSM class, extends @{Core.Fsm#FSM_PROCESS} +-- +-- ## ACT_ASSIST state machine: +-- +-- This class is a state machine: it manages a process that is triggered by events causing state transitions to occur. +-- All derived classes from this class will start with the class name, followed by a \_. See the relevant derived class descriptions below. +-- Each derived class follows exactly the same process, using the same events and following the same state transitions, +-- but will have **different implementation behaviour** upon each event or state transition. +-- +-- ### ACT_ASSIST **Events**: +-- +-- These are the events defined in this class: +-- +-- * **Start**: The process is started. +-- * **Next**: The process is smoking the targets in the given zone. +-- +-- ### ACT_ASSIST **Event methods**: +-- +-- Event methods are available (dynamically allocated by the state machine), that accomodate for state transitions occurring in the process. +-- There are two types of event methods, which you can use to influence the normal mechanisms in the state machine: +-- +-- * **Immediate**: The event method has exactly the name of the event. +-- * **Delayed**: The event method starts with a __ + the name of the event. The first parameter of the event method is a number value, expressing the delay in seconds when the event will be executed. +-- +-- ### ACT_ASSIST **States**: +-- +-- * **None**: The controllable did not receive route commands. +-- * **AwaitSmoke (*)**: The process is awaiting to smoke the targets in the zone. +-- * **Smoking (*)**: The process is smoking the targets in the zone. +-- * **Failed (*)**: The process has failed. +-- +-- (*) End states of the process. +-- +-- ### ACT_ASSIST state transition methods: +-- +-- State transition functions can be set **by the mission designer** customizing or improving the behaviour of the state. +-- There are 2 moments when state transition methods will be called by the state machine: +-- +-- * **Before** the state transition. +-- The state transition method needs to start with the name **OnBefore + the name of the state**. +-- If the state transition method returns false, then the processing of the state transition will not be done! +-- If you want to change the behaviour of the AIControllable at this event, return false, +-- but then you'll need to specify your own logic using the AIControllable! +-- +-- * **After** the state transition. +-- The state transition method needs to start with the name **OnAfter + the name of the state**. +-- These state transition methods need to provide a return value, which is specified at the function description. +-- +-- === +-- +-- # 1) @{#ACT_ASSIST_SMOKE_TARGETS_ZONE} class, extends @{Fsm.Route#ACT_ASSIST} +-- +-- The ACT_ASSIST_SMOKE_TARGETS_ZONE class implements the core functions to smoke targets in a @{Zone}. +-- The targets are smoked within a certain range around each target, simulating a realistic smoking behaviour. +-- At random intervals, a new target is smoked. +-- +-- # 1.1) ACT_ASSIST_SMOKE_TARGETS_ZONE constructor: +-- +-- * @{#ACT_ASSIST_SMOKE_TARGETS_ZONE.New}(): Creates a new ACT_ASSIST_SMOKE_TARGETS_ZONE object. +-- +-- === +-- +-- @module Smoke + +do -- ACT_ASSIST + + --- ACT_ASSIST class + -- @type ACT_ASSIST + -- @extends Core.Fsm#FSM_PROCESS + ACT_ASSIST = { + ClassName = "ACT_ASSIST", + } + + --- Creates a new target smoking state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator. + -- @param #ACT_ASSIST self + -- @return #ACT_ASSIST + function ACT_ASSIST:New() + + -- Inherits from BASE + local self = BASE:Inherit( self, FSM_PROCESS:New( "ACT_ASSIST" ) ) -- Core.Fsm#FSM_PROCESS + + self:AddTransition( "None", "Start", "AwaitSmoke" ) + self:AddTransition( "AwaitSmoke", "Next", "Smoking" ) + self:AddTransition( "Smoking", "Next", "AwaitSmoke" ) + self:AddTransition( "*", "Stop", "Success" ) + self:AddTransition( "*", "Fail", "Failed" ) + + self:AddEndState( "Failed" ) + self:AddEndState( "Success" ) + + self:SetStartState( "None" ) + + return self + end + + --- Task Events + + --- StateMachine callback function + -- @param #ACT_ASSIST self + -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit + -- @param #string Event + -- @param #string From + -- @param #string To + function ACT_ASSIST:onafterStart( ProcessUnit, Event, From, To ) + + local ProcessGroup = ProcessUnit:GetGroup() + local MissionMenu = self:GetMission():GetMissionMenu( ProcessGroup ) + + local function MenuSmoke( MenuParam ) + self:E( MenuParam ) + local self = MenuParam.self + local SmokeColor = MenuParam.SmokeColor + self.SmokeColor = SmokeColor + self:__Next( 1 ) + end + + self.Menu = MENU_GROUP:New( ProcessGroup, "Target acquisition", MissionMenu ) + self.MenuSmokeBlue = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop blue smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Blue } ) + self.MenuSmokeGreen = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop green smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Green } ) + self.MenuSmokeOrange = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop Orange smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Orange } ) + self.MenuSmokeRed = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop Red smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Red } ) + self.MenuSmokeWhite = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop White smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.White } ) + end + +end + +do -- ACT_ASSIST_SMOKE_TARGETS_ZONE + + --- ACT_ASSIST_SMOKE_TARGETS_ZONE class + -- @type ACT_ASSIST_SMOKE_TARGETS_ZONE + -- @field Set#SET_UNIT TargetSetUnit + -- @field Core.Zone#ZONE_BASE TargetZone + -- @extends #ACT_ASSIST + ACT_ASSIST_SMOKE_TARGETS_ZONE = { + ClassName = "ACT_ASSIST_SMOKE_TARGETS_ZONE", + } + +-- function ACT_ASSIST_SMOKE_TARGETS_ZONE:_Destructor() +-- self:E("_Destructor") +-- +-- self.Menu:Remove() +-- self:EventRemoveAll() +-- end + + --- Creates a new target smoking state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator. + -- @param #ACT_ASSIST_SMOKE_TARGETS_ZONE self + -- @param Set#SET_UNIT TargetSetUnit + -- @param Core.Zone#ZONE_BASE TargetZone + function ACT_ASSIST_SMOKE_TARGETS_ZONE:New( TargetSetUnit, TargetZone ) + local self = BASE:Inherit( self, ACT_ASSIST:New() ) -- #ACT_ASSIST + + self.TargetSetUnit = TargetSetUnit + self.TargetZone = TargetZone + + return self + end + + function ACT_ASSIST_SMOKE_TARGETS_ZONE:Init( FsmSmoke ) + + self.TargetSetUnit = FsmSmoke.TargetSetUnit + self.TargetZone = FsmSmoke.TargetZone + end + + --- Creates a new target smoking state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator. + -- @param #ACT_ASSIST_SMOKE_TARGETS_ZONE self + -- @param Set#SET_UNIT TargetSetUnit + -- @param Core.Zone#ZONE_BASE TargetZone + -- @return #ACT_ASSIST_SMOKE_TARGETS_ZONE self + function ACT_ASSIST_SMOKE_TARGETS_ZONE:Init( TargetSetUnit, TargetZone ) + + self.TargetSetUnit = TargetSetUnit + self.TargetZone = TargetZone + + return self + end + + --- StateMachine callback function + -- @param #ACT_ASSIST_SMOKE_TARGETS_ZONE self + -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit + -- @param #string Event + -- @param #string From + -- @param #string To + function ACT_ASSIST_SMOKE_TARGETS_ZONE:onenterSmoking( ProcessUnit, Event, From, To ) + + self.TargetSetUnit:ForEachUnit( + --- @param Wrapper.Unit#UNIT SmokeUnit + function( SmokeUnit ) + if math.random( 1, ( 100 * self.TargetSetUnit:Count() ) / 4 ) <= 100 then + SCHEDULER:New( self, + function() + if SmokeUnit:IsAlive() then + SmokeUnit:Smoke( self.SmokeColor, 150 ) + end + end, {}, math.random( 10, 60 ) + ) + end + end + ) + + end + +end \ No newline at end of file diff --git a/Moose Development/Moose/Process_JTAC.lua b/Moose Development/Moose/Actions/Act_JTAC.lua similarity index 89% rename from Moose Development/Moose/Process_JTAC.lua rename to Moose Development/Moose/Actions/Act_JTAC.lua index ac5ae5e69..592ee5e7e 100644 --- a/Moose Development/Moose/Process_JTAC.lua +++ b/Moose Development/Moose/Actions/Act_JTAC.lua @@ -2,9 +2,9 @@ --- PROCESS_JTAC class -- @type PROCESS_JTAC --- @field Unit#UNIT ProcessUnit --- @field Set#SET_UNIT TargetSetUnit --- @extends Process#PROCESS +-- @field Wrapper.Unit#UNIT ProcessUnit +-- @field Core.Set#SET_UNIT TargetSetUnit +-- @extends Core.Fsm#FSM_PROCESS PROCESS_JTAC = { ClassName = "PROCESS_JTAC", Fsm = {}, @@ -14,10 +14,10 @@ PROCESS_JTAC = { --- Creates a new DESTROY process. -- @param #PROCESS_JTAC self --- @param Task#TASK Task --- @param Unit#UNIT ProcessUnit --- @param Set#SET_UNIT TargetSetUnit --- @param Unit#UNIT FACUnit +-- @param Tasking.Task#TASK Task +-- @param Wrapper.Unit#UNIT ProcessUnit +-- @param Core.Set#SET_UNIT TargetSetUnit +-- @param Wrapper.Unit#UNIT FACUnit -- @return #PROCESS_JTAC self function PROCESS_JTAC:New( Task, ProcessUnit, TargetSetUnit, FACUnit ) @@ -34,7 +34,7 @@ function PROCESS_JTAC:New( Task, ProcessUnit, TargetSetUnit, FACUnit ) self.DisplayCategory = "HQ" -- Targets is the default display category - self.Fsm = STATEMACHINE_PROCESS:New( self, { + self.Fsm = FSM_PROCESS:New( self, { initial = 'Assigned', events = { { name = 'Start', from = 'Assigned', to = 'CreatedMenu' }, @@ -66,7 +66,7 @@ end --- StateMachine callback function for a PROCESS -- @param #PROCESS_JTAC self --- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param Core.Fsm#FSM_PROCESS Fsm -- @param #string Event -- @param #string From -- @param #string To @@ -77,7 +77,7 @@ end --- StateMachine callback function for a PROCESS -- @param #PROCESS_JTAC self --- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param Core.Fsm#FSM_PROCESS Fsm -- @param #string Event -- @param #string From -- @param #string To @@ -123,7 +123,7 @@ end --- StateMachine callback function for a PROCESS -- @param #PROCESS_JTAC self --- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param Core.Fsm#FSM_PROCESS Fsm -- @param #string Event -- @param #string From -- @param #string To @@ -131,7 +131,7 @@ function PROCESS_JTAC:OnJTACMenuAwait( Fsm, Event, From, To ) if self.DisplayCount >= self.DisplayInterval then - local TaskJTAC = self.Task -- Task#TASK_JTAC + local TaskJTAC = self.Task -- Tasking.Task#TASK_JTAC TaskJTAC.Spots = TaskJTAC.Spots or {} for TargetUnitName, SpotData in pairs( TaskJTAC.Spots) do local TargetUnit = UNIT:FindByName( TargetUnitName ) @@ -147,16 +147,16 @@ end --- StateMachine callback function for a PROCESS -- @param #PROCESS_JTAC self --- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param Core.Fsm#FSM_PROCESS Fsm -- @param #string Event -- @param #string From -- @param #string To --- @param Unit#UNIT TargetUnit +-- @param Wrapper.Unit#UNIT TargetUnit function PROCESS_JTAC:OnJTACMenuSpot( Fsm, Event, From, To, TargetUnit ) local TargetUnitName = TargetUnit:GetName() - local TaskJTAC = self.Task -- Task#TASK_JTAC + local TaskJTAC = self.Task -- Tasking.Task#TASK_JTAC TaskJTAC.Spots = TaskJTAC.Spots or {} TaskJTAC.Spots[TargetUnitName] = TaskJTAC.Spots[TargetUnitName] or {} @@ -174,16 +174,16 @@ end --- StateMachine callback function for a PROCESS -- @param #PROCESS_JTAC self --- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param Core.Fsm#FSM_PROCESS Fsm -- @param #string Event -- @param #string From -- @param #string To --- @param Unit#UNIT TargetUnit +-- @param Wrapper.Unit#UNIT TargetUnit function PROCESS_JTAC:OnJTACMenuCancel( Fsm, Event, From, To, TargetUnit ) local TargetUnitName = TargetUnit:GetName() - local TaskJTAC = self.Task -- Task#TASK_JTAC + local TaskJTAC = self.Task -- Tasking.Task#TASK_JTAC TaskJTAC.Spots = TaskJTAC.Spots or {} if TaskJTAC.Spots[TargetUnitName] then diff --git a/Moose Development/Moose/Process_Pickup.lua b/Moose Development/Moose/Actions/Act_Pickup.lua similarity index 86% rename from Moose Development/Moose/Process_Pickup.lua rename to Moose Development/Moose/Actions/Act_Pickup.lua index 968d1235d..ca79ccfdc 100644 --- a/Moose Development/Moose/Process_Pickup.lua +++ b/Moose Development/Moose/Actions/Act_Pickup.lua @@ -2,9 +2,9 @@ --- PROCESS_PICKUP class -- @type PROCESS_PICKUP --- @field Unit#UNIT ProcessUnit --- @field Set#SET_UNIT TargetSetUnit --- @extends Process#PROCESS +-- @field Wrapper.Unit#UNIT ProcessUnit +-- @field Core.Set#SET_UNIT TargetSetUnit +-- @extends Core.Fsm#FSM_PROCESS PROCESS_PICKUP = { ClassName = "PROCESS_PICKUP", Fsm = {}, @@ -14,9 +14,9 @@ PROCESS_PICKUP = { --- Creates a new DESTROY process. -- @param #PROCESS_PICKUP self --- @param Task#TASK Task --- @param Unit#UNIT ProcessUnit --- @param Set#SET_UNIT TargetSetUnit +-- @param Tasking.Task#TASK Task +-- @param Wrapper.Unit#UNIT ProcessUnit +-- @param Core.Set#SET_UNIT TargetSetUnit -- @return #PROCESS_PICKUP self function PROCESS_PICKUP:New( Task, ProcessName, ProcessUnit ) @@ -29,7 +29,7 @@ function PROCESS_PICKUP:New( Task, ProcessName, ProcessUnit ) self.DisplayTime = 10 -- 10 seconds is the default self.DisplayCategory = "HQ" -- Targets is the default display category - self.Fsm = STATEMACHINE_PROCESS:New( self, { + self.Fsm = FSM_PROCESS:New( self, { initial = 'Assigned', events = { { name = 'Start', from = 'Assigned', to = 'Navigating' }, @@ -57,7 +57,7 @@ end --- StateMachine callback function for a PROCESS -- @param #PROCESS_PICKUP self --- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param Core.Fsm#FSM_PROCESS Fsm -- @param #string Event -- @param #string From -- @param #string To @@ -68,7 +68,7 @@ end --- StateMachine callback function for a PROCESS -- @param #PROCESS_PICKUP self --- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param Core.Fsm#FSM_PROCESS Fsm -- @param #string Event -- @param #string From -- @param #string To @@ -89,11 +89,11 @@ end --- StateMachine callback function for a PROCESS -- @param #PROCESS_PICKUP self --- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param Core.Fsm#FSM_PROCESS Fsm -- @param #string Event -- @param #string From -- @param #string To --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event function PROCESS_PICKUP:OnHitTarget( Fsm, Event, From, To, Event ) @@ -115,7 +115,7 @@ end --- StateMachine callback function for a PROCESS -- @param #PROCESS_PICKUP self --- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param Core.Fsm#FSM_PROCESS Fsm -- @param #string Event -- @param #string From -- @param #string To @@ -126,11 +126,11 @@ end --- StateMachine callback function for a PROCESS -- @param #PROCESS_PICKUP self --- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param Core.Fsm#FSM_PROCESS Fsm -- @param #string Event -- @param #string From -- @param #string To --- @param Event#EVENTDATA DCSEvent +-- @param Core.Event#EVENTDATA DCSEvent function PROCESS_PICKUP:OnKilled( Fsm, Event, From, To ) self:NextEvent( Fsm.Restart ) @@ -139,7 +139,7 @@ end --- StateMachine callback function for a PROCESS -- @param #PROCESS_PICKUP self --- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param Core.Fsm#FSM_PROCESS Fsm -- @param #string Event -- @param #string From -- @param #string To @@ -151,7 +151,7 @@ end --- StateMachine callback function for a PROCESS -- @param #PROCESS_PICKUP self --- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param Core.Fsm#FSM_PROCESS Fsm -- @param #string Event -- @param #string From -- @param #string To @@ -162,7 +162,7 @@ end --- DCS Events --- @param #PROCESS_PICKUP self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event function PROCESS_PICKUP:EventDead( Event ) if Event.IniDCSUnit then diff --git a/Moose Development/Moose/Actions/Act_Route.lua b/Moose Development/Moose/Actions/Act_Route.lua new file mode 100644 index 000000000..d2598b6c2 --- /dev/null +++ b/Moose Development/Moose/Actions/Act_Route.lua @@ -0,0 +1,249 @@ +--- (SP) (MP) (FSM) Route AI or players through waypoints or to zones. +-- +-- === +-- +-- # @{#ACT_ROUTE} FSM class, extends @{Core.Fsm#FSM_PROCESS} +-- +-- ## ACT_ROUTE state machine: +-- +-- This class is a state machine: it manages a process that is triggered by events causing state transitions to occur. +-- All derived classes from this class will start with the class name, followed by a \_. See the relevant derived class descriptions below. +-- Each derived class follows exactly the same process, using the same events and following the same state transitions, +-- but will have **different implementation behaviour** upon each event or state transition. +-- +-- ### ACT_ROUTE **Events**: +-- +-- These are the events defined in this class: +-- +-- * **Start**: The process is started. The process will go into the Report state. +-- * **Report**: The process is reporting to the player the route to be followed. +-- * **Route**: The process is routing the controllable. +-- * **Pause**: The process is pausing the route of the controllable. +-- * **Arrive**: The controllable has arrived at a route point. +-- * **More**: There are more route points that need to be followed. The process will go back into the Report state. +-- * **NoMore**: There are no more route points that need to be followed. The process will go into the Success state. +-- +-- ### ACT_ROUTE **Event methods**: +-- +-- Event methods are available (dynamically allocated by the state machine), that accomodate for state transitions occurring in the process. +-- There are two types of event methods, which you can use to influence the normal mechanisms in the state machine: +-- +-- * **Immediate**: The event method has exactly the name of the event. +-- * **Delayed**: The event method starts with a __ + the name of the event. The first parameter of the event method is a number value, expressing the delay in seconds when the event will be executed. +-- +-- ### ACT_ROUTE **States**: +-- +-- * **None**: The controllable did not receive route commands. +-- * **Arrived (*)**: The controllable has arrived at a route point. +-- * **Aborted (*)**: The controllable has aborted the route path. +-- * **Routing**: The controllable is understay to the route point. +-- * **Pausing**: The process is pausing the routing. AI air will go into hover, AI ground will stop moving. Players can fly around. +-- * **Success (*)**: All route points were reached. +-- * **Failed (*)**: The process has failed. +-- +-- (*) End states of the process. +-- +-- ### ACT_ROUTE state transition methods: +-- +-- State transition functions can be set **by the mission designer** customizing or improving the behaviour of the state. +-- There are 2 moments when state transition methods will be called by the state machine: +-- +-- * **Before** the state transition. +-- The state transition method needs to start with the name **OnBefore + the name of the state**. +-- If the state transition method returns false, then the processing of the state transition will not be done! +-- If you want to change the behaviour of the AIControllable at this event, return false, +-- but then you'll need to specify your own logic using the AIControllable! +-- +-- * **After** the state transition. +-- The state transition method needs to start with the name **OnAfter + the name of the state**. +-- These state transition methods need to provide a return value, which is specified at the function description. +-- +-- === +-- +-- # 1) @{#ACT_ROUTE_ZONE} class, extends @{Fsm.Route#ACT_ROUTE} +-- +-- The ACT_ROUTE_ZONE class implements the core functions to route an AIR @{Controllable} player @{Unit} to a @{Zone}. +-- The player receives on perioding times messages with the coordinates of the route to follow. +-- Upon arrival at the zone, a confirmation of arrival is sent, and the process will be ended. +-- +-- # 1.1) ACT_ROUTE_ZONE constructor: +-- +-- * @{#ACT_ROUTE_ZONE.New}(): Creates a new ACT_ROUTE_ZONE object. +-- +-- === +-- +-- @module Route + + +do -- ACT_ROUTE + + --- ACT_ROUTE class + -- @type ACT_ROUTE + -- @field Tasking.Task#TASK TASK + -- @field Wrapper.Unit#UNIT ProcessUnit + -- @field Core.Zone#ZONE_BASE TargetZone + -- @extends Core.Fsm#FSM_PROCESS + ACT_ROUTE = { + ClassName = "ACT_ROUTE", + } + + + --- Creates a new routing state machine. The process will route a CLIENT to a ZONE until the CLIENT is within that ZONE. + -- @param #ACT_ROUTE self + -- @return #ACT_ROUTE self + function ACT_ROUTE:New() + + -- Inherits from BASE + local self = BASE:Inherit( self, FSM_PROCESS:New( "ACT_ROUTE" ) ) -- Core.Fsm#FSM_PROCESS + + self:AddTransition( "None", "Start", "Routing" ) + self:AddTransition( "*", "Report", "Reporting" ) + self:AddTransition( "*", "Route", "Routing" ) + self:AddTransition( "Routing", "Pause", "Pausing" ) + self:AddTransition( "*", "Abort", "Aborted" ) + self:AddTransition( "Routing", "Arrive", "Arrived" ) + self:AddTransition( "Arrived", "Success", "Success" ) + self:AddTransition( "*", "Fail", "Failed" ) + self:AddTransition( "", "", "" ) + self:AddTransition( "", "", "" ) + + self:AddEndState( "Arrived" ) + self:AddEndState( "Failed" ) + + self:SetStartState( "None" ) + + return self + end + + --- Task Events + + --- StateMachine callback function + -- @param #ACT_ROUTE self + -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit + -- @param #string Event + -- @param #string From + -- @param #string To + function ACT_ROUTE:onafterStart( ProcessUnit, Event, From, To ) + + + self:__Route( 1 ) + end + + --- Check if the controllable has arrived. + -- @param #ACT_ROUTE self + -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit + -- @return #boolean + function ACT_ROUTE:onfuncHasArrived( ProcessUnit ) + return false + end + + --- StateMachine callback function + -- @param #ACT_ROUTE self + -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit + -- @param #string Event + -- @param #string From + -- @param #string To + function ACT_ROUTE:onbeforeRoute( ProcessUnit, Event, From, To ) + + if ProcessUnit:IsAlive() then + local HasArrived = self:onfuncHasArrived( ProcessUnit ) -- Polymorphic + if self.DisplayCount >= self.DisplayInterval then + self:T( { HasArrived = HasArrived } ) + if not HasArrived then + self:__Report( 1 ) + end + self.DisplayCount = 1 + else + self.DisplayCount = self.DisplayCount + 1 + end + + self:T( { DisplayCount = self.DisplayCount } ) + + if HasArrived then + self:__Arrive( 1 ) + else + self:__Route( 1 ) + end + + return HasArrived -- if false, then the event will not be executed... + end + + return false + + end + +end -- ACT_ROUTE + + + +do -- ACT_ROUTE_ZONE + + --- ACT_ROUTE_ZONE class + -- @type ACT_ROUTE_ZONE + -- @field Tasking.Task#TASK TASK + -- @field Wrapper.Unit#UNIT ProcessUnit + -- @field Core.Zone#ZONE_BASE TargetZone + -- @extends #ACT_ROUTE + ACT_ROUTE_ZONE = { + ClassName = "ACT_ROUTE_ZONE", + } + + + --- Creates a new routing state machine. The task will route a controllable to a ZONE until the controllable is within that ZONE. + -- @param #ACT_ROUTE_ZONE self + -- @param Core.Zone#ZONE_BASE TargetZone + function ACT_ROUTE_ZONE:New( TargetZone ) + local self = BASE:Inherit( self, ACT_ROUTE:New() ) -- #ACT_ROUTE_ZONE + + self.TargetZone = TargetZone + + self.DisplayInterval = 30 + self.DisplayCount = 30 + self.DisplayMessage = true + self.DisplayTime = 10 -- 10 seconds is the default + + return self + end + + function ACT_ROUTE_ZONE:Init( FsmRoute ) + + self.TargetZone = FsmRoute.TargetZone + self.DisplayInterval = 30 + self.DisplayCount = 30 + self.DisplayMessage = true + self.DisplayTime = 10 -- 10 seconds is the default + end + + --- Method override to check if the controllable has arrived. + -- @param #ACT_ROUTE self + -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit + -- @return #boolean + function ACT_ROUTE_ZONE:onfuncHasArrived( ProcessUnit ) + + if ProcessUnit:IsInZone( self.TargetZone ) then + local RouteText = "You have arrived within the zone." + self:Message( RouteText ) + end + + return ProcessUnit:IsInZone( self.TargetZone ) + end + + --- Task Events + + --- StateMachine callback function + -- @param #ACT_ROUTE_ZONE self + -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit + -- @param #string Event + -- @param #string From + -- @param #string To + function ACT_ROUTE_ZONE:onenterReporting( ProcessUnit, Event, From, To ) + + local ZoneVec2 = self.TargetZone:GetVec2() + local ZonePointVec2 = POINT_VEC2:New( ZoneVec2.x, ZoneVec2.y ) + local TaskUnitVec2 = ProcessUnit:GetVec2() + local TaskUnitPointVec2 = POINT_VEC2:New( TaskUnitVec2.x, TaskUnitVec2.y ) + local RouteText = "Route to " .. TaskUnitPointVec2:GetBRText( ZonePointVec2 ) .. " km to target." + self:Message( RouteText ) + end + +end -- ACT_ROUTE_ZONE diff --git a/Moose Development/Moose/Cargo.lua b/Moose Development/Moose/Cargo.lua deleted file mode 100644 index 8d500d001..000000000 --- a/Moose Development/Moose/Cargo.lua +++ /dev/null @@ -1,1284 +0,0 @@ ---- This module contains the CARGO classes. --- --- === --- --- 1) @{Cargo#CARGO_BASE} class, extends @{Base#BASE} --- ================================================== --- The @{#CARGO_BASE} class defines the core functions that defines a cargo object within MOOSE. --- A cargo is a logical object defined within a @{Mission}, that is available for transport, and has a life status within a simulation. --- --- Cargo can be of various forms: --- --- * CARGO_UNIT, represented by a @{Unit} in a @{Group}: Cargo can be represented by a Unit in a Group. Destruction of the Unit will mean that the cargo is lost. --- * CARGO_STATIC, represented by a @{Static}: Cargo can be represented by a Static. Destruction of the Static will mean that the cargo is lost. --- * CARGO_PACKAGE, contained in a @{Unit} of a @{Group}: Cargo can be contained within a Unit of a Group. The cargo can be **delivered** by the @{Unit}. If the Unit is destroyed, the cargo will be destroyed also. --- * CARGO_PACKAGE, Contained in a @{Static}: Cargo can be contained within a Static. The cargo can be **collected** from the @Static. If the @{Static} is destroyed, the cargo will be destroyed. --- * CARGO_SLINGLOAD, represented by a @{Cargo} that is transportable: Cargo can be represented by a Cargo object that is transportable. Destruction of the Cargo will mean that the cargo is lost. --- --- @module Cargo - - - -CARGOS = {} - -do -- CARGO - - --- @type CARGO - -- @extends Base#BASE - -- @field #string Type A string defining the type of the cargo. eg. Engineers, Equipment, Screwdrivers. - -- @field #string Name A string defining the name of the cargo. The name is the unique identifier of the cargo. - -- @field #number Weight A number defining the weight of the cargo. The weight is expressed in kg. - -- @field #number ReportRadius (optional) A number defining the radius in meters when the cargo is signalling or reporting to a Carrier. - -- @field #number NearRadius (optional) A number defining the radius in meters when the cargo is near to a Carrier, so that it can be loaded. - -- @field Controllable#CONTROLLABLE CargoObject The alive DCS object representing the cargo. This value can be nil, meaning, that the cargo is not represented anywhere... - -- @field Positionable#POSITIONABLE CargoCarrier The alive DCS object carrying the cargo. This value can be nil, meaning, that the cargo is not contained anywhere... - -- @field #boolean Slingloadable This flag defines if the cargo can be slingloaded. - -- @field #boolean Moveable This flag defines if the cargo is moveable. - -- @field #boolean Representable This flag defines if the cargo can be represented by a DCS Unit. - -- @field #boolean Containable This flag defines if the cargo can be contained within a DCS Unit. - CARGO = { - ClassName = "CARGO", - Type = nil, - Name = nil, - Weight = nil, - CargoObject = nil, - CargoCarrier = nil, - Representable = false, - Slingloadable = false, - Moveable = false, - Containable = false, - } - ---- @type CARGO.CargoObjects --- @map < #string, Positionable#POSITIONABLE > The alive POSITIONABLE objects representing the the cargo. - - ---- CARGO Constructor. --- @param #CARGO self --- @param Mission#MISSION Mission --- @param #string Type --- @param #string Name --- @param #number Weight --- @param #number ReportRadius (optional) --- @param #number NearRadius (optional) --- @return #CARGO -function CARGO:New( Mission, Type, Name, Weight, ReportRadius, NearRadius ) - local self = BASE:Inherit( self, BASE:New() ) -- #CARGO - self:F( { Type, Name, Weight, ReportRadius, NearRadius } ) - - - self.Type = Type - self.Name = Name - self.Weight = Weight - self.ReportRadius = ReportRadius - self.NearRadius = NearRadius - self.CargoObject = nil - self.CargoCarrier = nil - self.Representable = false - self.Slingloadable = false - self.Moveable = false - self.Containable = false - - - self.CargoScheduler = SCHEDULER:New() - - CARGOS[self.Name] = self - - return self -end - - ---- Template method to spawn a new representation of the CARGO in the simulator. --- @param #CARGO self --- @return #CARGO -function CARGO:Spawn( PointVec2 ) - self:F() - -end - ---- Load Cargo to a Carrier. --- @param #CARGO self --- @param Unit#UNIT CargoCarrier -function CARGO:Load( CargoCarrier ) - self:F() - - self:_NextEvent( self.FsmP.Load, CargoCarrier ) -end - ---- UnLoad Cargo from a Carrier with a UnLoadDistance and an Angle. --- @param #CARGO self --- @param #number UnLoadDistance --- @param #number Angle -function CARGO:UnLoad( CargoCarrier ) - self:F() - - self:_NextEvent( self.FsmP.Board, CargoCarrier ) -end - ---- Board Cargo to a Carrier with a defined Speed. --- @param #CARGO self --- @param Unit#UNIT CargoCarrier -function CARGO:Board( CargoCarrier ) - self:F() - - self:_NextEvent( self.FsmP.Board, CargoCarrier ) -end - ---- UnLoad Cargo from a Carrier. --- @param #CARGO self -function CARGO:UnLoad() - self:F() - - self:_NextEvent( self.FsmP.UnLoad ) -end - ---- Check if CargoCarrier is near the Cargo to be Loaded. --- @param #CARGO self --- @param Point#POINT_VEC2 PointVec2 --- @return #boolean -function CARGO:IsNear( PointVec2 ) - self:F() - - local Distance = PointVec2:DistanceFromPointVec2( self.CargoObject:GetPointVec2() ) - self:T( Distance ) - - if Distance <= self.NearRadius then - return true - else - return false - end -end - - ---- On Loaded callback function. -function CARGO:OnLoaded( CallBackFunction, ... ) - self:F() - - self.OnLoadedCallBack = CallBackFunction - self.OnLoadedParameters = arg - -end - ---- On UnLoaded callback function. -function CARGO:OnUnLoaded( CallBackFunction, ... ) - self:F() - - self.OnUnLoadedCallBack = CallBackFunction - self.OnUnLoadedParameters = arg -end - ---- @param #CARGO self -function CARGO:_NextEvent( NextEvent, ... ) - self:F( self.Name ) - SCHEDULER:New( self.FsmP, NextEvent, arg, 1 ) -- This schedules the next event, but only if scheduling is activated. -end - ---- @param #CARGO self -function CARGO:_Next( NextEvent, ... ) - self:F( self.Name ) - self.FsmP.NextEvent( self, unpack(arg) ) -- This calls the next event... -end - -end - -do -- CARGO_REPRESENTABLE - - --- @type CARGO_REPRESENTABLE - -- @extends #CARGO - CARGO_REPRESENTABLE = { - ClassName = "CARGO_REPRESENTABLE" - } - ---- CARGO_REPRESENTABLE Constructor. --- @param #CARGO_REPRESENTABLE self --- @param Mission#MISSION Mission --- @param Controllable#Controllable CargoObject --- @param #string Type --- @param #string Name --- @param #number Weight --- @param #number ReportRadius (optional) --- @param #number NearRadius (optional) --- @return #CARGO_REPRESENTABLE -function CARGO_REPRESENTABLE:New( Mission, CargoObject, Type, Name, Weight, ReportRadius, NearRadius ) - local self = BASE:Inherit( self, CARGO:New( Mission, Type, Name, Weight, ReportRadius, NearRadius ) ) -- #CARGO - self:F( { Type, Name, Weight, ReportRadius, NearRadius } ) - - - - - return self -end - - - -end - -do -- CARGO_UNIT - - --- @type CARGO_UNIT - -- @extends #CARGO_REPRESENTABLE - CARGO_UNIT = { - ClassName = "CARGO_UNIT" - } - ---- CARGO_UNIT Constructor. --- @param #CARGO_UNIT self --- @param Mission#MISSION Mission --- @param Unit#UNIT CargoUnit --- @param #string Type --- @param #string Name --- @param #number Weight --- @param #number ReportRadius (optional) --- @param #number NearRadius (optional) --- @return #CARGO_UNIT -function CARGO_UNIT:New( Mission, CargoUnit, Type, Name, Weight, ReportRadius, NearRadius ) - local self = BASE:Inherit( self, CARGO_REPRESENTABLE:New( Mission, CargoUnit, Type, Name, Weight, ReportRadius, NearRadius ) ) -- #CARGO - self:F( { Type, Name, Weight, ReportRadius, NearRadius } ) - - self:T( CargoUnit ) - self.CargoObject = CargoUnit - - self.FsmP = STATEMACHINE_PROCESS:New( self, { - initial = 'UnLoaded', - events = { - { name = 'Board', from = 'UnLoaded', to = 'Boarding' }, - { name = 'Load', from = 'Boarding', to = 'Loaded' }, - { name = 'UnLoad', from = 'Loaded', to = 'UnBoarding' }, - { name = 'UnBoard', from = 'UnBoarding', to = 'UnLoaded' }, - { name = 'Load', from = 'UnLoaded', to = 'Loaded' }, - }, - callbacks = { - onafterBoard = self.EventBoard, - onafterLoad = self.EventLoad, - onafterUnBoard = self.EventUnBoard, - onafterUnLoad = self.EventUnLoad, - onenterBoarding = self.EnterStateBoarding, - onleaveBoarding = self.LeaveStateBoarding, - onenterLoaded = self.EnterStateLoaded, - onenterUnBoarding = self.EnterStateUnBoarding, - onleaveUnBoarding = self.LeaveStateUnBoarding, - onenterUnLoaded = self.EnterStateUnLoaded, - }, - } ) - - self:T( self.ClassName ) - - return self -end - ---- Enter UnBoarding State. --- @param #CARGO_UNIT self --- @param StateMachine#STATEMACHINE_PROCESS FsmP --- @param #string Event --- @param #string From --- @param #string To --- @param Point#POINT_VEC2 ToPointVec2 -function CARGO_UNIT:EnterStateUnBoarding( FsmP, Event, From, To, ToPointVec2 ) - self:F() - - local Angle = 180 - local Speed = 10 - local DeployDistance = 5 - local RouteDistance = 60 - - if From == "Loaded" then - - local CargoCarrierPointVec2 = self.CargoCarrier:GetPointVec2() - local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - local CargoDeployPointVec2 = CargoCarrierPointVec2:Translate( DeployDistance, CargoDeployHeading ) - local CargoRoutePointVec2 = CargoCarrierPointVec2:Translate( RouteDistance, CargoDeployHeading ) - - if not ToPointVec2 then - ToPointVec2 = CargoRoutePointVec2 - end - - local FromPointVec2 = CargoCarrierPointVec2 - - -- Respawn the group... - if self.CargoObject then - self.CargoObject:ReSpawn( CargoDeployPointVec2:GetVec3(), CargoDeployHeading ) - self.CargoCarrier = nil - - local Points = {} - Points[#Points+1] = FromPointVec2:RoutePointGround( Speed ) - Points[#Points+1] = ToPointVec2:RoutePointGround( Speed ) - - local TaskRoute = self.CargoObject:TaskRoute( Points ) - self.CargoObject:SetTask( TaskRoute, 1 ) - - self:_NextEvent( FsmP.UnBoard, ToPointVec2 ) - end - end - -end - ---- Leave UnBoarding State. --- @param #CARGO_UNIT self --- @param StateMachine#STATEMACHINE_PROCESS FsmP --- @param #string Event --- @param #string From --- @param #string To --- @param Point#POINT_VEC2 ToPointVec2 -function CARGO_UNIT:LeaveStateUnBoarding( FsmP, Event, From, To, ToPointVec2 ) - self:F() - - local Angle = 180 - local Speed = 10 - local Distance = 5 - - if From == "UnBoarding" then - if self:IsNear( ToPointVec2 ) then - return true - else - self:_NextEvent( FsmP.UnBoard, ToPointVec2 ) - end - return false - end - -end - ---- Enter UnLoaded State. --- @param #CARGO_UNIT self --- @param StateMachine#STATEMACHINE_PROCESS FsmP --- @param #string Event --- @param #string From --- @param #string To -function CARGO_UNIT:EnterStateUnLoaded( FsmP, Event, From, To, ToPointVec2 ) - self:F() - - local Angle = 180 - local Speed = 10 - local Distance = 5 - - if From == "Loaded" then - local StartPointVec2 = self.CargoCarrier:GetPointVec2() - local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - local CargoDeployPointVec2 = StartPointVec2:Translate( Distance, CargoDeployHeading ) - - -- Respawn the group... - if self.CargoObject then - self.CargoObject:ReSpawn( ToPointVec2:GetVec3(), 0 ) - self.CargoCarrier = nil - end - - end - - if self.OnUnLoadedCallBack then - self.OnUnLoadedCallBack( self, unpack( self.OnUnLoadedParameters ) ) - self.OnUnLoadedCallBack = nil - end - -end - - - ---- Enter Boarding State. --- @param #CARGO_UNIT self --- @param StateMachine#STATEMACHINE_PROCESS FsmP --- @param #string Event --- @param #string From --- @param #string To --- @param Unit#UNIT CargoCarrier -function CARGO_UNIT:EnterStateBoarding( FsmP, Event, From, To, CargoCarrier ) - self:F() - - local Speed = 10 - local Angle = 180 - local Distance = 5 - - if From == "UnLoaded" then - local CargoCarrierPointVec2 = CargoCarrier:GetPointVec2() - local CargoCarrierHeading = CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - local CargoDeployPointVec2 = CargoCarrierPointVec2:Translate( Distance, CargoDeployHeading ) - - local Points = {} - - local PointStartVec2 = self.CargoObject:GetPointVec2() - - Points[#Points+1] = PointStartVec2:RoutePointGround( Speed ) - Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) - - local TaskRoute = self.CargoObject:TaskRoute( Points ) - self.CargoObject:SetTask( TaskRoute, 2 ) - end -end - ---- Leave Boarding State. --- @param #CARGO_UNIT self --- @param StateMachine#STATEMACHINE_PROCESS FsmP --- @param #string Event --- @param #string From --- @param #string To --- @param Unit#UNIT CargoCarrier -function CARGO_UNIT:LeaveStateBoarding( FsmP, Event, From, To, CargoCarrier ) - self:F() - - if self:IsNear( CargoCarrier:GetPointVec2() ) then - return true - else - self:_NextEvent( FsmP.Load, CargoCarrier ) - end - return false -end - ---- Loaded State. --- @param #CARGO_UNIT self --- @param StateMachine#STATEMACHINE_PROCESS FsmP --- @param #string Event --- @param #string From --- @param #string To --- @param Unit#UNIT CargoCarrier -function CARGO_UNIT:EnterStateLoaded( FsmP, Event, From, To, CargoCarrier ) - self:F() - - self.CargoCarrier = CargoCarrier - - -- Only destroy the CargoObject is if there is a CargoObject (packages don't have CargoObjects). - if self.CargoObject then - self.CargoObject:Destroy() - end - - if self.OnLoadedCallBack then - self.OnLoadedCallBack( self, unpack( self.OnLoadedParameters ) ) - self.OnLoadedCallBack = nil - end - -end - - ---- Board Event. --- @param #CARGO_UNIT self --- @param StateMachine#STATEMACHINE_PROCESS FsmP --- @param #string Event --- @param #string From --- @param #string To -function CARGO_UNIT:EventBoard( FsmP, Event, From, To, CargoCarrier ) - self:F() - - self.CargoInAir = self.CargoObject:InAir() - - self:T( self.CargoInAir ) - - -- Only move the group to the carrier when the cargo is not in the air - -- (eg. cargo can be on a oil derrick, moving the cargo on the oil derrick will drop the cargo on the sea). - if not self.CargoInAir then - self:_NextEvent( FsmP.Load, CargoCarrier ) - end - - -end - ---- UnBoard Event. --- @param #CARGO_UNIT self --- @param StateMachine#STATEMACHINE_PROCESS FsmP --- @param #string Event --- @param #string From --- @param #string To -function CARGO_UNIT:EventUnBoard( FsmP, Event, From, To ) - self:F() - - self.CargoInAir = self.CargoObject:InAir() - - self:T( self.CargoInAir ) - - -- Only unboard the cargo when the carrier is not in the air. - -- (eg. cargo can be on a oil derrick, moving the cargo on the oil derrick will drop the cargo on the sea). - if not self.CargoInAir then - - end - - self:_NextEvent( FsmP.UnLoad ) - -end - ---- Load Event. --- @param #CARGO_UNIT self --- @param StateMachine#STATEMACHINE_PROCESS FsmP --- @param #string Event --- @param #string From --- @param #string To --- @param Unit#UNIT CargoCarrier -function CARGO_UNIT:EventLoad( FsmP, Event, From, To, CargoCarrier ) - self:F() - - self:T( self.ClassName ) - -end - ---- UnLoad Event. --- @param #CARGO_UNIT self --- @param StateMachine#STATEMACHINE_PROCESS FsmP --- @param #string Event --- @param #string From --- @param #string To -function CARGO_UNIT:EventUnLoad( FsmP, Event, From, To ) - self:F() - -end - -end - -do -- CARGO_PACKAGE - - --- @type CARGO_PACKAGE - -- @extends #CARGO_REPRESENTABLE - CARGO_PACKAGE = { - ClassName = "CARGO_PACKAGE" - } - ---- CARGO_PACKAGE Constructor. --- @param #CARGO_PACKAGE self --- @param Mission#MISSION Mission --- @param Unit#UNIT CargoCarrier The UNIT carrying the package. --- @param #string Type --- @param #string Name --- @param #number Weight --- @param #number ReportRadius (optional) --- @param #number NearRadius (optional) --- @return #CARGO_PACKAGE -function CARGO_PACKAGE:New( Mission, CargoCarrier, Type, Name, Weight, ReportRadius, NearRadius ) - local self = BASE:Inherit( self, CARGO_REPRESENTABLE:New( Mission, CargoCarrier, Type, Name, Weight, ReportRadius, NearRadius ) ) -- #CARGO - self:F( { Type, Name, Weight, ReportRadius, NearRadius } ) - - self:T( CargoCarrier ) - self.CargoCarrier = CargoCarrier - - self.FsmP = STATEMACHINE_PROCESS:New( self, { - initial = 'UnLoaded', - events = { - { name = 'Board', from = 'UnLoaded', to = 'Boarding' }, - { name = 'Boarded', from = 'Boarding', to = 'Boarding' }, - { name = 'Load', from = 'Boarding', to = 'Loaded' }, - { name = 'Load', from = 'UnLoaded', to = 'Loaded' }, - { name = 'UnBoard', from = 'Loaded', to = 'UnBoarding' }, - { name = 'UnBoarded', from = 'UnBoarding', to = 'UnBoarding' }, - { name = 'UnLoad', from = 'UnBoarding', to = 'UnLoaded' }, - { name = 'UnLoad', from = 'Loaded', to = 'UnLoaded' }, - }, - callbacks = { - onBoard = self.OnBoard, - onBoarded = self.OnBoarded, - onLoad = self.OnLoad, - onUnBoard = self.OnUnBoard, - onUnBoarded = self.OnUnBoarded, - onUnLoad = self.OnUnLoad, - onLoaded = self.OnLoaded, - onUnLoaded = self.OnUnLoaded, - }, - } ) - - return self -end - ---- Board Event. --- @param #CARGO_PACKAGE self --- @param StateMachine#STATEMACHINE_PROCESS FsmP --- @param #string Event --- @param #string From --- @param #string To --- @param Unit#UNIT CargoCarrier --- @param #number Speed --- @param #number BoardDistance --- @param #number Angle -function CARGO_PACKAGE:OnBoard( FsmP, Event, From, To, CargoCarrier, Speed, BoardDistance, LoadDistance, Angle ) - self:F() - - self.CargoInAir = self.CargoCarrier:InAir() - - self:T( self.CargoInAir ) - - -- Only move the CargoCarrier to the New CargoCarrier when the New CargoCarrier is not in the air. - if not self.CargoInAir then - - local Points = {} - - local StartPointVec2 = self.CargoCarrier:GetPointVec2() - local CargoCarrierHeading = CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - self:T( { CargoCarrierHeading, CargoDeployHeading } ) - local CargoDeployPointVec2 = CargoCarrier:GetPointVec2():Translate( BoardDistance, CargoDeployHeading ) - - Points[#Points+1] = StartPointVec2:RoutePointGround( Speed ) - Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) - - local TaskRoute = self.CargoCarrier:TaskRoute( Points ) - self.CargoCarrier:SetTask( TaskRoute, 1 ) - end - - self:_NextEvent( FsmP.Boarded, CargoCarrier, Speed, BoardDistance, LoadDistance, Angle ) - -end - ---- Check if CargoCarrier is near the Cargo to be Loaded. --- @param #CARGO_PACKAGE self --- @param Unit#UNIT CargoCarrier --- @return #boolean -function CARGO_PACKAGE:IsNear( CargoCarrier ) - self:F() - - local CargoCarrierPoint = CargoCarrier:GetPointVec2() - - local Distance = CargoCarrierPoint:DistanceFromPointVec2( self.CargoCarrier:GetPointVec2() ) - self:T( Distance ) - - if Distance <= self.NearRadius then - return true - else - return false - end -end - ---- Boarded Event. --- @param #CARGO_PACKAGE self --- @param StateMachine#STATEMACHINE_PROCESS FsmP --- @param #string Event --- @param #string From --- @param #string To --- @param Unit#UNIT CargoCarrier -function CARGO_PACKAGE:OnBoarded( FsmP, Event, From, To, CargoCarrier, Speed, BoardDistance, LoadDistance, Angle ) - self:F() - - if self:IsNear( CargoCarrier ) then - self:_NextEvent( FsmP.Load, CargoCarrier, Speed, LoadDistance, Angle ) - else - self:_NextEvent( FsmP.Boarded, CargoCarrier, Speed, BoardDistance, LoadDistance, Angle ) - end -end - ---- UnBoard Event. --- @param #CARGO_PACKAGE self --- @param StateMachine#STATEMACHINE_PROCESS FsmP --- @param #string Event --- @param #string From --- @param #string To --- @param #number Speed --- @param #number UnLoadDistance --- @param #number UnBoardDistance --- @param #number Radius --- @param #number Angle -function CARGO_PACKAGE:OnUnBoard( FsmP, Event, From, To, CargoCarrier, Speed, UnLoadDistance, UnBoardDistance, Radius, Angle ) - self:F() - - self.CargoInAir = self.CargoCarrier:InAir() - - self:T( self.CargoInAir ) - - -- Only unboard the cargo when the carrier is not in the air. - -- (eg. cargo can be on a oil derrick, moving the cargo on the oil derrick will drop the cargo on the sea). - if not self.CargoInAir then - - self:_Next( self.FsmP.UnLoad, UnLoadDistance, Angle ) - - local Points = {} - - local StartPointVec2 = CargoCarrier:GetPointVec2() - local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - self:T( { CargoCarrierHeading, CargoDeployHeading } ) - local CargoDeployPointVec2 = StartPointVec2:Translate( UnBoardDistance, CargoDeployHeading ) - - Points[#Points+1] = StartPointVec2:RoutePointGround( Speed ) - Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) - - local TaskRoute = CargoCarrier:TaskRoute( Points ) - CargoCarrier:SetTask( TaskRoute, 1 ) - end - - self:_NextEvent( FsmP.UnBoarded, CargoCarrier, Speed ) - -end - ---- UnBoarded Event. --- @param #CARGO_PACKAGE self --- @param StateMachine#STATEMACHINE_PROCESS FsmP --- @param #string Event --- @param #string From --- @param #string To --- @param Unit#UNIT CargoCarrier -function CARGO_PACKAGE:OnUnBoarded( FsmP, Event, From, To, CargoCarrier, Speed ) - self:F() - - if self:IsNear( CargoCarrier ) then - self:_NextEvent( FsmP.UnLoad, CargoCarrier, Speed ) - else - self:_NextEvent( FsmP.UnBoarded, CargoCarrier, Speed ) - end -end - ---- Load Event. --- @param #CARGO_PACKAGE self --- @param StateMachine#STATEMACHINE_PROCESS FsmP --- @param #string Event --- @param #string From --- @param #string To --- @param Unit#UNIT CargoCarrier --- @param #number Speed --- @param #number LoadDistance --- @param #number Angle -function CARGO_PACKAGE:OnLoad( FsmP, Event, From, To, CargoCarrier, Speed, LoadDistance, Angle ) - self:F() - - self.CargoCarrier = CargoCarrier - - local StartPointVec2 = self.CargoCarrier:GetPointVec2() - local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - local CargoDeployPointVec2 = StartPointVec2:Translate( LoadDistance, CargoDeployHeading ) - - local Points = {} - Points[#Points+1] = StartPointVec2:RoutePointGround( Speed ) - Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) - - local TaskRoute = self.CargoCarrier:TaskRoute( Points ) - self.CargoCarrier:SetTask( TaskRoute, 1 ) - -end - ---- UnLoad Event. --- @param #CARGO_PACKAGE self --- @param StateMachine#STATEMACHINE_PROCESS FsmP --- @param #string Event --- @param #string From --- @param #string To --- @param #number Distance --- @param #number Angle -function CARGO_PACKAGE:OnUnLoad( FsmP, Event, From, To, CargoCarrier, Speed, Distance, Angle ) - self:F() - - local StartPointVec2 = self.CargoCarrier:GetPointVec2() - local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - local CargoDeployPointVec2 = StartPointVec2:Translate( Distance, CargoDeployHeading ) - - self.CargoCarrier = CargoCarrier - - local Points = {} - Points[#Points+1] = StartPointVec2:RoutePointGround( Speed ) - Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) - - local TaskRoute = self.CargoCarrier:TaskRoute( Points ) - self.CargoCarrier:SetTask( TaskRoute, 1 ) - -end - - -end - - - -CARGO_SLINGLOAD = { - ClassName = "CARGO_SLINGLOAD" -} - - -function CARGO_SLINGLOAD:New( CargoType, CargoName, CargoWeight, CargoZone, CargoHostName, CargoCountryID ) - local self = BASE:Inherit( self, CARGO:New( CargoType, CargoName, CargoWeight ) ) - self:F( { CargoType, CargoName, CargoWeight, CargoZone, CargoHostName, CargoCountryID } ) - - self.CargoHostName = CargoHostName - - -- Cargo will be initialized around the CargoZone position. - self.CargoZone = CargoZone - - self.CargoCount = 0 - self.CargoStaticName = string.format( "%s#%03d", self.CargoName, self.CargoCount ) - - -- The country ID needs to be correctly set. - self.CargoCountryID = CargoCountryID - - CARGOS[self.CargoName] = self - - return self - -end - - -function CARGO_SLINGLOAD:IsLandingRequired() - self:F() - return false -end - - -function CARGO_SLINGLOAD:IsSlingLoad() - self:F() - return true -end - - -function CARGO_SLINGLOAD:Spawn( Client ) - self:F( { self, Client } ) - - local Zone = trigger.misc.getZone( self.CargoZone ) - - local ZonePos = {} - ZonePos.x = Zone.point.x + math.random( Zone.radius / 2 * -1, Zone.radius / 2 ) - ZonePos.y = Zone.point.z + math.random( Zone.radius / 2 * -1, Zone.radius / 2 ) - - self:T( "Cargo Location = " .. ZonePos.x .. ", " .. ZonePos.y ) - - --[[ - - - - - - - - -- This does not work in 1.5.2. - - - - - - - - CargoStatic = StaticObject.getByName( self.CargoName ) - - - - - - - - if CargoStatic then - - - - - - - - CargoStatic:destroy() - - - - - - - - end - - - - - - - - --]] - - CargoStatic = StaticObject.getByName( self.CargoStaticName ) - - if CargoStatic and CargoStatic:isExist() then - CargoStatic:destroy() - end - - -- I need to make every time a new cargo due to bugs in 1.5.2. - - self.CargoCount = self.CargoCount + 1 - self.CargoStaticName = string.format( "%s#%03d", self.CargoName, self.CargoCount ) - - local CargoTemplate = { - ["category"] = "Cargo", - ["shape_name"] = "ab-212_cargo", - ["type"] = "Cargo1", - ["x"] = ZonePos.x, - ["y"] = ZonePos.y, - ["mass"] = self.CargoWeight, - ["name"] = self.CargoStaticName, - ["canCargo"] = true, - ["heading"] = 0, - } - - coalition.addStaticObject( self.CargoCountryID, CargoTemplate ) - - -- end - - return self -end - - -function CARGO_SLINGLOAD:IsNear( Client, LandingZone ) - self:F() - - local Near = false - - return Near -end - - -function CARGO_SLINGLOAD:IsInLandingZone( Client, LandingZone ) - self:F() - - local Near = false - - local CargoStaticUnit = StaticObject.getByName( self.CargoName ) - if CargoStaticUnit then - if routines.IsStaticInZones( CargoStaticUnit, LandingZone ) then - Near = true - end - end - - return Near -end - - -function CARGO_SLINGLOAD:OnBoard( Client, LandingZone, OnBoardSide ) - self:F() - - local Valid = true - - - return Valid -end - - -function CARGO_SLINGLOAD:OnBoarded( Client, LandingZone ) - self:F() - - local OnBoarded = false - - local CargoStaticUnit = StaticObject.getByName( self.CargoName ) - if CargoStaticUnit then - if not routines.IsStaticInZones( CargoStaticUnit, LandingZone ) then - OnBoarded = true - end - end - - return OnBoarded -end - - -function CARGO_SLINGLOAD:UnLoad( Client, TargetZoneName ) - self:F() - - self:T( 'self.CargoName = ' .. self.CargoName ) - self:T( 'self.CargoGroupName = ' .. self.CargoGroupName ) - - self:StatusUnLoaded() - - return Cargo -end - -CARGO_ZONE = { - ClassName="CARGO_ZONE", - CargoZoneName = '', - CargoHostUnitName = '', - SIGNAL = { - TYPE = { - SMOKE = { ID = 1, TEXT = "smoke" }, - FLARE = { ID = 2, TEXT = "flare" } - }, - COLOR = { - GREEN = { ID = 1, TRIGGERCOLOR = trigger.smokeColor.Green, TEXT = "A green" }, - RED = { ID = 2, TRIGGERCOLOR = trigger.smokeColor.Red, TEXT = "A red" }, - WHITE = { ID = 3, TRIGGERCOLOR = trigger.smokeColor.White, TEXT = "A white" }, - ORANGE = { ID = 4, TRIGGERCOLOR = trigger.smokeColor.Orange, TEXT = "An orange" }, - BLUE = { ID = 5, TRIGGERCOLOR = trigger.smokeColor.Blue, TEXT = "A blue" }, - YELLOW = { ID = 6, TRIGGERCOLOR = trigger.flareColor.Yellow, TEXT = "A yellow" } - } - } -} - ---- Creates a new zone where cargo can be collected or deployed. --- The zone functionality is useful to smoke or indicate routes for cargo pickups or deployments. --- Provide the zone name as declared in the mission file into the CargoZoneName in the :New method. --- An optional parameter is the CargoHostName, which is a Group declared with Late Activation switched on in the mission file. --- The CargoHostName is the "host" of the cargo zone: --- --- * It will smoke the zone position when a client is approaching the zone. --- * Depending on the cargo type, it will assist in the delivery of the cargo by driving to and from the client. --- --- @param #CARGO_ZONE self --- @param #string CargoZoneName The name of the zone as declared within the mission editor. --- @param #string CargoHostName The name of the Group "hosting" the zone. The Group MUST NOT be a static, and must be a "mobile" unit. -function CARGO_ZONE:New( CargoZoneName, CargoHostName ) local self = BASE:Inherit( self, ZONE:New( CargoZoneName ) ) - self:F( { CargoZoneName, CargoHostName } ) - - self.CargoZoneName = CargoZoneName - self.SignalHeight = 2 - --self.CargoZone = trigger.misc.getZone( CargoZoneName ) - - - if CargoHostName then - self.CargoHostName = CargoHostName - end - - self:T( self.CargoZoneName ) - - return self -end - -function CARGO_ZONE:Spawn() - self:F( self.CargoHostName ) - - if self.CargoHostName then -- Only spawn a host in the zone when there is one given as a parameter in the New function. - if self.CargoHostSpawn then - local CargoHostGroup = self.CargoHostSpawn:GetGroupFromIndex() - if CargoHostGroup and CargoHostGroup:IsAlive() then - else - self.CargoHostSpawn:ReSpawn( 1 ) - end - else - self:T( "Initialize CargoHostSpawn" ) - self.CargoHostSpawn = SPAWN:New( self.CargoHostName ):Limit( 1, 1 ) - self.CargoHostSpawn:ReSpawn( 1 ) - end - end - - return self -end - -function CARGO_ZONE:GetHostUnit() - self:F( self ) - - if self.CargoHostName then - - -- A Host has been given, signal the host - local CargoHostGroup = self.CargoHostSpawn:GetGroupFromIndex() - local CargoHostUnit - if CargoHostGroup and CargoHostGroup:IsAlive() then - CargoHostUnit = CargoHostGroup:GetUnit(1) - else - CargoHostUnit = StaticObject.getByName( self.CargoHostName ) - end - - return CargoHostUnit - end - - return nil -end - -function CARGO_ZONE:ReportCargosToClient( Client, CargoType ) - self:F() - - local SignalUnit = self:GetHostUnit() - - if SignalUnit then - - local SignalUnitTypeName = SignalUnit:getTypeName() - - local HostMessage = "" - - local IsCargo = false - for CargoID, Cargo in pairs( CARGOS ) do - if Cargo.CargoType == Task.CargoType then - if Cargo:IsStatusNone() then - HostMessage = HostMessage .. " - " .. Cargo.CargoName .. " - " .. Cargo.CargoType .. " (" .. Cargo.Weight .. "kg)" .. "\n" - IsCargo = true - end - end - end - - if not IsCargo then - HostMessage = "No Cargo Available." - end - - Client:Message( HostMessage, 20, SignalUnitTypeName .. ": Reporting Cargo", 10 ) - end -end - - -function CARGO_ZONE:Signal() - self:F() - - local Signalled = false - - if self.SignalType then - - if self.CargoHostName then - - -- A Host has been given, signal the host - - local SignalUnit = self:GetHostUnit() - - if SignalUnit then - - self:T( 'Signalling Unit' ) - local SignalVehicleVec3 = SignalUnit:GetVec3() - SignalVehicleVec3.y = SignalVehicleVec3.y + 2 - - if self.SignalType.ID == CARGO_ZONE.SIGNAL.TYPE.SMOKE.ID then - - trigger.action.smoke( SignalVehicleVec3, self.SignalColor.TRIGGERCOLOR ) - Signalled = true - - elseif self.SignalType.ID == CARGO_ZONE.SIGNAL.TYPE.FLARE.ID then - - trigger.action.signalFlare( SignalVehicleVec3, self.SignalColor.TRIGGERCOLOR , 0 ) - Signalled = false - - end - end - - else - - local ZoneVec3 = self:GetPointVec3( self.SignalHeight ) -- Get the zone position + the landheight + 2 meters - - if self.SignalType.ID == CARGO_ZONE.SIGNAL.TYPE.SMOKE.ID then - - trigger.action.smoke( ZoneVec3, self.SignalColor.TRIGGERCOLOR ) - Signalled = true - - elseif self.SignalType.ID == CARGO_ZONE.SIGNAL.TYPE.FLARE.ID then - trigger.action.signalFlare( ZoneVec3, self.SignalColor.TRIGGERCOLOR, 0 ) - Signalled = false - - end - end - end - - return Signalled - -end - -function CARGO_ZONE:WhiteSmoke( SignalHeight ) - self:F() - - self.SignalType = CARGO_ZONE.SIGNAL.TYPE.SMOKE - self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.WHITE - - if SignalHeight then - self.SignalHeight = SignalHeight - end - - return self -end - -function CARGO_ZONE:BlueSmoke( SignalHeight ) - self:F() - - self.SignalType = CARGO_ZONE.SIGNAL.TYPE.SMOKE - self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.BLUE - - if SignalHeight then - self.SignalHeight = SignalHeight - end - - return self -end - -function CARGO_ZONE:RedSmoke( SignalHeight ) - self:F() - - self.SignalType = CARGO_ZONE.SIGNAL.TYPE.SMOKE - self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.RED - - if SignalHeight then - self.SignalHeight = SignalHeight - end - - return self -end - -function CARGO_ZONE:OrangeSmoke( SignalHeight ) - self:F() - - self.SignalType = CARGO_ZONE.SIGNAL.TYPE.SMOKE - self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.ORANGE - - if SignalHeight then - self.SignalHeight = SignalHeight - end - - return self -end - -function CARGO_ZONE:GreenSmoke( SignalHeight ) - self:F() - - self.SignalType = CARGO_ZONE.SIGNAL.TYPE.SMOKE - self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.GREEN - - if SignalHeight then - self.SignalHeight = SignalHeight - end - - return self -end - - -function CARGO_ZONE:WhiteFlare( SignalHeight ) - self:F() - - self.SignalType = CARGO_ZONE.SIGNAL.TYPE.FLARE - self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.WHITE - - if SignalHeight then - self.SignalHeight = SignalHeight - end - - return self -end - -function CARGO_ZONE:RedFlare( SignalHeight ) - self:F() - - self.SignalType = CARGO_ZONE.SIGNAL.TYPE.FLARE - self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.RED - - if SignalHeight then - self.SignalHeight = SignalHeight - end - - return self -end - -function CARGO_ZONE:GreenFlare( SignalHeight ) - self:F() - - self.SignalType = CARGO_ZONE.SIGNAL.TYPE.FLARE - self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.GREEN - - if SignalHeight then - self.SignalHeight = SignalHeight - end - - return self -end - -function CARGO_ZONE:YellowFlare( SignalHeight ) - self:F() - - self.SignalType = CARGO_ZONE.SIGNAL.TYPE.FLARE - self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.YELLOW - - if SignalHeight then - self.SignalHeight = SignalHeight - end - - return self -end - - -function CARGO_ZONE:GetCargoHostUnit() - self:F( self ) - - if self.CargoHostSpawn then - local CargoHostGroup = self.CargoHostSpawn:GetGroupFromIndex(1) - if CargoHostGroup and CargoHostGroup:IsAlive() then - local CargoHostUnit = CargoHostGroup:GetUnit(1) - if CargoHostUnit and CargoHostUnit:IsAlive() then - return CargoHostUnit - end - end - end - - return nil -end - -function CARGO_ZONE:GetCargoZoneName() - self:F() - - return self.CargoZoneName -end - - - - - - - - diff --git a/Moose Development/Moose/Base.lua b/Moose Development/Moose/Core/Base.lua similarity index 64% rename from Moose Development/Moose/Base.lua rename to Moose Development/Moose/Core/Base.lua index b8559f421..e0cfd57ae 100644 --- a/Moose Development/Moose/Base.lua +++ b/Moose Development/Moose/Core/Base.lua @@ -13,8 +13,8 @@ -- -- 1.1) BASE constructor -- --------------------- --- Any class derived from BASE, must use the @{Base#BASE.New) constructor within the @{Base#BASE.Inherit) method. --- See an example at the @{Base#BASE.New} method how this is done. +-- Any class derived from BASE, must use the @{Core.Base#BASE.New) constructor within the @{Core.Base#BASE.Inherit) method. +-- See an example at the @{Core.Base#BASE.New} method how this is done. -- -- 1.2) BASE Trace functionality -- ----------------------------- @@ -90,24 +90,6 @@ FORMATION = { ---- The base constructor. This is the top top class of all classed defined within the MOOSE. --- Any new class needs to be derived from this class for proper inheritance. --- @param #BASE self --- @return #BASE The new instance of the BASE class. --- @usage --- -- This declares the constructor of the class TASK, inheriting from BASE. --- --- TASK constructor --- -- @param #TASK self --- -- @param Parameter The parameter of the New constructor. --- -- @return #TASK self --- function TASK:New( Parameter ) --- --- local self = BASE:Inherit( self, BASE:New() ) --- --- self.Variable = Parameter --- --- return self --- end -- @todo need to investigate if the deepCopy is really needed... Don't think so. function BASE:New() local self = routines.utils.deepCopy( self ) -- Create a new self instance @@ -116,9 +98,40 @@ function BASE:New() self.__index = self _ClassID = _ClassID + 1 self.ClassID = _ClassID + + return self end +function BASE:_Destructor() + --self:E("_Destructor") + + --self:EventRemoveAll() +end + +function BASE:_SetDestructor() + + -- TODO: Okay, this is really technical... + -- When you set a proxy to a table to catch __gc, weak tables don't behave like weak... + -- Therefore, I am parking this logic until I've properly discussed all this with the community. + --[[ + local proxy = newproxy(true) + local proxyMeta = getmetatable(proxy) + + proxyMeta.__gc = function () + env.info("In __gc for " .. self:GetClassNameAndID() ) + if self._Destructor then + self:_Destructor() + end + end + + -- keep the userdata from newproxy reachable until the object + -- table is about to be garbage-collected - then the __gc hook + -- will be invoked and the destructor called + rawset( self, '__proxy', proxy ) + --]] +end + --- This is the worker method to inherit from a parent class. -- @param #BASE self -- @param Child is the Child class that inherits. @@ -131,6 +144,8 @@ function BASE:Inherit( Child, Parent ) if Child ~= nil then setmetatable( Child, Parent ) Child.__index = Child + + Child:_SetDestructor() end --self:T( 'Inherited from ' .. Parent.ClassName ) return Child @@ -170,7 +185,7 @@ end --- Set a new listener for the class. -- @param self --- @param DCSTypes#Event Event +-- @param Dcs.DCSTypes#Event Event -- @param #function EventFunction -- @return #BASE function BASE:AddEvent( Event, EventFunction ) @@ -186,12 +201,278 @@ end --- Returns the event dispatcher -- @param #BASE self --- @return Event#EVENT +-- @return Core.Event#EVENT function BASE:Event() return _EVENTDISPATCHER end +--- Remove all subscribed events +-- @param #BASE self +-- @return #BASE +function BASE:EventRemoveAll() + + _EVENTDISPATCHER:RemoveAll( self ) + + return self +end + +--- Subscribe to a S_EVENT_SHOT event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnShot( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_SHOT ) + + return self +end + +--- Subscribe to a S_EVENT_HIT event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnHit( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_HIT ) + + return self +end + +--- Subscribe to a S_EVENT_TAKEOFF event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnTakeOff( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_TAKEOFF ) + + return self +end + +--- Subscribe to a S_EVENT_LAND event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnLand( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_LAND ) + + return self +end + +--- Subscribe to a S_EVENT_CRASH event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnCrash( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_CRASH ) + + return self +end + +--- Subscribe to a S_EVENT_EJECTION event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnEjection( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_EJECTION ) + + return self +end + + +--- Subscribe to a S_EVENT_REFUELING event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnRefueling( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_REFUELING ) + + return self +end + +--- Subscribe to a S_EVENT_DEAD event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnDead( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_DEAD ) + + return self +end + +--- Subscribe to a S_EVENT_PILOT_DEAD event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnPilotDead( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_PILOT_DEAD ) + + return self +end + +--- Subscribe to a S_EVENT_BASE_CAPTURED event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnBaseCaptured( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_BASE_CAPTURED ) + + return self +end + +--- Subscribe to a S_EVENT_MISSION_START event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnMissionStart( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_MISSION_START ) + + return self +end + +--- Subscribe to a S_EVENT_MISSION_END event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnPlayerMissionEnd( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_MISSION_END ) + + return self +end + +--- Subscribe to a S_EVENT_TOOK_CONTROL event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnTookControl( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_TOOK_CONTROL ) + + return self +end + +--- Subscribe to a S_EVENT_REFUELING_STOP event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnRefuelingStop( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_REFUELING_STOP ) + + return self +end + +--- Subscribe to a S_EVENT_BIRTH event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnBirth( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_BIRTH ) + + return self +end + +--- Subscribe to a S_EVENT_HUMAN_FAILURE event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnHumanFailure( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_HUMAN_FAILURE ) + + return self +end + +--- Subscribe to a S_EVENT_ENGINE_STARTUP event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnEngineStartup( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_ENGINE_STARTUP ) + + return self +end + +--- Subscribe to a S_EVENT_ENGINE_SHUTDOWN event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnEngineShutdown( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_ENGINE_SHUTDOWN ) + + return self +end + +--- Subscribe to a S_EVENT_PLAYER_ENTER_UNIT event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnPlayerEnterUnit( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_PLAYER_ENTER_UNIT ) + + return self +end + +--- Subscribe to a S_EVENT_PLAYER_LEAVE_UNIT event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnPlayerLeaveUnit( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_PLAYER_LEAVE_UNIT ) + + return self +end + +--- Subscribe to a S_EVENT_PLAYER_COMMENT event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnPlayerComment( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_PLAYER_COMMENT ) + + return self +end + +--- Subscribe to a S_EVENT_SHOOTING_START event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnShootingStart( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_SHOOTING_START ) + + return self +end + +--- Subscribe to a S_EVENT_SHOOTING_END event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnShootingEnd( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_SHOOTING_END ) + + return self +end + + + @@ -268,8 +549,8 @@ local BaseEventCodes = { --- Creation of a Birth Event. -- @param #BASE self --- @param DCSTypes#Time EventTime The time stamp of the event. --- @param DCSObject#Object Initiator The initiating object of the event. +-- @param Dcs.DCSTypes#Time EventTime The time stamp of the event. +-- @param Dcs.DCSWrapper.Object#Object Initiator The initiating object of the event. -- @param #string IniUnitName The initiating unit name. -- @param place -- @param subplace @@ -290,8 +571,8 @@ end --- Creation of a Crash Event. -- @param #BASE self --- @param DCSTypes#Time EventTime The time stamp of the event. --- @param DCSObject#Object Initiator The initiating object of the event. +-- @param Dcs.DCSTypes#Time EventTime The time stamp of the event. +-- @param Dcs.DCSWrapper.Object#Object Initiator The initiating object of the event. function BASE:CreateEventCrash( EventTime, Initiator ) self:F( { EventTime, Initiator } ) @@ -304,10 +585,10 @@ function BASE:CreateEventCrash( EventTime, Initiator ) world.onEvent( Event ) end --- TODO: Complete DCSTypes#Event structure. +-- TODO: Complete Dcs.DCSTypes#Event structure. --- The main event handling function... This function captures all events generated for the class. -- @param #BASE self --- @param DCSTypes#Event event +-- @param Dcs.DCSTypes#Event event function BASE:onEvent(event) --self:F( { BaseEventCodes[event.id], event } ) @@ -350,7 +631,7 @@ function BASE:GetState( Object, StateName ) local ClassNameAndID = Object:GetClassNameAndID() if self.States[ClassNameAndID] then - local State = self.States[ClassNameAndID][StateName] + local State = self.States[ClassNameAndID][StateName] or false self:T2( { ClassNameAndID, StateName, State } ) return State end diff --git a/Moose Development/Moose/Database.lua b/Moose Development/Moose/Core/Database.lua similarity index 98% rename from Moose Development/Moose/Database.lua rename to Moose Development/Moose/Core/Database.lua index 04e8d52b1..37b76490c 100644 --- a/Moose Development/Moose/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -2,7 +2,7 @@ -- -- ==== -- --- 1) @{Database#DATABASE} class, extends @{Base#BASE} +-- 1) @{Core.Database#DATABASE} class, extends @{Core.Base#BASE} -- =================================================== -- Mission designers can use the DATABASE class to refer to: -- @@ -38,7 +38,7 @@ --- DATABASE class -- @type DATABASE --- @extends Base#BASE +-- @extends Core.Base#BASE DATABASE = { ClassName = "DATABASE", Templates = { @@ -106,7 +106,7 @@ end --- Finds a Unit based on the Unit Name. -- @param #DATABASE self -- @param #string UnitName --- @return Unit#UNIT The found Unit. +-- @return Wrapper.Unit#UNIT The found Unit. function DATABASE:FindUnit( UnitName ) local UnitFound = self.UNITS[UnitName] @@ -154,7 +154,7 @@ end --- Finds a STATIC based on the StaticName. -- @param #DATABASE self -- @param #string StaticName --- @return Static#STATIC The found STATIC. +-- @return Wrapper.Static#STATIC The found STATIC. function DATABASE:FindStatic( StaticName ) local StaticFound = self.STATICS[StaticName] @@ -181,7 +181,7 @@ end --- Finds a AIRBASE based on the AirbaseName. -- @param #DATABASE self -- @param #string AirbaseName --- @return Airbase#AIRBASE The found AIRBASE. +-- @return Wrapper.Airbase#AIRBASE The found AIRBASE. function DATABASE:FindAirbase( AirbaseName ) local AirbaseFound = self.AIRBASES[AirbaseName] @@ -192,7 +192,7 @@ end --- Finds a CLIENT based on the ClientName. -- @param #DATABASE self -- @param #string ClientName --- @return Client#CLIENT The found CLIENT. +-- @return Wrapper.Client#CLIENT The found CLIENT. function DATABASE:FindClient( ClientName ) local ClientFound = self.CLIENTS[ClientName] @@ -215,7 +215,7 @@ end --- Finds a GROUP based on the GroupName. -- @param #DATABASE self -- @param #string GroupName --- @return Group#GROUP The found GROUP. +-- @return Wrapper.Group#GROUP The found GROUP. function DATABASE:FindGroup( GroupName ) local GroupFound = self.GROUPS[GroupName] @@ -535,7 +535,7 @@ end --- Handles the OnBirth event for the alive units set. -- @param #DATABASE self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event function DATABASE:_EventOnBirth( Event ) self:F2( { Event } ) @@ -549,7 +549,7 @@ end --- Handles the OnDead or OnCrash event for alive units set. -- @param #DATABASE self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event function DATABASE:_EventOnDeadOrCrash( Event ) self:F2( { Event } ) @@ -564,7 +564,7 @@ end --- Handles the OnPlayerEnterUnit event to fill the active players table (with the unit filter applied). -- @param #DATABASE self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event function DATABASE:_EventOnPlayerEnterUnit( Event ) self:F2( { Event } ) @@ -579,7 +579,7 @@ end --- Handles the OnPlayerLeaveUnit event to clean the active players table. -- @param #DATABASE self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event function DATABASE:_EventOnPlayerLeaveUnit( Event ) self:F2( { Event } ) diff --git a/Moose Development/Moose/Event.lua b/Moose Development/Moose/Core/Event.lua similarity index 60% rename from Moose Development/Moose/Event.lua rename to Moose Development/Moose/Core/Event.lua index 694099d59..e3f5301a2 100644 --- a/Moose Development/Moose/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -1,6 +1,19 @@ ---- The EVENT class models an efficient event handling process between other classes and its units, weapons. +--- This module contains the EVENT class. +-- +-- === +-- +-- Takes care of EVENT dispatching between DCS events and event handling functions defined in MOOSE classes. +-- +-- === +-- +-- The above menus classes **are derived** from 2 main **abstract** classes defined within the MOOSE framework (so don't use these): +-- +-- === +-- +-- ### Contributions: - +-- ### Authors: FlightControl : Design & Programming +-- -- @module Event --- @author FlightControl --- The EVENT structure -- @type EVENT @@ -45,13 +58,13 @@ local _EVENTCODES = { -- @field weapon -- @field IniDCSUnit -- @field IniDCSUnitName --- @field Unit#UNIT IniUnit +-- @field Wrapper.Unit#UNIT IniUnit -- @field #string IniUnitName -- @field IniDCSGroup -- @field IniDCSGroupName -- @field TgtDCSUnit -- @field TgtDCSUnitName --- @field Unit#UNIT TgtUnit +-- @field Wrapper.Unit#UNIT TgtUnit -- @field #string TgtUnitName -- @field TgtDCSGroup -- @field TgtDCSGroupName @@ -80,45 +93,62 @@ end --- Initializes the Events structure for the event -- @param #EVENT self --- @param DCSWorld#world.event EventID --- @param #string EventClass +-- @param Dcs.DCSWorld#world.event EventID +-- @param Core.Base#BASE EventClass -- @return #EVENT.Events function EVENT:Init( EventID, EventClass ) self:F3( { _EVENTCODES[EventID], EventClass } ) - if not self.Events[EventID] then - self.Events[EventID] = {} + + if not self.Events[EventID] then + -- Create a WEAK table to ensure that the garbage collector is cleaning the event links when the object usage is cleaned. + self.Events[EventID] = setmetatable( {}, { __mode = "k" } ) + end + if not self.Events[EventID][EventClass] then - self.Events[EventID][EventClass] = {} + self.Events[EventID][EventClass] = setmetatable( {}, { __mode = "k" } ) end return self.Events[EventID][EventClass] end --- Removes an Events entry -- @param #EVENT self --- @param Base#BASE EventSelf The self instance of the class for which the event is. --- @param DCSWorld#world.event EventID +-- @param Core.Base#BASE EventClass The self instance of the class for which the event is. +-- @param Dcs.DCSWorld#world.event EventID -- @return #EVENT.Events -function EVENT:Remove( EventSelf, EventID ) - self:F3( { EventSelf, _EVENTCODES[EventID] } ) +function EVENT:Remove( EventClass, EventID ) + self:F3( { EventClass, _EVENTCODES[EventID] } ) - local EventClass = EventSelf:GetClassNameAndID() + local EventClass = EventClass self.Events[EventID][EventClass] = nil end +--- Clears all event subscriptions for a @{Core.Base#BASE} derived object. +-- @param #EVENT self +-- @param Core.Base#BASE EventObject +function EVENT:RemoveAll( EventObject ) + self:F3( { EventObject:GetClassNameAndID() } ) + + local EventClass = EventObject:GetClassNameAndID() + for EventID, EventData in pairs( self.Events ) do + self.Events[EventID][EventClass] = nil + end +end + + --- Create an OnDead event handler for a group -- @param #EVENT self -- @param #table EventTemplate -- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param EventSelf The self instance of the class for which the event is. +-- @param EventClass The instance of the class for which the event is. -- @param #function OnEventFunction -- @return #EVENT -function EVENT:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, OnEventFunction ) +function EVENT:OnEventForTemplate( EventTemplate, EventFunction, EventClass, OnEventFunction ) self:F2( EventTemplate.name ) for EventUnitID, EventUnit in pairs( EventTemplate.units ) do - OnEventFunction( self, EventUnit.name, EventFunction, EventSelf ) + OnEventFunction( self, EventUnit.name, EventFunction, EventClass ) end return self end @@ -126,15 +156,15 @@ end --- Set a new listener for an S_EVENT_X event independent from a unit or a weapon. -- @param #EVENT self -- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf The self instance of the class for which the event is. +-- @param Core.Base#BASE EventClass The self instance of the class for which the event is captured. When the event happens, the event process will be called in this class provided. -- @param EventID -- @return #EVENT -function EVENT:OnEventGeneric( EventFunction, EventSelf, EventID ) +function EVENT:OnEventGeneric( EventFunction, EventClass, EventID ) self:F2( { EventID } ) - local Event = self:Init( EventID, EventSelf:GetClassNameAndID() ) + local Event = self:Init( EventID, EventClass ) Event.EventFunction = EventFunction - Event.EventSelf = EventSelf + Event.EventClass = EventClass return self end @@ -143,19 +173,19 @@ end -- @param #EVENT self -- @param #string EventDCSUnitName -- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf The self instance of the class for which the event is. +-- @param Core.Base#BASE EventClass The self instance of the class for which the event is. -- @param EventID -- @return #EVENT -function EVENT:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, EventID ) +function EVENT:OnEventForUnit( EventDCSUnitName, EventFunction, EventClass, EventID ) self:F2( EventDCSUnitName ) - local Event = self:Init( EventID, EventSelf:GetClassNameAndID() ) + local Event = self:Init( EventID, EventClass ) if not Event.IniUnit then Event.IniUnit = {} end Event.IniUnit[EventDCSUnitName] = {} Event.IniUnit[EventDCSUnitName].EventFunction = EventFunction - Event.IniUnit[EventDCSUnitName].EventSelf = EventSelf + Event.IniUnit[EventDCSUnitName].EventClass = EventClass return self end @@ -163,14 +193,14 @@ do -- OnBirth --- Create an OnBirth event handler for a group -- @param #EVENT self - -- @param Group#GROUP EventGroup + -- @param Wrapper.Group#GROUP EventGroup -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param EventSelf The self instance of the class for which the event is. + -- @param EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnBirthForTemplate( EventTemplate, EventFunction, EventSelf ) + function EVENT:OnBirthForTemplate( EventTemplate, EventFunction, EventClass ) self:F2( EventTemplate.name ) - self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnBirthForUnit ) + self:OnEventForTemplate( EventTemplate, EventFunction, EventClass, self.OnBirthForUnit ) return self end @@ -178,12 +208,12 @@ do -- OnBirth --- Set a new listener for an S_EVENT_BIRTH event, and registers the unit born. -- @param #EVENT self -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf + -- @param Base#BASE EventClass -- @return #EVENT - function EVENT:OnBirth( EventFunction, EventSelf ) + function EVENT:OnBirth( EventFunction, EventClass ) self:F2() - self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_BIRTH ) + self:OnEventGeneric( EventFunction, EventClass, world.event.S_EVENT_BIRTH ) return self end @@ -192,24 +222,24 @@ do -- OnBirth -- @param #EVENT self -- @param #string EventDCSUnitName The id of the unit for the event to be handled. -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf + -- @param Base#BASE EventClass -- @return #EVENT - function EVENT:OnBirthForUnit( EventDCSUnitName, EventFunction, EventSelf ) + function EVENT:OnBirthForUnit( EventDCSUnitName, EventFunction, EventClass ) self:F2( EventDCSUnitName ) - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_BIRTH ) + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventClass, world.event.S_EVENT_BIRTH ) return self end --- Stop listening to S_EVENT_BIRTH event. -- @param #EVENT self - -- @param Base#BASE EventSelf + -- @param Base#BASE EventClass -- @return #EVENT - function EVENT:OnBirthRemove( EventSelf ) + function EVENT:OnBirthRemove( EventClass ) self:F2() - self:Remove( EventSelf, world.event.S_EVENT_BIRTH ) + self:Remove( EventClass, world.event.S_EVENT_BIRTH ) return self end @@ -221,14 +251,14 @@ do -- OnCrash --- Create an OnCrash event handler for a group -- @param #EVENT self - -- @param Group#GROUP EventGroup + -- @param Wrapper.Group#GROUP EventGroup -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param EventSelf The self instance of the class for which the event is. + -- @param EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnCrashForTemplate( EventTemplate, EventFunction, EventSelf ) + function EVENT:OnCrashForTemplate( EventTemplate, EventFunction, EventClass ) self:F2( EventTemplate.name ) - self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnCrashForUnit ) + self:OnEventForTemplate( EventTemplate, EventFunction, EventClass, self.OnCrashForUnit ) return self end @@ -236,12 +266,12 @@ do -- OnCrash --- Set a new listener for an S_EVENT_CRASH event. -- @param #EVENT self -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf + -- @param Base#BASE EventClass -- @return #EVENT - function EVENT:OnCrash( EventFunction, EventSelf ) + function EVENT:OnCrash( EventFunction, EventClass ) self:F2() - self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_CRASH ) + self:OnEventGeneric( EventFunction, EventClass, world.event.S_EVENT_CRASH ) return self end @@ -250,24 +280,24 @@ do -- OnCrash -- @param #EVENT self -- @param #string EventDCSUnitName -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @param Base#BASE EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnCrashForUnit( EventDCSUnitName, EventFunction, EventSelf ) + function EVENT:OnCrashForUnit( EventDCSUnitName, EventFunction, EventClass ) self:F2( EventDCSUnitName ) - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_CRASH ) + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventClass, world.event.S_EVENT_CRASH ) return self end --- Stop listening to S_EVENT_CRASH event. -- @param #EVENT self - -- @param Base#BASE EventSelf + -- @param Base#BASE EventClass -- @return #EVENT - function EVENT:OnCrashRemove( EventSelf ) + function EVENT:OnCrashRemove( EventClass ) self:F2() - self:Remove( EventSelf, world.event.S_EVENT_CRASH ) + self:Remove( EventClass, world.event.S_EVENT_CRASH ) return self end @@ -278,14 +308,14 @@ do -- OnDead --- Create an OnDead event handler for a group -- @param #EVENT self - -- @param Group#GROUP EventGroup + -- @param Wrapper.Group#GROUP EventGroup -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param EventSelf The self instance of the class for which the event is. + -- @param EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnDeadForTemplate( EventTemplate, EventFunction, EventSelf ) + function EVENT:OnDeadForTemplate( EventTemplate, EventFunction, EventClass ) self:F2( EventTemplate.name ) - self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnDeadForUnit ) + self:OnEventForTemplate( EventTemplate, EventFunction, EventClass, self.OnDeadForUnit ) return self end @@ -293,12 +323,12 @@ do -- OnDead --- Set a new listener for an S_EVENT_DEAD event. -- @param #EVENT self -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf + -- @param Base#BASE EventClass -- @return #EVENT - function EVENT:OnDead( EventFunction, EventSelf ) + function EVENT:OnDead( EventFunction, EventClass ) self:F2() - self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_DEAD ) + self:OnEventGeneric( EventFunction, EventClass, world.event.S_EVENT_DEAD ) return self end @@ -308,24 +338,24 @@ do -- OnDead -- @param #EVENT self -- @param #string EventDCSUnitName -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @param Base#BASE EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnDeadForUnit( EventDCSUnitName, EventFunction, EventSelf ) + function EVENT:OnDeadForUnit( EventDCSUnitName, EventFunction, EventClass ) self:F2( EventDCSUnitName ) - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_DEAD ) + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventClass, world.event.S_EVENT_DEAD ) return self end --- Stop listening to S_EVENT_DEAD event. -- @param #EVENT self - -- @param Base#BASE EventSelf + -- @param Base#BASE EventClass -- @return #EVENT - function EVENT:OnDeadRemove( EventSelf ) + function EVENT:OnDeadRemove( EventClass ) self:F2() - self:Remove( EventSelf, world.event.S_EVENT_DEAD ) + self:Remove( EventClass, world.event.S_EVENT_DEAD ) return self end @@ -338,12 +368,12 @@ do -- OnPilotDead --- Set a new listener for an S_EVENT_PILOT_DEAD event. -- @param #EVENT self -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf + -- @param Base#BASE EventClass -- @return #EVENT - function EVENT:OnPilotDead( EventFunction, EventSelf ) + function EVENT:OnPilotDead( EventFunction, EventClass ) self:F2() - self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_PILOT_DEAD ) + self:OnEventGeneric( EventFunction, EventClass, world.event.S_EVENT_PILOT_DEAD ) return self end @@ -352,24 +382,24 @@ do -- OnPilotDead -- @param #EVENT self -- @param #string EventDCSUnitName -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @param Base#BASE EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnPilotDeadForUnit( EventDCSUnitName, EventFunction, EventSelf ) + function EVENT:OnPilotDeadForUnit( EventDCSUnitName, EventFunction, EventClass ) self:F2( EventDCSUnitName ) - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_PILOT_DEAD ) + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventClass, world.event.S_EVENT_PILOT_DEAD ) return self end --- Stop listening to S_EVENT_PILOT_DEAD event. -- @param #EVENT self - -- @param Base#BASE EventSelf + -- @param Base#BASE EventClass -- @return #EVENT - function EVENT:OnPilotDeadRemove( EventSelf ) + function EVENT:OnPilotDeadRemove( EventClass ) self:F2() - self:Remove( EventSelf, world.event.S_EVENT_PILOT_DEAD ) + self:Remove( EventClass, world.event.S_EVENT_PILOT_DEAD ) return self end @@ -381,12 +411,12 @@ do -- OnLand -- @param #EVENT self -- @param #table EventTemplate -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param EventSelf The self instance of the class for which the event is. + -- @param EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnLandForTemplate( EventTemplate, EventFunction, EventSelf ) + function EVENT:OnLandForTemplate( EventTemplate, EventFunction, EventClass ) self:F2( EventTemplate.name ) - self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnLandForUnit ) + self:OnEventForTemplate( EventTemplate, EventFunction, EventClass, self.OnLandForUnit ) return self end @@ -395,24 +425,24 @@ do -- OnLand -- @param #EVENT self -- @param #string EventDCSUnitName -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @param Base#BASE EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnLandForUnit( EventDCSUnitName, EventFunction, EventSelf ) + function EVENT:OnLandForUnit( EventDCSUnitName, EventFunction, EventClass ) self:F2( EventDCSUnitName ) - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_LAND ) + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventClass, world.event.S_EVENT_LAND ) return self end --- Stop listening to S_EVENT_LAND event. -- @param #EVENT self - -- @param Base#BASE EventSelf + -- @param Base#BASE EventClass -- @return #EVENT - function EVENT:OnLandRemove( EventSelf ) + function EVENT:OnLandRemove( EventClass ) self:F2() - self:Remove( EventSelf, world.event.S_EVENT_LAND ) + self:Remove( EventClass, world.event.S_EVENT_LAND ) return self end @@ -425,12 +455,12 @@ do -- OnTakeOff -- @param #EVENT self -- @param #table EventTemplate -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param EventSelf The self instance of the class for which the event is. + -- @param EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnTakeOffForTemplate( EventTemplate, EventFunction, EventSelf ) + function EVENT:OnTakeOffForTemplate( EventTemplate, EventFunction, EventClass ) self:F2( EventTemplate.name ) - self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnTakeOffForUnit ) + self:OnEventForTemplate( EventTemplate, EventFunction, EventClass, self.OnTakeOffForUnit ) return self end @@ -439,24 +469,24 @@ do -- OnTakeOff -- @param #EVENT self -- @param #string EventDCSUnitName -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @param Base#BASE EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnTakeOffForUnit( EventDCSUnitName, EventFunction, EventSelf ) + function EVENT:OnTakeOffForUnit( EventDCSUnitName, EventFunction, EventClass ) self:F2( EventDCSUnitName ) - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_TAKEOFF ) + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventClass, world.event.S_EVENT_TAKEOFF ) return self end --- Stop listening to S_EVENT_TAKEOFF event. -- @param #EVENT self - -- @param Base#BASE EventSelf + -- @param Base#BASE EventClass -- @return #EVENT - function EVENT:OnTakeOffRemove( EventSelf ) + function EVENT:OnTakeOffRemove( EventClass ) self:F2() - self:Remove( EventSelf, world.event.S_EVENT_TAKEOFF ) + self:Remove( EventClass, world.event.S_EVENT_TAKEOFF ) return self end @@ -470,12 +500,12 @@ do -- OnEngineShutDown -- @param #EVENT self -- @param #table EventTemplate -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param EventSelf The self instance of the class for which the event is. + -- @param EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnEngineShutDownForTemplate( EventTemplate, EventFunction, EventSelf ) + function EVENT:OnEngineShutDownForTemplate( EventTemplate, EventFunction, EventClass ) self:F2( EventTemplate.name ) - self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnEngineShutDownForUnit ) + self:OnEventForTemplate( EventTemplate, EventFunction, EventClass, self.OnEngineShutDownForUnit ) return self end @@ -484,24 +514,24 @@ do -- OnEngineShutDown -- @param #EVENT self -- @param #string EventDCSUnitName -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @param Base#BASE EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnEngineShutDownForUnit( EventDCSUnitName, EventFunction, EventSelf ) + function EVENT:OnEngineShutDownForUnit( EventDCSUnitName, EventFunction, EventClass ) self:F2( EventDCSUnitName ) - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_ENGINE_SHUTDOWN ) + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventClass, world.event.S_EVENT_ENGINE_SHUTDOWN ) return self end --- Stop listening to S_EVENT_ENGINE_SHUTDOWN event. -- @param #EVENT self - -- @param Base#BASE EventSelf + -- @param Base#BASE EventClass -- @return #EVENT - function EVENT:OnEngineShutDownRemove( EventSelf ) + function EVENT:OnEngineShutDownRemove( EventClass ) self:F2() - self:Remove( EventSelf, world.event.S_EVENT_ENGINE_SHUTDOWN ) + self:Remove( EventClass, world.event.S_EVENT_ENGINE_SHUTDOWN ) return self end @@ -514,24 +544,24 @@ do -- OnEngineStartUp -- @param #EVENT self -- @param #string EventDCSUnitName -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @param Base#BASE EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnEngineStartUpForUnit( EventDCSUnitName, EventFunction, EventSelf ) + function EVENT:OnEngineStartUpForUnit( EventDCSUnitName, EventFunction, EventClass ) self:F2( EventDCSUnitName ) - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_ENGINE_STARTUP ) + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventClass, world.event.S_EVENT_ENGINE_STARTUP ) return self end --- Stop listening to S_EVENT_ENGINE_STARTUP event. -- @param #EVENT self - -- @param Base#BASE EventSelf + -- @param Base#BASE EventClass -- @return #EVENT - function EVENT:OnEngineStartUpRemove( EventSelf ) + function EVENT:OnEngineStartUpRemove( EventClass ) self:F2() - self:Remove( EventSelf, world.event.S_EVENT_ENGINE_STARTUP ) + self:Remove( EventClass, world.event.S_EVENT_ENGINE_STARTUP ) return self end @@ -542,12 +572,12 @@ do -- OnShot --- Set a new listener for an S_EVENT_SHOT event. -- @param #EVENT self -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @param Base#BASE EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnShot( EventFunction, EventSelf ) + function EVENT:OnShot( EventFunction, EventClass ) self:F2() - self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_SHOT ) + self:OnEventGeneric( EventFunction, EventClass, world.event.S_EVENT_SHOT ) return self end @@ -556,24 +586,24 @@ do -- OnShot -- @param #EVENT self -- @param #string EventDCSUnitName -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @param Base#BASE EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnShotForUnit( EventDCSUnitName, EventFunction, EventSelf ) + function EVENT:OnShotForUnit( EventDCSUnitName, EventFunction, EventClass ) self:F2( EventDCSUnitName ) - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_SHOT ) + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventClass, world.event.S_EVENT_SHOT ) return self end --- Stop listening to S_EVENT_SHOT event. -- @param #EVENT self - -- @param Base#BASE EventSelf + -- @param Base#BASE EventClass -- @return #EVENT - function EVENT:OnShotRemove( EventSelf ) + function EVENT:OnShotRemove( EventClass ) self:F2() - self:Remove( EventSelf, world.event.S_EVENT_SHOT ) + self:Remove( EventClass, world.event.S_EVENT_SHOT ) return self end @@ -586,12 +616,12 @@ do -- OnHit --- Set a new listener for an S_EVENT_HIT event. -- @param #EVENT self -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @param Base#BASE EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnHit( EventFunction, EventSelf ) + function EVENT:OnHit( EventFunction, EventClass ) self:F2() - self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_HIT ) + self:OnEventGeneric( EventFunction, EventClass, world.event.S_EVENT_HIT ) return self end @@ -600,24 +630,24 @@ do -- OnHit -- @param #EVENT self -- @param #string EventDCSUnitName -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @param Base#BASE EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnHitForUnit( EventDCSUnitName, EventFunction, EventSelf ) + function EVENT:OnHitForUnit( EventDCSUnitName, EventFunction, EventClass ) self:F2( EventDCSUnitName ) - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_HIT ) + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventClass, world.event.S_EVENT_HIT ) return self end --- Stop listening to S_EVENT_HIT event. -- @param #EVENT self - -- @param Base#BASE EventSelf + -- @param Base#BASE EventClass -- @return #EVENT - function EVENT:OnHitRemove( EventSelf ) + function EVENT:OnHitRemove( EventClass ) self:F2() - self:Remove( EventSelf, world.event.S_EVENT_HIT ) + self:Remove( EventClass, world.event.S_EVENT_HIT ) return self end @@ -629,24 +659,24 @@ do -- OnPlayerEnterUnit --- Set a new listener for an S_EVENT_PLAYER_ENTER_UNIT event. -- @param #EVENT self -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @param Base#BASE EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnPlayerEnterUnit( EventFunction, EventSelf ) + function EVENT:OnPlayerEnterUnit( EventFunction, EventClass ) self:F2() - self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_PLAYER_ENTER_UNIT ) + self:OnEventGeneric( EventFunction, EventClass, world.event.S_EVENT_PLAYER_ENTER_UNIT ) return self end --- Stop listening to S_EVENT_PLAYER_ENTER_UNIT event. -- @param #EVENT self - -- @param Base#BASE EventSelf + -- @param Base#BASE EventClass -- @return #EVENT - function EVENT:OnPlayerEnterRemove( EventSelf ) + function EVENT:OnPlayerEnterRemove( EventClass ) self:F2() - self:Remove( EventSelf, world.event.S_EVENT_PLAYER_ENTER_UNIT ) + self:Remove( EventClass, world.event.S_EVENT_PLAYER_ENTER_UNIT ) return self end @@ -657,24 +687,24 @@ do -- OnPlayerLeaveUnit --- Set a new listener for an S_EVENT_PLAYER_LEAVE_UNIT event. -- @param #EVENT self -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @param Base#BASE EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnPlayerLeaveUnit( EventFunction, EventSelf ) + function EVENT:OnPlayerLeaveUnit( EventFunction, EventClass ) self:F2() - self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_PLAYER_LEAVE_UNIT ) + self:OnEventGeneric( EventFunction, EventClass, world.event.S_EVENT_PLAYER_LEAVE_UNIT ) return self end --- Stop listening to S_EVENT_PLAYER_LEAVE_UNIT event. -- @param #EVENT self - -- @param Base#BASE EventSelf + -- @param Base#BASE EventClass -- @return #EVENT - function EVENT:OnPlayerLeaveRemove( EventSelf ) + function EVENT:OnPlayerLeaveRemove( EventClass ) self:F2() - self:Remove( EventSelf, world.event.S_EVENT_PLAYER_LEAVE_UNIT ) + self:Remove( EventClass, world.event.S_EVENT_PLAYER_LEAVE_UNIT ) return self end @@ -694,6 +724,10 @@ function EVENT:onEvent( Event ) Event.IniDCSUnitName = Event.IniDCSUnit:getName() Event.IniUnitName = Event.IniDCSUnitName Event.IniUnit = UNIT:FindByName( Event.IniDCSUnitName ) + if not Event.IniUnit then + -- Unit can be a CLIENT. Most likely this will be the case ... + Event.IniUnit = CLIENT:FindByName( Event.IniDCSUnitName, '', true ) + end Event.IniDCSGroupName = "" if Event.IniDCSGroup and Event.IniDCSGroup:isExist() then Event.IniDCSGroupName = Event.IniDCSGroup:getName() @@ -717,16 +751,21 @@ function EVENT:onEvent( Event ) Event.WeaponName = Event.Weapon:getTypeName() --Event.WeaponTgtDCSUnit = Event.Weapon:getTarget() end - self:E( { _EVENTCODES[Event.id], Event.initiator, Event.IniDCSUnitName, Event.target, Event.TgtDCSUnitName, Event.weapon, Event.WeaponName } ) - for ClassName, EventData in pairs( self.Events[Event.id] ) do + self:E( { _EVENTCODES[Event.id], Event, Event.IniDCSUnitName, Event.TgtDCSUnitName } ) + + -- Okay, we got the event from DCS. Now loop the self.Events[] table for the received Event.id, and for each EventData registered, check if a function needs to be called. + for EventClass, EventData in pairs( self.Events[Event.id] ) do + -- If the EventData is for a UNIT, the call directly the EventClass EventFunction for that UNIT. if Event.IniDCSUnitName and EventData.IniUnit and EventData.IniUnit[Event.IniDCSUnitName] then - self:T( { "Calling event function for class ", ClassName, " unit ", Event.IniUnitName } ) - EventData.IniUnit[Event.IniDCSUnitName].EventFunction( EventData.IniUnit[Event.IniDCSUnitName].EventSelf, Event ) + self:T( { "Calling EventFunction for Class ", EventClass:GetClassNameAndID(), ", Unit ", Event.IniUnitName } ) + EventData.IniUnit[Event.IniDCSUnitName].EventFunction( EventData.IniUnit[Event.IniDCSUnitName].EventClass, Event ) else + -- If the EventData is not bound to a specific unit, then call the EventClass EventFunction. + -- Note that here the EventFunction will need to implement and determine the logic for the relevant source- or target unit, or weapon. if Event.IniDCSUnit and not EventData.IniUnit then - if ClassName == EventData.EventSelf:GetClassNameAndID() then - self:T( { "Calling event function for class ", ClassName } ) - EventData.EventFunction( EventData.EventSelf, Event ) + if EventClass == EventData.EventClass then + self:T( { "Calling EventFunction for Class ", EventClass:GetClassNameAndID() } ) + EventData.EventFunction( EventData.EventClass, Event ) end end end diff --git a/Moose Development/Moose/Core/Fsm.lua b/Moose Development/Moose/Core/Fsm.lua new file mode 100644 index 000000000..ee4563e39 --- /dev/null +++ b/Moose Development/Moose/Core/Fsm.lua @@ -0,0 +1,729 @@ +--- This module contains the FSM class. +-- This development is based on a state machine implementation made by Conroy Kyle. +-- The state machine can be found here: https://github.com/kyleconroy/lua-state-machine +-- +-- I've taken the development and enhanced it to make the state machine hierarchical... +-- It is a fantastic development, this module. +-- +-- === +-- +-- 1) @{Workflow#FSM} class, extends @{Core.Base#BASE} +-- ============================================== +-- +-- 1.1) Add or remove objects from the FSM +-- -------------------------------------------- +-- @module Fsm +-- @author FlightControl + +do -- FSM + + --- FSM class + -- @type FSM + -- @extends Core.Base#BASE + FSM = { + ClassName = "FSM", + } + + --- Creates a new FSM object. + -- @param #FSM self + -- @return #FSM + function FSM:New( FsmT ) + + -- Inherits from BASE + local self = BASE:Inherit( self, BASE:New() ) + + self.options = options or {} + self.options.subs = self.options.subs or {} + self.current = self.options.initial or 'none' + self.Events = {} + self.subs = {} + self.endstates = {} + + self.Scores = {} + + self._StartState = "none" + self._Transitions = {} + self._Processes = {} + self._EndStates = {} + self._Scores = {} + + self.CallScheduler = SCHEDULER:New( self ) + + + return self + end + + + function FSM:SetStartState( State ) + + self._StartState = State + self.current = State + end + + + function FSM:GetStartState() + + return self._StartState or {} + end + + function FSM:AddTransition( From, Event, To ) + + local Transition = {} + Transition.From = From + Transition.Event = Event + Transition.To = To + + self:E( Transition ) + + self._Transitions[Transition] = Transition + self:_eventmap( self.Events, Transition ) + end + + function FSM:GetTransitions() + + return self._Transitions or {} + end + + --- Set the default @{Process} template with key ProcessName providing the ProcessClass and the process object when it is assigned to a @{Controllable} by the task. + -- @return Core.Fsm#FSM_PROCESS + function FSM:AddProcess( From, Event, Process, ReturnEvents ) + self:E( { From, Event, Process, ReturnEvents } ) + + local Sub = {} + Sub.From = From + Sub.Event = Event + Sub.fsm = Process + Sub.StartEvent = "Start" + Sub.ReturnEvents = ReturnEvents + + self._Processes[Sub] = Sub + + self:_submap( self.subs, Sub, nil ) + + self:AddTransition( From, Event, From ) + + return Process + end + + function FSM:GetProcesses() + + return self._Processes or {} + end + + function FSM:GetProcess( From, Event ) + + for ProcessID, Process in pairs( self:GetProcesses() ) do + if Process.From == From and Process.Event == Event then + self:E( Process ) + return Process.fsm + end + end + + error( "Sub-Process from state " .. From .. " with event " .. Event .. " not found!" ) + end + + function FSM:AddEndState( State ) + + self._EndStates[State] = State + self.endstates[State] = State + end + + function FSM:GetEndStates() + + return self._EndStates or {} + end + + + --- Adds a score for the FSM to be achieved. + -- @param #FSM self + -- @param #string State is the state of the process when the score needs to be given. (See the relevant state descriptions of the process). + -- @param #string ScoreText is a text describing the score that is given according the status. + -- @param #number Score is a number providing the score of the status. + -- @return #FSM self + function FSM:AddScore( State, ScoreText, Score ) + self:F2( { State, ScoreText, Score } ) + + self._Scores[State] = self._Scores[State] or {} + self._Scores[State].ScoreText = ScoreText + self._Scores[State].Score = Score + + return self + end + + --- Adds a score for the FSM_PROCESS to be achieved. + -- @param #FSM self + -- @param #string From is the From State of the main process. + -- @param #string Event is the Event of the main process. + -- @param #string State is the state of the process when the score needs to be given. (See the relevant state descriptions of the process). + -- @param #string ScoreText is a text describing the score that is given according the status. + -- @param #number Score is a number providing the score of the status. + -- @return #FSM self + function FSM:AddScoreProcess( From, Event, State, ScoreText, Score ) + self:F2( { Event, State, ScoreText, Score } ) + + local Process = self:GetProcess( From, Event ) + + self:E( { Process = Process._Name, Scores = Process._Scores, State = State, ScoreText = ScoreText, Score = Score } ) + Process._Scores[State] = Process._Scores[State] or {} + Process._Scores[State].ScoreText = ScoreText + Process._Scores[State].Score = Score + + return Process + end + + function FSM:GetScores() + + return self._Scores or {} + end + + + function FSM:GetSubs() + + return self.options.subs + end + + + function FSM:LoadCallBacks( CallBackTable ) + + for name, callback in pairs( CallBackTable or {} ) do + self[name] = callback + end + + end + + function FSM:_eventmap( Events, EventStructure ) + + local Event = EventStructure.Event + local __Event = "__" .. EventStructure.Event + self[Event] = self[Event] or self:_create_transition(Event) + self[__Event] = self[__Event] or self:_delayed_transition(Event) + self:T( "Added methods: " .. Event .. ", " .. __Event ) + Events[Event] = self.Events[Event] or { map = {} } + self:_add_to_map( Events[Event].map, EventStructure ) + + end + + function FSM:_submap( subs, sub, name ) + self:F( { sub = sub, name = name } ) + subs[sub.From] = subs[sub.From] or {} + subs[sub.From][sub.Event] = subs[sub.From][sub.Event] or {} + + -- Make the reference table weak. + -- setmetatable( subs[sub.From][sub.Event], { __mode = "k" } ) + + subs[sub.From][sub.Event][sub] = {} + subs[sub.From][sub.Event][sub].fsm = sub.fsm + subs[sub.From][sub.Event][sub].StartEvent = sub.StartEvent + subs[sub.From][sub.Event][sub].ReturnEvents = sub.ReturnEvents or {} -- these events need to be given to find the correct continue event ... if none given, the processing will stop. + subs[sub.From][sub.Event][sub].name = name + subs[sub.From][sub.Event][sub].fsmparent = self + end + + + function FSM:_call_handler(handler, params) + if self[handler] then + self:E( "Calling " .. handler ) + return self[handler]( self, unpack(params) ) + end + end + + function FSM._handler( self, EventName, ... ) + + self:E( { EventName, ... } ) + + local can, to = self:can( EventName ) + self:E( { EventName, self.current, can, to } ) + + local ReturnValues = nil + + if can then + local from = self.current + local params = { EventName, from, to, ... } + + if self:_call_handler("onbefore" .. EventName, params) == false + or self:_call_handler("onleave" .. from, params) == false then + return false + end + + self.current = to + + local execute = true + + local subtable = self:_gosub( from, EventName ) + for _, sub in pairs( subtable ) do + --if sub.nextevent then + -- self:F2( "nextevent = " .. sub.nextevent ) + -- self[sub.nextevent]( self ) + --end + self:E( "calling sub start event: " .. sub.StartEvent ) + sub.fsm.fsmparent = self + sub.fsm.ReturnEvents = sub.ReturnEvents + sub.fsm[sub.StartEvent]( sub.fsm ) + execute = true + end + + local fsmparent, Event = self:_isendstate( to ) + if fsmparent and Event then + self:F2( { "end state: ", fsmparent, Event } ) + self:_call_handler("onenter" .. to, params) + self:_call_handler("onafter" .. EventName, params) + self:_call_handler("onstatechange", params) + fsmparent[Event]( fsmparent ) + execute = false + end + + if execute then + -- only execute the call if the From state is not equal to the To state! Otherwise this function should never execute! + if from ~= to then + self:T3( { onenter = "onenter" .. to, callback = self["onenter" .. to] } ) + self:_call_handler("onenter" .. to, params) + end + + self:T3( { On = "OnBefore" .. to, callback = self["OnBefore" .. to] } ) + if ( self:_call_handler("OnBefore" .. to, params ) ~= false ) then + + self:T3( { onafter = "onafter" .. EventName, callback = self["onafter" .. EventName] } ) + self:_call_handler("onafter" .. EventName, params) + + self:T3( { On = "OnAfter" .. to, callback = self["OnAfter" .. to] } ) + ReturnValues = self:_call_handler("OnAfter" .. to, params ) + end + + self:_call_handler("onstatechange", params) + end + + return ReturnValues + end + + return nil + end + + function FSM:_delayed_transition( EventName ) + self:E( { EventName = EventName } ) + return function( self, DelaySeconds, ... ) + self:T( "Delayed Event: " .. EventName ) + local CallID = self.CallScheduler:Schedule( self, self._handler, { EventName, ... }, DelaySeconds or 1 ) + self:T( { CallID = CallID } ) + end + end + + function FSM:_create_transition( EventName ) + self:E( { Event = EventName } ) + return function( self, ... ) return self._handler( self, EventName , ... ) end + end + + function FSM:_gosub( ParentFrom, ParentEvent ) + local fsmtable = {} + if self.subs[ParentFrom] and self.subs[ParentFrom][ParentEvent] then + self:E( { ParentFrom, ParentEvent, self.subs[ParentFrom], self.subs[ParentFrom][ParentEvent] } ) + return self.subs[ParentFrom][ParentEvent] + else + return {} + end + end + + function FSM:_isendstate( Current ) + local FSMParent = self.fsmparent + if FSMParent and self.endstates[Current] then + self:E( { state = Current, endstates = self.endstates, endstate = self.endstates[Current] } ) + FSMParent.current = Current + local ParentFrom = FSMParent.current + self:E( ParentFrom ) + self:E( self.ReturnEvents ) + local Event = self.ReturnEvents[Current] + self:E( { ParentFrom, Event, self.ReturnEvents } ) + if Event then + return FSMParent, Event + else + self:E( { "Could not find parent event name for state ", ParentFrom } ) + end + end + + return nil + end + + function FSM:_add_to_map( Map, Event ) + self:F3( { Map, Event } ) + if type(Event.From) == 'string' then + Map[Event.From] = Event.To + else + for _, From in ipairs(Event.From) do + Map[From] = Event.To + end + end + self:T3( { Map, Event } ) + end + + function FSM:GetState() + return self.current + end + + + function FSM:Is( State ) + return self.current == State + end + + function FSM:is(state) + return self.current == state + end + + function FSM:can(e) + self:E( { e, self.Events, self.Events[e] } ) + local Event = self.Events[e] + self:F3( { self.current, Event } ) + local To = Event and Event.map[self.current] or Event.map['*'] + return To ~= nil, To + end + + function FSM:cannot(e) + return not self:can(e) + end + +end + +do -- FSM_CONTROLLABLE + + --- FSM_CONTROLLABLE class + -- @type FSM_CONTROLLABLE + -- @field Wrapper.Controllable#CONTROLLABLE Controllable + -- @extends Core.Fsm#FSM + FSM_CONTROLLABLE = { + ClassName = "FSM_CONTROLLABLE", + } + + --- Creates a new FSM_CONTROLLABLE object. + -- @param #FSM_CONTROLLABLE self + -- @param #table FSMT Finite State Machine Table + -- @param Wrapper.Controllable#CONTROLLABLE Controllable (optional) The CONTROLLABLE object that the FSM_CONTROLLABLE governs. + -- @return #FSM_CONTROLLABLE + function FSM_CONTROLLABLE:New( FSMT, Controllable ) + + -- Inherits from BASE + local self = BASE:Inherit( self, FSM:New( FSMT ) ) -- Core.Fsm#FSM_CONTROLLABLE + + if Controllable then + self:SetControllable( Controllable ) + end + + return self + end + + --- Sets the CONTROLLABLE object that the FSM_CONTROLLABLE governs. + -- @param #FSM_CONTROLLABLE self + -- @param Wrapper.Controllable#CONTROLLABLE FSMControllable + -- @return #FSM_CONTROLLABLE + function FSM_CONTROLLABLE:SetControllable( FSMControllable ) + self:F( FSMControllable ) + self.Controllable = FSMControllable + end + + --- Gets the CONTROLLABLE object that the FSM_CONTROLLABLE governs. + -- @param #FSM_CONTROLLABLE self + -- @return Wrapper.Controllable#CONTROLLABLE + function FSM_CONTROLLABLE:GetControllable() + return self.Controllable + end + + function FSM_CONTROLLABLE:_call_handler( handler, params ) + + local ErrorHandler = function( errmsg ) + + env.info( "Error in SCHEDULER function:" .. errmsg ) + if debug ~= nil then + env.info( debug.traceback() ) + end + + return errmsg + end + + if self[handler] then + self:E( "Calling " .. handler ) + return xpcall( function() return self[handler]( self, self.Controllable, unpack( params ) ) end, ErrorHandler ) + --return self[handler]( self, self.Controllable, unpack( params ) ) + end + end + +end + +do -- FSM_PROCESS + + --- FSM_PROCESS class + -- @type FSM_PROCESS + -- @field Tasking.Task#TASK Task + -- @extends Core.Fsm#FSM_CONTROLLABLE + FSM_PROCESS = { + ClassName = "FSM_PROCESS", + } + + --- Creates a new FSM_PROCESS object. + -- @param #FSM_PROCESS self + -- @return #FSM_PROCESS + function FSM_PROCESS:New( Controllable, Task ) + + local self = BASE:Inherit( self, FSM_CONTROLLABLE:New() ) -- Core.Fsm#FSM_PROCESS + + self:F( Controllable, Task ) + + self:Assign( Controllable, Task ) + + return self + end + + function FSM_PROCESS:Init( FsmProcess ) + self:E( "No Initialisation" ) + end + + --- Creates a new FSM_PROCESS object based on this FSM_PROCESS. + -- @param #FSM_PROCESS self + -- @return #FSM_PROCESS + function FSM_PROCESS:Copy( Controllable, Task ) + self:E( { self:GetClassNameAndID() } ) + + local NewFsm = self:New( Controllable, Task ) -- Core.Fsm#FSM_PROCESS + + NewFsm:Assign( Controllable, Task ) + + -- Polymorphic call to initialize the new FSM_PROCESS based on self FSM_PROCESS + NewFsm:Init( self ) + + -- Set Start State + NewFsm:SetStartState( self:GetStartState() ) + + -- Copy Transitions + for TransitionID, Transition in pairs( self:GetTransitions() ) do + NewFsm:AddTransition( Transition.From, Transition.Event, Transition.To ) + end + + -- Copy Processes + for ProcessID, Process in pairs( self:GetProcesses() ) do + self:E( { Process} ) + local FsmProcess = NewFsm:AddProcess( Process.From, Process.Event, Process.fsm:Copy( Controllable, Task ), Process.ReturnEvents ) + end + + -- Copy End States + for EndStateID, EndState in pairs( self:GetEndStates() ) do + self:E( EndState ) + NewFsm:AddEndState( EndState ) + end + + -- Copy the score tables + for ScoreID, Score in pairs( self:GetScores() ) do + self:E( Score ) + NewFsm:AddScore( ScoreID, Score.ScoreText, Score.Score ) + end + + return NewFsm + end + + --- Sets the task of the process. + -- @param #FSM_PROCESS self + -- @param Tasking.Task#TASK Task + -- @return #FSM_PROCESS + function FSM_PROCESS:SetTask( Task ) + + self.Task = Task + + return self + end + + --- Gets the task of the process. + -- @param #FSM_PROCESS self + -- @return Tasking.Task#TASK + function FSM_PROCESS:GetTask() + + return self.Task + end + + --- Gets the mission of the process. + -- @param #FSM_PROCESS self + -- @return Tasking.Mission#MISSION + function FSM_PROCESS:GetMission() + + return self.Task.Mission + end + + --- Gets the mission of the process. + -- @param #FSM_PROCESS self + -- @return Tasking.CommandCenter#COMMANDCENTER + function FSM_PROCESS:GetCommandCenter() + + return self:GetTask():GetMission():GetCommandCenter() + end + + --- Send a message of the @{Task} to the Group of the Unit. +-- @param #FSM_PROCESS self +function FSM_PROCESS:Message( Message ) + self:F( { Message = Message } ) + + local CC = self:GetCommandCenter() + local TaskGroup = self.Controllable:GetGroup() + + CC:MessageToGroup( Message, TaskGroup ) +end + + + + + --- Assign the process to a @{Unit} and activate the process. + -- @param #FSM_PROCESS self + -- @param Task.Tasking#TASK Task + -- @param Wrapper.Unit#UNIT ProcessUnit + -- @return #FSM_PROCESS self + function FSM_PROCESS:Assign( ProcessUnit, Task ) + self:E( { Task, ProcessUnit } ) + + self:SetControllable( ProcessUnit ) + self:SetTask( Task ) + + --self.ProcessGroup = ProcessUnit:GetGroup() + + return self + end + + --- Adds a score for the FSM_PROCESS to be achieved. + -- @param #FSM_PROCESS self + -- @param #string State is the state of the process when the score needs to be given. (See the relevant state descriptions of the process). + -- @param #string ScoreText is a text describing the score that is given according the status. + -- @param #number Score is a number providing the score of the status. + -- @return #FSM_PROCESS self + function FSM_PROCESS:AddScore( State, ScoreText, Score ) + self:F2( { State, ScoreText, Score } ) + + self.Scores[State] = self.Scores[State] or {} + self.Scores[State].ScoreText = ScoreText + self.Scores[State].Score = Score + + return self + end + + function FSM_PROCESS:onenterAssigned( ProcessUnit ) + self:E( "Assign" ) + + self.Task:Assign() + end + + function FSM_PROCESS:onenterFailed( ProcessUnit ) + self:E( "Failed" ) + + self.Task:Fail() + end + + function FSM_PROCESS:onenterSuccess( ProcessUnit ) + self:E( "Success" ) + + self.Task:Success() + end + + --- StateMachine callback function for a FSM_PROCESS + -- @param #FSM_PROCESS self + -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit + -- @param #string Event + -- @param #string From + -- @param #string To + function FSM_PROCESS:onstatechange( ProcessUnit, Event, From, To, Dummy ) + self:E( { ProcessUnit, Event, From, To, Dummy, self:IsTrace() } ) + + if self:IsTrace() then + MESSAGE:New( "@ Process " .. self:GetClassNameAndID() .. " : " .. Event .. " changed to state " .. To, 2 ):ToAll() + end + + self:E( self.Scores[To] ) + -- TODO: This needs to be reworked with a callback functions allocated within Task, and set within the mission script from the Task Objects... + if self.Scores[To] then + + local Task = self.Task + local Scoring = Task:GetScoring() + if Scoring then + Scoring:_AddMissionTaskScore( Task.Mission, ProcessUnit, self.Scores[To].ScoreText, self.Scores[To].Score ) + end + end + end + +end + +do -- FSM_TASK + + --- FSM_TASK class + -- @type FSM_TASK + -- @field Tasking.Task#TASK Task + -- @extends Core.Fsm#FSM + FSM_TASK = { + ClassName = "FSM_TASK", + } + + --- Creates a new FSM_TASK object. + -- @param #FSM_TASK self + -- @param #table FSMT + -- @param Tasking.Task#TASK Task + -- @param Wrapper.Unit#UNIT TaskUnit + -- @return #FSM_TASK + function FSM_TASK:New( FSMT ) + + local self = BASE:Inherit( self, FSM_CONTROLLABLE:New( FSMT ) ) -- Core.Fsm#FSM_TASK + + self["onstatechange"] = self.OnStateChange + + return self + end + + function FSM_TASK:_call_handler( handler, params ) + if self[handler] then + self:E( "Calling " .. handler ) + return self[handler]( self, unpack( params ) ) + end + end + +end -- FSM_TASK + +do -- FSM_SET + + --- FSM_SET class + -- @type FSM_SET + -- @field Core.Set#SET_BASE Set + -- @extends Core.Fsm#FSM + FSM_SET = { + ClassName = "FSM_SET", + } + + --- Creates a new FSM_SET object. + -- @param #FSM_SET self + -- @param #table FSMT Finite State Machine Table + -- @param Set_SET_BASE FSMSet (optional) The Set object that the FSM_SET governs. + -- @return #FSM_SET + function FSM_SET:New( FSMSet ) + + -- Inherits from BASE + local self = BASE:Inherit( self, FSM:New() ) -- Core.Fsm#FSM_SET + + if FSMSet then + self:Set( FSMSet ) + end + + return self + end + + --- Sets the SET_BASE object that the FSM_SET governs. + -- @param #FSM_SET self + -- @param Core.Set#SET_BASE FSMSet + -- @return #FSM_SET + function FSM_SET:Set( FSMSet ) + self:F( FSMSet ) + self.Set = FSMSet + end + + --- Gets the SET_BASE object that the FSM_SET governs. + -- @param #FSM_SET self + -- @return Core.Set#SET_BASE + function FSM_SET:Get() + return self.Controllable + end + + function FSM_SET:_call_handler( handler, params ) + if self[handler] then + self:E( "Calling " .. handler ) + return self[handler]( self, self.Set, unpack( params ) ) + end + end + +end -- FSM_SET + diff --git a/Moose Development/Moose/Menu.lua b/Moose Development/Moose/Core/Menu.lua similarity index 82% rename from Moose Development/Moose/Menu.lua rename to Moose Development/Moose/Core/Menu.lua index 4e404ca7c..d09aa809e 100644 --- a/Moose Development/Moose/Menu.lua +++ b/Moose Development/Moose/Core/Menu.lua @@ -15,17 +15,17 @@ -- -- ### To manage **main menus**, the classes begin with **MENU_**: -- --- * @{Menu#MENU_MISSION}: Manages main menus for whole mission file. --- * @{Menu#MENU_COALITION}: Manages main menus for whole coalition. --- * @{Menu#MENU_GROUP}: Manages main menus for GROUPs. --- * @{Menu#MENU_CLIENT}: Manages main menus for CLIENTs. This manages menus for units with the skill level "Client". +-- * @{Core.Menu#MENU_MISSION}: Manages main menus for whole mission file. +-- * @{Core.Menu#MENU_COALITION}: Manages main menus for whole coalition. +-- * @{Core.Menu#MENU_GROUP}: Manages main menus for GROUPs. +-- * @{Core.Menu#MENU_CLIENT}: Manages main menus for CLIENTs. This manages menus for units with the skill level "Client". -- -- ### To manage **command menus**, which are menus that allow the player to issue **functions**, the classes begin with **MENU_COMMAND_**: -- --- * @{Menu#MENU_MISSION_COMMAND}: Manages command menus for whole mission file. --- * @{Menu#MENU_COALITION_COMMAND}: Manages command menus for whole coalition. --- * @{Menu#MENU_GROUP_COMMAND}: Manages command menus for GROUPs. --- * @{Menu#MENU_CLIENT_COMMAND}: Manages command menus for CLIENTs. This manages menus for units with the skill level "Client". +-- * @{Core.Menu#MENU_MISSION_COMMAND}: Manages command menus for whole mission file. +-- * @{Core.Menu#MENU_COALITION_COMMAND}: Manages command menus for whole coalition. +-- * @{Core.Menu#MENU_GROUP_COMMAND}: Manages command menus for GROUPs. +-- * @{Core.Menu#MENU_CLIENT_COMMAND}: Manages command menus for CLIENTs. This manages menus for units with the skill level "Client". -- -- === -- @@ -37,11 +37,11 @@ -- These are simply abstract base classes defining a couple of fields that are used by the -- derived MENU_ classes to manage menus. -- --- 1.1) @{Menu#MENU_BASE} class, extends @{Base#BASE} +-- 1.1) @{Core.Menu#MENU_BASE} class, extends @{Core.Base#BASE} -- -------------------------------------------------- -- The @{#MENU_BASE} class defines the main MENU class where other MENU classes are derived from. -- --- 1.2) @{Menu#MENU_COMMAND_BASE} class, extends @{Base#BASE} +-- 1.2) @{Core.Menu#MENU_COMMAND_BASE} class, extends @{Core.Base#BASE} -- ---------------------------------------------------------- -- The @{#MENU_COMMAND_BASE} class defines the main MENU class where other MENU COMMAND_ classes are derived from, in order to set commands. -- @@ -53,15 +53,15 @@ -- ====================== -- The underlying classes manage the menus for a complete mission file. -- --- 2.1) @{Menu#MENU_MISSION} class, extends @{Menu#MENU_BASE} +-- 2.1) @{Menu#MENU_MISSION} class, extends @{Core.Menu#MENU_BASE} -- --------------------------------------------------------- --- The @{Menu#MENU_MISSION} class manages the main menus for a complete mission. +-- The @{Core.Menu#MENU_MISSION} class manages the main menus for a complete mission. -- You can add menus with the @{#MENU_MISSION.New} method, which constructs a MENU_MISSION object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_MISSION.Remove}. -- --- 2.2) @{Menu#MENU_MISSION_COMMAND} class, extends @{Menu#MENU_COMMAND_BASE} +-- 2.2) @{Menu#MENU_MISSION_COMMAND} class, extends @{Core.Menu#MENU_COMMAND_BASE} -- ------------------------------------------------------------------------- --- The @{Menu#MENU_MISSION_COMMAND} class manages the command menus for a complete mission, which allow players to execute functions during mission execution. +-- The @{Core.Menu#MENU_MISSION_COMMAND} class manages the command menus for a complete mission, which allow players to execute functions during mission execution. -- You can add menus with the @{#MENU_MISSION_COMMAND.New} method, which constructs a MENU_MISSION_COMMAND object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_MISSION_COMMAND.Remove}. -- @@ -71,15 +71,15 @@ -- ========================= -- The underlying classes manage the menus for whole coalitions. -- --- 3.1) @{Menu#MENU_COALITION} class, extends @{Menu#MENU_BASE} +-- 3.1) @{Menu#MENU_COALITION} class, extends @{Core.Menu#MENU_BASE} -- ------------------------------------------------------------ --- The @{Menu#MENU_COALITION} class manages the main menus for coalitions. +-- The @{Core.Menu#MENU_COALITION} class manages the main menus for coalitions. -- You can add menus with the @{#MENU_COALITION.New} method, which constructs a MENU_COALITION object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_COALITION.Remove}. -- --- 3.2) @{Menu#MENU_COALITION_COMMAND} class, extends @{Menu#MENU_COMMAND_BASE} +-- 3.2) @{Menu#MENU_COALITION_COMMAND} class, extends @{Core.Menu#MENU_COMMAND_BASE} -- ---------------------------------------------------------------------------- --- The @{Menu#MENU_COALITION_COMMAND} class manages the command menus for coalitions, which allow players to execute functions during mission execution. +-- The @{Core.Menu#MENU_COALITION_COMMAND} class manages the command menus for coalitions, which allow players to execute functions during mission execution. -- You can add menus with the @{#MENU_COALITION_COMMAND.New} method, which constructs a MENU_COALITION_COMMAND object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_COALITION_COMMAND.Remove}. -- @@ -89,15 +89,15 @@ -- ===================== -- The underlying classes manage the menus for groups. Note that groups can be inactive, alive or can be destroyed. -- --- 4.1) @{Menu#MENU_GROUP} class, extends @{Menu#MENU_BASE} +-- 4.1) @{Menu#MENU_GROUP} class, extends @{Core.Menu#MENU_BASE} -- -------------------------------------------------------- --- The @{Menu#MENU_GROUP} class manages the main menus for coalitions. +-- The @{Core.Menu#MENU_GROUP} class manages the main menus for coalitions. -- You can add menus with the @{#MENU_GROUP.New} method, which constructs a MENU_GROUP object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_GROUP.Remove}. -- --- 4.2) @{Menu#MENU_GROUP_COMMAND} class, extends @{Menu#MENU_COMMAND_BASE} +-- 4.2) @{Menu#MENU_GROUP_COMMAND} class, extends @{Core.Menu#MENU_COMMAND_BASE} -- ------------------------------------------------------------------------ --- The @{Menu#MENU_GROUP_COMMAND} class manages the command menus for coalitions, which allow players to execute functions during mission execution. +-- The @{Core.Menu#MENU_GROUP_COMMAND} class manages the command menus for coalitions, which allow players to execute functions during mission execution. -- You can add menus with the @{#MENU_GROUP_COMMAND.New} method, which constructs a MENU_GROUP_COMMAND object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_GROUP_COMMAND.Remove}. -- @@ -107,15 +107,15 @@ -- ====================== -- The underlying classes manage the menus for units with skill level client or player. -- --- 5.1) @{Menu#MENU_CLIENT} class, extends @{Menu#MENU_BASE} +-- 5.1) @{Menu#MENU_CLIENT} class, extends @{Core.Menu#MENU_BASE} -- --------------------------------------------------------- --- The @{Menu#MENU_CLIENT} class manages the main menus for coalitions. +-- The @{Core.Menu#MENU_CLIENT} class manages the main menus for coalitions. -- You can add menus with the @{#MENU_CLIENT.New} method, which constructs a MENU_CLIENT object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_CLIENT.Remove}. -- --- 5.2) @{Menu#MENU_CLIENT_COMMAND} class, extends @{Menu#MENU_COMMAND_BASE} +-- 5.2) @{Menu#MENU_CLIENT_COMMAND} class, extends @{Core.Menu#MENU_COMMAND_BASE} -- ------------------------------------------------------------------------- --- The @{Menu#MENU_CLIENT_COMMAND} class manages the command menus for coalitions, which allow players to execute functions during mission execution. +-- The @{Core.Menu#MENU_CLIENT_COMMAND} class manages the command menus for coalitions, which allow players to execute functions during mission execution. -- You can add menus with the @{#MENU_CLIENT_COMMAND.New} method, which constructs a MENU_CLIENT_COMMAND object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_CLIENT_COMMAND.Remove}. -- @@ -351,7 +351,7 @@ do -- MENU_COALITION --- MENU_COALITION constructor. Creates a new MENU_COALITION object and creates the menu for a complete coalition. -- @param #MENU_COALITION self - -- @param DCSCoalition#coalition.side Coalition The coalition owning the menu. + -- @param Dcs.DCSCoalition#coalition.side Coalition The coalition owning the menu. -- @param #string MenuText The text for the menu. -- @param #table ParentMenu The parent menu. This parameter can be ignored if you want the menu to be located at the perent menu of DCS world (under F10 other). -- @return #MENU_COALITION self @@ -420,7 +420,7 @@ do -- MENU_COALITION_COMMAND --- MENU_COALITION constructor. Creates a new radio command item for a coalition, which can invoke a function with parameters. -- @param #MENU_COALITION_COMMAND self - -- @param DCSCoalition#coalition.side Coalition The coalition owning the menu. + -- @param Dcs.DCSCoalition#coalition.side Coalition The coalition owning the menu. -- @param #string MenuText The text for the menu. -- @param Menu#MENU_COALITION ParentMenu The parent menu. -- @param CommandMenuFunction A function that is called when the menu key is pressed. @@ -493,7 +493,7 @@ do -- MENU_CLIENT -- MenuStatus[MenuClientName]:Remove() -- end -- - -- --- @param Client#CLIENT MenuClient + -- --- @param Wrapper.Client#CLIENT MenuClient -- local function AddStatusMenu( MenuClient ) -- local MenuClientName = MenuClient:GetName() -- -- This would create a menu for the red coalition under the MenuCoalitionRed menu object. @@ -526,7 +526,7 @@ do -- MENU_CLIENT --- MENU_CLIENT constructor. Creates a new radio menu item for a client. -- @param #MENU_CLIENT self - -- @param Client#CLIENT Client The Client owning the menu. + -- @param Wrapper.Client#CLIENT Client The Client owning the menu. -- @param #string MenuText The text for the menu. -- @param #table ParentMenu The parent menu. -- @return #MENU_CLIENT self @@ -618,7 +618,7 @@ do -- MENU_CLIENT --- MENU_CLIENT_COMMAND constructor. Creates a new radio command item for a client, which can invoke a function with parameters. -- @param #MENU_CLIENT_COMMAND self - -- @param Client#CLIENT Client The Client owning the menu. + -- @param Wrapper.Client#CLIENT Client The Client owning the menu. -- @param #string MenuText The text for the menu. -- @param #MENU_BASE ParentMenu The parent menu. -- @param CommandMenuFunction A function that is called when the menu key is pressed. @@ -719,7 +719,7 @@ do -- MenuStatus[MenuGroupName]:Remove() -- end -- - -- --- @param Group#GROUP MenuGroup + -- --- @param Wrapper.Group#GROUP MenuGroup -- local function AddStatusMenu( MenuGroup ) -- local MenuGroupName = MenuGroup:GetName() -- -- This would create a menu for the red coalition under the MenuCoalitionRed menu object. @@ -753,51 +753,41 @@ do --- MENU_GROUP constructor. Creates a new radio menu item for a group. -- @param #MENU_GROUP self - -- @param Group#GROUP MenuGroup The Group owning the menu. + -- @param Wrapper.Group#GROUP MenuGroup The Group owning the menu. -- @param #string MenuText The text for the menu. -- @param #table ParentMenu The parent menu. -- @return #MENU_GROUP self function MENU_GROUP:New( MenuGroup, MenuText, ParentMenu ) - -- Arrange meta tables - local MenuParentPath = {} - if ParentMenu ~= nil then - MenuParentPath = ParentMenu.MenuPath - end - - local self = BASE:Inherit( self, MENU_BASE:New( MenuText, MenuParentPath ) ) - self:F( { MenuGroup, MenuText, ParentMenu } ) - - self.MenuGroup = MenuGroup - self.MenuGroupID = MenuGroup:GetID() - self.MenuParentPath = MenuParentPath - self.MenuText = MenuText - self.ParentMenu = ParentMenu + -- Determine if the menu was not already created and already visible at the group. + -- If it is visible, then return the cached self, otherwise, create self and cache it. - self.Menus = {} - - if not _MENUGROUPS[self.MenuGroupID] then - _MENUGROUPS[self.MenuGroupID] = {} + MenuGroup._Menus = MenuGroup._Menus or {} + local Path = ( ParentMenu and ( table.concat( ParentMenu.MenuPath or {}, "@" ) .. "@" .. MenuText ) ) or MenuText + if MenuGroup._Menus[Path] then + self = MenuGroup._Menus[Path] + else + self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) ) + MenuGroup._Menus[Path] = self + + self.Menus = {} + + self.MenuGroup = MenuGroup + self.Path = Path + self.MenuGroupID = MenuGroup:GetID() + self.MenuText = MenuText + self.ParentMenu = ParentMenu + + self:T( { "Adding Menu ", MenuText, self.MenuParentPath } ) + self.MenuPath = missionCommands.addSubMenuForGroup( self.MenuGroupID, MenuText, self.MenuParentPath ) + + if ParentMenu and ParentMenu.Menus then + ParentMenu.Menus[self.MenuPath] = self + end end - local MenuPath = _MENUGROUPS[self.MenuGroupID] - - self:T( { MenuGroup:GetName(), MenuPath[table.concat(MenuParentPath)], MenuParentPath, MenuText } ) - - local MenuPathID = table.concat(MenuParentPath) .. "/" .. MenuText - if MenuPath[MenuPathID] then - missionCommands.removeItemForGroup( self.MenuGroupID, MenuPath[MenuPathID] ) - end - - self:T( { "Adding for MenuPath ", MenuText, MenuParentPath } ) - self.MenuPath = missionCommands.addSubMenuForGroup( self.MenuGroupID, MenuText, MenuParentPath ) - MenuPath[MenuPathID] = self.MenuPath - - self:T( { self.MenuGroupID, self.MenuPath } ) - - if ParentMenu and ParentMenu.Menus then - ParentMenu.Menus[self.MenuPath] = self - end + self:F( { MenuGroup:GetName(), MenuText, ParentMenu.MenuPath } ) + return self end @@ -817,23 +807,20 @@ do -- @param #MENU_GROUP self -- @return #nil function MENU_GROUP:Remove() - self:F( self.MenuPath ) + self:F( { self.MenuGroupID, self.MenuPath } ) self:RemoveSubMenus() - if not _MENUGROUPS[self.MenuGroupID] then - _MENUGROUPS[self.MenuGroupID] = {} - end + if self.MenuGroup._Menus[self.Path] then + self = self.MenuGroup._Menus[self.Path] - local MenuPath = _MENUGROUPS[self.MenuGroupID] - - if MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] then - MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] = nil - end - - missionCommands.removeItemForGroup( self.MenuGroupID, self.MenuPath ) - if self.ParentMenu then - self.ParentMenu.Menus[self.MenuPath] = nil + missionCommands.removeItemForGroup( self.MenuGroupID, self.MenuPath ) + if self.ParentMenu then + self.ParentMenu.Menus[self.MenuPath] = nil + end + self:E( self.MenuGroup._Menus[self.Path] ) + self.MenuGroup._Menus[self.Path] = nil + self = nil end return nil end @@ -848,7 +835,7 @@ do --- Creates a new radio command item for a group -- @param #MENU_GROUP_COMMAND self - -- @param Group#GROUP MenuGroup The Group owning the menu. + -- @param Wrapper.Group#GROUP MenuGroup The Group owning the menu. -- @param MenuText The text for the menu. -- @param ParentMenu The parent menu. -- @param CommandMenuFunction A function that is called when the menu key is pressed. @@ -856,32 +843,30 @@ do -- @return Menu#MENU_GROUP_COMMAND self function MENU_GROUP_COMMAND:New( MenuGroup, MenuText, ParentMenu, CommandMenuFunction, ... ) - local self = BASE:Inherit( self, MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, arg ) ) - - self.MenuGroup = MenuGroup - self.MenuGroupID = MenuGroup:GetID() - self.MenuText = MenuText - self.ParentMenu = ParentMenu - - if not _MENUGROUPS[self.MenuGroupID] then - _MENUGROUPS[self.MenuGroupID] = {} + MenuGroup._Menus = MenuGroup._Menus or {} + local Path = ( ParentMenu and ( table.concat( ParentMenu.MenuPath or {}, "@" ) .. "@" .. MenuText ) ) or MenuText + if MenuGroup._Menus[Path] then + self = MenuGroup._Menus[Path] + else + self = BASE:Inherit( self, MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, arg ) ) + MenuGroup._Menus[Path] = self + + self.Path = Path + self.MenuGroup = MenuGroup + self.MenuGroupID = MenuGroup:GetID() + self.MenuText = MenuText + self.ParentMenu = ParentMenu + + self:T( { "Adding Command Menu ", MenuText, self.MenuParentPath } ) + self.MenuPath = missionCommands.addCommandForGroup( self.MenuGroupID, MenuText, self.MenuParentPath, self.MenuCallHandler, arg ) + + if ParentMenu and ParentMenu.Menus then + ParentMenu.Menus[self.MenuPath] = self + end end - - local MenuPath = _MENUGROUPS[self.MenuGroupID] - - self:T( { MenuGroup:GetName(), MenuPath[table.concat(self.MenuParentPath)], self.MenuParentPath, MenuText, CommandMenuFunction, arg } ) - - local MenuPathID = table.concat(self.MenuParentPath) .. "/" .. MenuText - if MenuPath[MenuPathID] then - missionCommands.removeItemForGroup( self.MenuGroupID, MenuPath[MenuPathID] ) - end - - self:T( { "Adding for MenuPath ", MenuText, self.MenuParentPath } ) - self.MenuPath = missionCommands.addCommandForGroup( self.MenuGroupID, MenuText, self.MenuParentPath, self.MenuCallHandler, arg ) - MenuPath[MenuPathID] = self.MenuPath - - ParentMenu.Menus[self.MenuPath] = self - + + self:F( { MenuGroup:GetName(), MenuText, ParentMenu.MenuPath } ) + return self end @@ -889,21 +874,18 @@ do -- @param #MENU_GROUP_COMMAND self -- @return #nil function MENU_GROUP_COMMAND:Remove() - self:F( self.MenuPath ) - - if not _MENUGROUPS[self.MenuGroupID] then - _MENUGROUPS[self.MenuGroupID] = {} - end - - local MenuPath = _MENUGROUPS[self.MenuGroupID] + self:F( { self.MenuGroupID, self.MenuPath } ) + if self.MenuGroup._Menus[self.Path] then + self = self.MenuGroup._Menus[self.Path] - if MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] then - MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] = nil + missionCommands.removeItemForGroup( self.MenuGroupID, self.MenuPath ) + self.ParentMenu.Menus[self.MenuPath] = nil + self:E( self.MenuGroup._Menus[self.Path] ) + self.MenuGroup._Menus[self.Path] = nil + self = nil end - missionCommands.removeItemForGroup( self.MenuGroupID, self.MenuPath ) - self.ParentMenu.Menus[self.MenuPath] = nil return nil end diff --git a/Moose Development/Moose/Message.lua b/Moose Development/Moose/Core/Message.lua similarity index 94% rename from Moose Development/Moose/Message.lua rename to Moose Development/Moose/Core/Message.lua index bc44263bc..15a627890 100644 --- a/Moose Development/Moose/Message.lua +++ b/Moose Development/Moose/Core/Message.lua @@ -1,6 +1,6 @@ --- This module contains the MESSAGE class. -- --- 1) @{Message#MESSAGE} class, extends @{Base#BASE} +-- 1) @{Core.Message#MESSAGE} class, extends @{Core.Base#BASE} -- ================================================= -- Message System to display Messages to Clients, Coalitions or All. -- Messages are shown on the display panel for an amount of seconds, and will then disappear. @@ -8,23 +8,23 @@ -- -- 1.1) MESSAGE construction methods -- --------------------------------- --- Messages are created with @{Message#MESSAGE.New}. Note that when the MESSAGE object is created, no message is sent yet. +-- Messages are created with @{Core.Message#MESSAGE.New}. Note that when the MESSAGE object is created, no message is sent yet. -- To send messages, you need to use the To functions. -- -- 1.2) Send messages with MESSAGE To methods -- ------------------------------------------ -- Messages are sent to: -- --- * Clients with @{Message#MESSAGE.ToClient}. --- * Coalitions with @{Message#MESSAGE.ToCoalition}. --- * All Players with @{Message#MESSAGE.ToAll}. +-- * Clients with @{Core.Message#MESSAGE.ToClient}. +-- * Coalitions with @{Core.Message#MESSAGE.ToCoalition}. +-- * All Players with @{Core.Message#MESSAGE.ToAll}. -- -- @module Message -- @author FlightControl --- The MESSAGE class -- @type MESSAGE --- @extends Base#BASE +-- @extends Core.Base#BASE MESSAGE = { ClassName = "MESSAGE", MessageCategory = 0, @@ -54,7 +54,11 @@ function MESSAGE:New( MessageText, MessageDuration, MessageCategory ) -- When no MessageCategory is given, we don't show it as a title... if MessageCategory and MessageCategory ~= "" then - self.MessageCategory = MessageCategory .. ": " + if MessageCategory:sub(-1) ~= "\n" then + self.MessageCategory = MessageCategory .. ": " + else + self.MessageCategory = MessageCategory:sub( 1, -2 ) .. ":\n" + end else self.MessageCategory = "" end @@ -72,7 +76,7 @@ end --- Sends a MESSAGE to a Client Group. Note that the Group needs to be defined within the ME with the skillset "Client" or "Player". -- @param #MESSAGE self --- @param Client#CLIENT Client is the Group of the Client. +-- @param Wrapper.Client#CLIENT Client is the Group of the Client. -- @return #MESSAGE -- @usage -- -- Send the 2 messages created with the @{New} method to the Client Group. @@ -104,7 +108,7 @@ end --- Sends a MESSAGE to a Group. -- @param #MESSAGE self --- @param Group#GROUP Group is the Group. +-- @param Wrapper.Group#GROUP Group is the Group. -- @return #MESSAGE function MESSAGE:ToGroup( Group ) self:F( Group.GroupName ) diff --git a/Moose Development/Moose/Point.lua b/Moose Development/Moose/Core/Point.lua similarity index 71% rename from Moose Development/Moose/Point.lua rename to Moose Development/Moose/Core/Point.lua index de49027cb..4269fb4cc 100644 --- a/Moose Development/Moose/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -1,59 +1,74 @@ --- This module contains the POINT classes. -- --- 1) @{Point#POINT_VEC3} class, extends @{Base#BASE} --- =============================================== --- The @{Point#POINT_VEC3} class defines a 3D point in the simulator. +-- 1) @{Core.Point#POINT_VEC3} class, extends @{Core.Base#BASE} +-- ================================================== +-- The @{Core.Point#POINT_VEC3} class defines a 3D point in the simulator. -- -- **Important Note:** Most of the functions in this section were taken from MIST, and reworked to OO concepts. -- In order to keep the credibility of the the author, I want to emphasize that the of the MIST framework was created by Grimes, who you can find on the Eagle Dynamics Forums. -- -- 1.1) POINT_VEC3 constructor -- --------------------------- --- --- A new POINT instance can be created with: +-- A new POINT_VEC3 instance can be created with: -- -- * @{#POINT_VEC3.New}(): a 3D point. +-- * @{#POINT_VEC3.NewFromVec3}(): a 3D point created from a @{Dcs.DCSTypes#Vec3}. +-- -- --- 2) @{Point#POINT_VEC2} class, extends @{Point#POINT_VEC3} +-- 2) @{Core.Point#POINT_VEC2} class, extends @{Core.Point#POINT_VEC3} -- ========================================================= --- The @{Point#POINT_VEC2} class defines a 2D point in the simulator. The height coordinate (if needed) will be the land height + an optional added height specified. +-- The @{Core.Point#POINT_VEC2} class defines a 2D point in the simulator. The height coordinate (if needed) will be the land height + an optional added height specified. -- -- 2.1) POINT_VEC2 constructor -- --------------------------- --- --- A new POINT instance can be created with: +-- A new POINT_VEC2 instance can be created with: -- --- * @{#POINT_VEC2.New}(): a 2D point. +-- * @{#POINT_VEC2.New}(): a 2D point, taking an additional height parameter. +-- * @{#POINT_VEC2.NewFromVec2}(): a 2D point created from a @{Dcs.DCSTypes#Vec2}. +-- +-- === +-- +-- **API CHANGE HISTORY** +-- ====================== +-- +-- The underlying change log documents the API changes. Please read this carefully. The following notation is used: +-- +-- * **Added** parts are expressed in bold type face. +-- * _Removed_ parts are expressed in italic type face. +-- +-- Hereby the change log: +-- +-- 2016-08-12: POINT_VEC3:**Translate( Distance, Angle )** added. +-- +-- 2016-08-06: Made PointVec3 and Vec3, PointVec2 and Vec2 terminology used in the code consistent. +-- +-- * Replaced method _Point_Vec3() to **Vec3**() where the code manages a Vec3. Replaced all references to the method. +-- * Replaced method _Point_Vec2() to **Vec2**() where the code manages a Vec2. Replaced all references to the method. +-- * Replaced method Random_Point_Vec3() to **RandomVec3**() where the code manages a Vec3. Replaced all references to the method. +-- . +-- === +-- +-- ### Authors: +-- +-- * FlightControl : Design & Programming +-- +-- ### Contributions: -- -- @module Point --- @author FlightControl --- The POINT_VEC3 class -- @type POINT_VEC3 --- @extends Base#BASE +-- @extends Core.Base#BASE -- @field #number x The x coordinate in 3D space. -- @field #number y The y coordinate in 3D space. -- @field #number z The z coordiante in 3D space. --- @field #POINT_VEC3.SmokeColor SmokeColor --- @field #POINT_VEC3.FlareColor FlareColor +-- @field Utilities.Utils#SMOKECOLOR SmokeColor +-- @field Utilities.Utils#FLARECOLOR FlareColor -- @field #POINT_VEC3.RoutePointAltType RoutePointAltType -- @field #POINT_VEC3.RoutePointType RoutePointType -- @field #POINT_VEC3.RoutePointAction RoutePointAction POINT_VEC3 = { ClassName = "POINT_VEC3", - SmokeColor = { - Green = trigger.smokeColor.Green, - Red = trigger.smokeColor.Red, - White = trigger.smokeColor.White, - Orange = trigger.smokeColor.Orange, - Blue = trigger.smokeColor.Blue - }, - FlareColor = { - Green = trigger.flareColor.Green, - Red = trigger.flareColor.Red, - White = trigger.flareColor.White, - Yellow = trigger.flareColor.Yellow - }, Metric = true, RoutePointAltType = { BARO = "BARO", @@ -66,81 +81,70 @@ POINT_VEC3 = { }, } - ---- SmokeColor --- @type POINT_VEC3.SmokeColor --- @field Green --- @field Red --- @field White --- @field Orange --- @field Blue - - - ---- FlareColor --- @type POINT_VEC3.FlareColor --- @field Green --- @field Red --- @field White --- @field Yellow +--- The POINT_VEC2 class +-- @type POINT_VEC2 +-- @extends #POINT_VEC3 +-- @field Dcs.DCSTypes#Distance x The x coordinate in meters. +-- @field Dcs.DCSTypes#Distance y the y coordinate in meters. +POINT_VEC2 = { + ClassName = "POINT_VEC2", +} +do -- POINT_VEC3 --- RoutePoint AltTypes -- @type POINT_VEC3.RoutePointAltType -- @field BARO "BARO" - - --- RoutePoint Types -- @type POINT_VEC3.RoutePointType -- @field TurningPoint "Turning Point" - - --- RoutePoint Actions -- @type POINT_VEC3.RoutePointAction -- @field TurningPoint "Turning Point" - - -- Constructor. --- Create a new POINT_VEC3 object. -- @param #POINT_VEC3 self --- @param DCSTypes#Distance x The x coordinate of the Vec3 point, pointing to the North. --- @param DCSTypes#Distance y The y coordinate of the Vec3 point, pointing Upwards. --- @param DCSTypes#Distance z The z coordinate of the Vec3 point, pointing to the Right. --- @return Point#POINT_VEC3 self +-- @param Dcs.DCSTypes#Distance x The x coordinate of the Vec3 point, pointing to the North. +-- @param Dcs.DCSTypes#Distance y The y coordinate of the Vec3 point, pointing Upwards. +-- @param Dcs.DCSTypes#Distance z The z coordinate of the Vec3 point, pointing to the Right. +-- @return Core.Point#POINT_VEC3 self function POINT_VEC3:New( x, y, z ) local self = BASE:Inherit( self, BASE:New() ) self.x = x self.y = y self.z = z + return self end --- Create a new POINT_VEC3 object from Vec3 coordinates. -- @param #POINT_VEC3 self --- @param DCSTypes#Vec3 Vec3 The Vec3 point. --- @return Point#POINT_VEC3 self +-- @param Dcs.DCSTypes#Vec3 Vec3 The Vec3 point. +-- @return Core.Point#POINT_VEC3 self function POINT_VEC3:NewFromVec3( Vec3 ) - return self:New( Vec3.x, Vec3.y, Vec3.z ) + self = self:New( Vec3.x, Vec3.y, Vec3.z ) + self:F2( self ) + return self end --- Return the coordinates of the POINT_VEC3 in Vec3 format. -- @param #POINT_VEC3 self --- @return DCSTypes#Vec3 The Vec3 coodinate. +-- @return Dcs.DCSTypes#Vec3 The Vec3 coodinate. function POINT_VEC3:GetVec3() return { x = self.x, y = self.y, z = self.z } end --- Return the coordinates of the POINT_VEC3 in Vec2 format. -- @param #POINT_VEC3 self --- @return DCSTypes#Vec2 The Vec2 coodinate. +-- @return Dcs.DCSTypes#Vec2 The Vec2 coodinate. function POINT_VEC3:GetVec2() return { x = self.x, y = self.z } end @@ -187,9 +191,9 @@ end --- Return a random Vec2 within an Outer Radius and optionally NOT within an Inner Radius of the POINT_VEC3. -- @param #POINT_VEC3 self --- @param DCSTypes#Distance OuterRadius --- @param DCSTypes#Distance InnerRadius --- @return DCSTypes#Vec2 Vec2 +-- @param Dcs.DCSTypes#Distance OuterRadius +-- @param Dcs.DCSTypes#Distance InnerRadius +-- @return Dcs.DCSTypes#Vec2 Vec2 function POINT_VEC3:GetRandomVec2InRadius( OuterRadius, InnerRadius ) self:F2( { OuterRadius, InnerRadius } ) @@ -218,8 +222,8 @@ end --- Return a random POINT_VEC2 within an Outer Radius and optionally NOT within an Inner Radius of the POINT_VEC3. -- @param #POINT_VEC3 self --- @param DCSTypes#Distance OuterRadius --- @param DCSTypes#Distance InnerRadius +-- @param Dcs.DCSTypes#Distance OuterRadius +-- @param Dcs.DCSTypes#Distance InnerRadius -- @return #POINT_VEC2 function POINT_VEC3:GetRandomPointVec2InRadius( OuterRadius, InnerRadius ) self:F2( { OuterRadius, InnerRadius } ) @@ -229,9 +233,9 @@ end --- Return a random Vec3 within an Outer Radius and optionally NOT within an Inner Radius of the POINT_VEC3. -- @param #POINT_VEC3 self --- @param DCSTypes#Distance OuterRadius --- @param DCSTypes#Distance InnerRadius --- @return DCSTypes#Vec3 Vec3 +-- @param Dcs.DCSTypes#Distance OuterRadius +-- @param Dcs.DCSTypes#Distance InnerRadius +-- @return Dcs.DCSTypes#Vec3 Vec3 function POINT_VEC3:GetRandomVec3InRadius( OuterRadius, InnerRadius ) local RandomVec2 = self:GetRandomVec2InRadius( OuterRadius, InnerRadius ) @@ -243,8 +247,8 @@ end --- Return a random POINT_VEC3 within an Outer Radius and optionally NOT within an Inner Radius of the POINT_VEC3. -- @param #POINT_VEC3 self --- @param DCSTypes#Distance OuterRadius --- @param DCSTypes#Distance InnerRadius +-- @param Dcs.DCSTypes#Distance OuterRadius +-- @param Dcs.DCSTypes#Distance InnerRadius -- @return #POINT_VEC3 function POINT_VEC3:GetRandomPointVec3InRadius( OuterRadius, InnerRadius ) @@ -255,7 +259,7 @@ end --- Return a direction vector Vec3 from POINT_VEC3 to the POINT_VEC3. -- @param #POINT_VEC3 self -- @param #POINT_VEC3 TargetPointVec3 The target POINT_VEC3. --- @return DCSTypes#Vec3 DirectionVec3 The direction vector in Vec3 format. +-- @return Dcs.DCSTypes#Vec3 DirectionVec3 The direction vector in Vec3 format. function POINT_VEC3:GetDirectionVec3( TargetPointVec3 ) return { x = TargetPointVec3:GetX() - self:GetX(), y = TargetPointVec3:GetY() - self:GetY(), z = TargetPointVec3:GetZ() - self:GetZ() } end @@ -273,7 +277,7 @@ end --- Return a direction in radians from the POINT_VEC3 using a direction vector in Vec3 format. -- @param #POINT_VEC3 self --- @param DCSTypes#Vec3 DirectionVec3 The direction vector in Vec3 format. +-- @param Dcs.DCSTypes#Vec3 DirectionVec3 The direction vector in Vec3 format. -- @return #number DirectionRadians The direction in radians. function POINT_VEC3:GetDirectionRadians( DirectionVec3 ) local DirectionRadians = math.atan2( DirectionVec3.z, DirectionVec3.x ) @@ -287,7 +291,7 @@ end --- Return the 2D distance in meters between the target POINT_VEC3 and the POINT_VEC3. -- @param #POINT_VEC3 self -- @param #POINT_VEC3 TargetPointVec3 The target POINT_VEC3. --- @return DCSTypes#Distance Distance The distance in meters. +-- @return Dcs.DCSTypes#Distance Distance The distance in meters. function POINT_VEC3:Get2DDistance( TargetPointVec3 ) local TargetVec3 = TargetPointVec3:GetVec3() local SourceVec3 = self:GetVec3() @@ -297,7 +301,7 @@ end --- Return the 3D distance in meters between the target POINT_VEC3 and the POINT_VEC3. -- @param #POINT_VEC3 self -- @param #POINT_VEC3 TargetPointVec3 The target POINT_VEC3. --- @return DCSTypes#Distance Distance The distance in meters. +-- @return Dcs.DCSTypes#Distance Distance The distance in meters. function POINT_VEC3:Get3DDistance( TargetPointVec3 ) local TargetVec3 = TargetPointVec3:GetVec3() local SourceVec3 = self:GetVec3() @@ -373,6 +377,20 @@ function POINT_VEC3:IsMetric() return self.Metric end +--- Add a Distance in meters from the POINT_VEC3 horizontal plane, with the given angle, and calculate the new POINT_VEC3. +-- @param #POINT_VEC3 self +-- @param Dcs.DCSTypes#Distance Distance The Distance to be added in meters. +-- @param Dcs.DCSTypes#Angle Angle The Angle in degrees. +-- @return #POINT_VEC3 The new calculated POINT_VEC3. +function POINT_VEC3:Translate( Distance, Angle ) + local SX = self:GetX() + local SZ = self:GetZ() + local Radians = Angle / 180 * math.pi + local TX = Distance * math.cos( Radians ) + SX + local TZ = Distance * math.sin( Radians ) + SZ + + return POINT_VEC3:New( TX, self:GetY(), TZ ) +end @@ -381,7 +399,7 @@ end -- @param #POINT_VEC3.RoutePointAltType AltType The altitude type. -- @param #POINT_VEC3.RoutePointType Type The route point type. -- @param #POINT_VEC3.RoutePointAction Action The route point action. --- @param DCSTypes#Speed Speed Airspeed in km/h. +-- @param Dcs.DCSTypes#Speed Speed Airspeed in km/h. -- @param #boolean SpeedLocked true means the speed is locked. -- @return #table The route point. function POINT_VEC3:RoutePointAir( AltType, Type, Action, Speed, SpeedLocked ) @@ -422,7 +440,7 @@ end --- Build an ground type route point. -- @param #POINT_VEC3 self --- @param DCSTypes#Speed Speed Speed in km/h. +-- @param Dcs.DCSTypes#Speed Speed Speed in km/h. -- @param #POINT_VEC3.RoutePointAction Formation The route point Formation. -- @return #table The route point. function POINT_VEC3:RoutePointGround( Speed, Formation ) @@ -462,7 +480,7 @@ end --- Smokes the point in a color. -- @param #POINT_VEC3 self --- @param Point#POINT_VEC3.SmokeColor SmokeColor +-- @param Utilities.Utils#SMOKECOLOR SmokeColor function POINT_VEC3:Smoke( SmokeColor ) self:F2( { SmokeColor } ) trigger.action.smoke( self:GetVec3(), SmokeColor ) @@ -472,41 +490,41 @@ end -- @param #POINT_VEC3 self function POINT_VEC3:SmokeGreen() self:F2() - self:Smoke( POINT_VEC3.SmokeColor.Green ) + self:Smoke( SMOKECOLOR.Green ) end --- Smoke the POINT_VEC3 Red. -- @param #POINT_VEC3 self function POINT_VEC3:SmokeRed() self:F2() - self:Smoke( POINT_VEC3.SmokeColor.Red ) + self:Smoke( SMOKECOLOR.Red ) end --- Smoke the POINT_VEC3 White. -- @param #POINT_VEC3 self function POINT_VEC3:SmokeWhite() self:F2() - self:Smoke( POINT_VEC3.SmokeColor.White ) + self:Smoke( SMOKECOLOR.White ) end --- Smoke the POINT_VEC3 Orange. -- @param #POINT_VEC3 self function POINT_VEC3:SmokeOrange() self:F2() - self:Smoke( POINT_VEC3.SmokeColor.Orange ) + self:Smoke( SMOKECOLOR.Orange ) end --- Smoke the POINT_VEC3 Blue. -- @param #POINT_VEC3 self function POINT_VEC3:SmokeBlue() self:F2() - self:Smoke( POINT_VEC3.SmokeColor.Blue ) + self:Smoke( SMOKECOLOR.Blue ) end --- Flares the point in a color. -- @param #POINT_VEC3 self --- @param Point#POINT_VEC3.FlareColor --- @param DCSTypes#Azimuth (optional) Azimuth The azimuth of the flare direction. The default azimuth is 0. +-- @param Utilities.Utils#FLARECOLOR FlareColor +-- @param Dcs.DCSTypes#Azimuth (optional) Azimuth The azimuth of the flare direction. The default azimuth is 0. function POINT_VEC3:Flare( FlareColor, Azimuth ) self:F2( { FlareColor } ) trigger.action.signalFlare( self:GetVec3(), FlareColor, Azimuth and Azimuth or 0 ) @@ -514,51 +532,47 @@ end --- Flare the POINT_VEC3 White. -- @param #POINT_VEC3 self --- @param DCSTypes#Azimuth (optional) Azimuth The azimuth of the flare direction. The default azimuth is 0. +-- @param Dcs.DCSTypes#Azimuth (optional) Azimuth The azimuth of the flare direction. The default azimuth is 0. function POINT_VEC3:FlareWhite( Azimuth ) self:F2( Azimuth ) - self:Flare( POINT_VEC3.FlareColor.White, Azimuth ) + self:Flare( FLARECOLOR.White, Azimuth ) end --- Flare the POINT_VEC3 Yellow. -- @param #POINT_VEC3 self --- @param DCSTypes#Azimuth (optional) Azimuth The azimuth of the flare direction. The default azimuth is 0. +-- @param Dcs.DCSTypes#Azimuth (optional) Azimuth The azimuth of the flare direction. The default azimuth is 0. function POINT_VEC3:FlareYellow( Azimuth ) self:F2( Azimuth ) - self:Flare( POINT_VEC3.FlareColor.Yellow, Azimuth ) + self:Flare( FLARECOLOR.Yellow, Azimuth ) end --- Flare the POINT_VEC3 Green. -- @param #POINT_VEC3 self --- @param DCSTypes#Azimuth (optional) Azimuth The azimuth of the flare direction. The default azimuth is 0. +-- @param Dcs.DCSTypes#Azimuth (optional) Azimuth The azimuth of the flare direction. The default azimuth is 0. function POINT_VEC3:FlareGreen( Azimuth ) self:F2( Azimuth ) - self:Flare( POINT_VEC3.FlareColor.Green, Azimuth ) + self:Flare( FLARECOLOR.Green, Azimuth ) end --- Flare the POINT_VEC3 Red. -- @param #POINT_VEC3 self function POINT_VEC3:FlareRed( Azimuth ) self:F2( Azimuth ) - self:Flare( POINT_VEC3.FlareColor.Red, Azimuth ) + self:Flare( FLARECOLOR.Red, Azimuth ) end +end ---- The POINT_VEC2 class --- @type POINT_VEC2 --- @extends Point#POINT_VEC3 --- @field #number x The x coordinate in 2D space. --- @field #number y the y coordinate in 2D space. -POINT_VEC2 = { - ClassName = "POINT_VEC2", - } +do -- POINT_VEC2 ---- Create a new POINT_VEC2 object. + + +--- POINT_VEC2 constructor. -- @param #POINT_VEC2 self --- @param DCSTypes#Distance x The x coordinate of the Vec3 point, pointing to the North. --- @param DCSTypes#Distance y The y coordinate of the Vec3 point, pointing to the Right. --- @param DCSTypes#Distance LandHeightAdd (optional) The default height if required to be evaluated will be the land height of the x, y coordinate. You can specify an extra height to be added to the land height. --- @return Point#POINT_VEC2 +-- @param Dcs.DCSTypes#Distance x The x coordinate of the Vec3 point, pointing to the North. +-- @param Dcs.DCSTypes#Distance y The y coordinate of the Vec3 point, pointing to the Right. +-- @param Dcs.DCSTypes#Distance LandHeightAdd (optional) The default height if required to be evaluated will be the land height of the x, y coordinate. You can specify an extra height to be added to the land height. +-- @return Core.Point#POINT_VEC2 function POINT_VEC2:New( x, y, LandHeightAdd ) local LandHeight = land.getHeight( { ["x"] = x, ["y"] = y } ) @@ -566,15 +580,16 @@ function POINT_VEC2:New( x, y, LandHeightAdd ) LandHeightAdd = LandHeightAdd or 0 LandHeight = LandHeight + LandHeightAdd - local self = BASE:Inherit( self, POINT_VEC3:New( x, LandHeight, y ) ) + self = BASE:Inherit( self, POINT_VEC3:New( x, LandHeight, y ) ) + self:F2( self ) return self end --- Create a new POINT_VEC2 object from Vec2 coordinates. -- @param #POINT_VEC2 self --- @param DCSTypes#Vec2 Vec2 The Vec2 point. --- @return Point#POINT_VEC2 self +-- @param Dcs.DCSTypes#Vec2 Vec2 The Vec2 point. +-- @return Core.Point#POINT_VEC2 self function POINT_VEC2:NewFromVec2( Vec2, LandHeightAdd ) local LandHeight = land.getHeight( Vec2 ) @@ -582,16 +597,16 @@ function POINT_VEC2:NewFromVec2( Vec2, LandHeightAdd ) LandHeightAdd = LandHeightAdd or 0 LandHeight = LandHeight + LandHeightAdd - local self = BASE:Inherit( self, POINT_VEC3:New( Vec2.x, LandHeight, Vec2.y ) ) - self:F2( { Vec2.x, Vec2.y, LandHeightAdd } ) + self = BASE:Inherit( self, POINT_VEC3:New( Vec2.x, LandHeight, Vec2.y ) ) + self:F2( self ) return self end --- Create a new POINT_VEC2 object from Vec3 coordinates. -- @param #POINT_VEC2 self --- @param DCSTypes#Vec3 Vec3 The Vec3 point. --- @return Point#POINT_VEC2 self +-- @param Dcs.DCSTypes#Vec3 Vec3 The Vec3 point. +-- @return Core.Point#POINT_VEC2 self function POINT_VEC2:NewFromVec3( Vec3 ) local self = BASE:Inherit( self, BASE:New() ) @@ -599,8 +614,8 @@ function POINT_VEC2:NewFromVec3( Vec3 ) local LandHeight = land.getHeight( Vec2 ) - local self = BASE:Inherit( self, POINT_VEC3:New( Vec2.x, LandHeight, Vec2.y ) ) - self:F2( { Vec2.x, LandHeight, Vec2.y } ) + self = BASE:Inherit( self, POINT_VEC3:New( Vec2.x, LandHeight, Vec2.y ) ) + self:F2( self ) return self end @@ -643,7 +658,7 @@ end --- Calculate the distance from a reference @{#POINT_VEC2}. -- @param #POINT_VEC2 self -- @param #POINT_VEC2 PointVec2Reference The reference @{#POINT_VEC2}. --- @return DCSTypes#Distance The distance from the reference @{#POINT_VEC2} in meters. +-- @return Dcs.DCSTypes#Distance The distance from the reference @{#POINT_VEC2} in meters. function POINT_VEC2:DistanceFromPointVec2( PointVec2Reference ) self:F2( PointVec2Reference ) @@ -653,10 +668,10 @@ function POINT_VEC2:DistanceFromPointVec2( PointVec2Reference ) return Distance end ---- Calculate the distance from a reference @{DCSTypes#Vec2}. +--- Calculate the distance from a reference @{Dcs.DCSTypes#Vec2}. -- @param #POINT_VEC2 self --- @param DCSTypes#Vec2 Vec2Reference The reference @{DCSTypes#Vec2}. --- @return DCSTypes#Distance The distance from the reference @{DCSTypes#Vec2} in meters. +-- @param Dcs.DCSTypes#Vec2 Vec2Reference The reference @{Dcs.DCSTypes#Vec2}. +-- @return Dcs.DCSTypes#Distance The distance from the reference @{Dcs.DCSTypes#Vec2} in meters. function POINT_VEC2:DistanceFromVec2( Vec2Reference ) self:F2( Vec2Reference ) @@ -676,8 +691,8 @@ end --- Add a Distance in meters from the POINT_VEC2 orthonormal plane, with the given angle, and calculate the new POINT_VEC2. -- @param #POINT_VEC2 self --- @param DCSTypes#Distance Distance The Distance to be added in meters. --- @param DCSTypes#Angle Angle The Angle in degrees. +-- @param Dcs.DCSTypes#Distance Distance The Distance to be added in meters. +-- @param Dcs.DCSTypes#Angle Angle The Angle in degrees. -- @return #POINT_VEC2 The new calculated POINT_VEC2. function POINT_VEC2:Translate( Distance, Angle ) local SX = self:GetX() @@ -689,5 +704,6 @@ function POINT_VEC2:Translate( Distance, Angle ) return POINT_VEC2:New( TX, TY ) end +end diff --git a/Moose Development/Moose/Core/ScheduleDispatcher.lua b/Moose Development/Moose/Core/ScheduleDispatcher.lua new file mode 100644 index 000000000..b34ffd782 --- /dev/null +++ b/Moose Development/Moose/Core/ScheduleDispatcher.lua @@ -0,0 +1,210 @@ +--- This module defines the SCHEDULEDISPATCHER class, which is used by a central object called _SCHEDULEDISPATCHER. +-- +-- === +-- +-- Takes care of the creation and dispatching of scheduled functions for SCHEDULER objects. +-- +-- This class is tricky and needs some thorought explanation. +-- SCHEDULE classes are used to schedule functions for objects, or as persistent objects. +-- The SCHEDULEDISPATCHER class ensures that: +-- +-- - Scheduled functions are planned according the SCHEDULER object parameters. +-- - Scheduled functions are repeated when requested, according the SCHEDULER object parameters. +-- - Scheduled functions are automatically removed when the schedule is finished, according the SCHEDULER object parameters. +-- +-- The SCHEDULEDISPATCHER class will manage SCHEDULER object in memory during garbage collection: +-- - When a SCHEDULER object is not attached to another object (that is, it's first :Schedule() parameter is nil), then the SCHEDULER +-- object is _persistent_ within memory. +-- - When a SCHEDULER object *is* attached to another object, then the SCHEDULER object is _not persistent_ within memory after a garbage collection! +-- The none persistency of SCHEDULERS attached to objects is required to allow SCHEDULER objects to be garbage collectged, when the parent object is also desroyed or nillified and garbage collected. +-- Even when there are pending timer scheduled functions to be executed for the SCHEDULER object, +-- these will not be executed anymore when the SCHEDULER object has been destroyed. +-- +-- The SCHEDULEDISPATCHER allows multiple scheduled functions to be planned and executed for one SCHEDULER object. +-- The SCHEDULER object therefore keeps a table of "CallID's", which are returned after each planning of a new scheduled function by the SCHEDULEDISPATCHER. +-- The SCHEDULER object plans new scheduled functions through the @{Core.Scheduler#SCHEDULER.Schedule}() method. +-- The Schedule() method returns the CallID that is the reference ID for each planned schedule. +-- +-- === +-- +-- === +-- +-- ### Contributions: - +-- ### Authors: FlightControl : Design & Programming +-- +-- @module ScheduleDispatcher + +--- The SCHEDULEDISPATCHER structure +-- @type SCHEDULEDISPATCHER +SCHEDULEDISPATCHER = { + ClassName = "SCHEDULEDISPATCHER", + CallID = 0, +} + +function SCHEDULEDISPATCHER:New() + local self = BASE:Inherit( self, BASE:New() ) + self:F3() + return self +end + +--- Add a Schedule to the ScheduleDispatcher. +-- The development of this method was really tidy. +-- It is constructed as such that a garbage collection is executed on the weak tables, when the Scheduler is nillified. +-- Nothing of this code should be modified without testing it thoroughly. +-- @param #SCHEDULEDISPATCHER self +-- @param Core.Scheduler#SCHEDULER Scheduler +function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleArguments, Start, Repeat, Randomize, Stop ) + self:F2( { Scheduler, ScheduleFunction, ScheduleArguments, Start, Repeat, Randomize, Stop } ) + + self.CallID = self.CallID + 1 + + -- Initialize the ObjectSchedulers array, which is a weakly coupled table. + -- If the object used as the key is nil, then the garbage collector will remove the item from the Functions array. + self.PersistentSchedulers = self.PersistentSchedulers or {} + + -- Initialize the ObjectSchedulers array, which is a weakly coupled table. + -- If the object used as the key is nil, then the garbage collector will remove the item from the Functions array. + self.ObjectSchedulers = self.ObjectSchedulers or setmetatable( {}, { __mode = "v" } ) + + if Scheduler.SchedulerObject then + self.ObjectSchedulers[self.CallID] = Scheduler + self:T3( { self.CallID, self.ObjectSchedulers[self.CallID] } ) + else + self.PersistentSchedulers[self.CallID] = Scheduler + self:T3( { self.CallID, self.PersistentSchedulers[self.CallID] } ) + end + + self.Schedule = self.Schedule or setmetatable( {}, { __mode = "k" } ) + self.Schedule[Scheduler] = {} + self.Schedule[Scheduler][self.CallID] = {} + self.Schedule[Scheduler][self.CallID].Function = ScheduleFunction + self.Schedule[Scheduler][self.CallID].Arguments = ScheduleArguments + self.Schedule[Scheduler][self.CallID].StartTime = timer.getTime() + ( Start or 0 ) + self.Schedule[Scheduler][self.CallID].Start = Start + .001 + self.Schedule[Scheduler][self.CallID].Repeat = Repeat + self.Schedule[Scheduler][self.CallID].Randomize = Randomize + self.Schedule[Scheduler][self.CallID].Stop = Stop + + self:T3( self.Schedule[Scheduler][self.CallID] ) + + self.Schedule[Scheduler][self.CallID].CallHandler = function( CallID ) + self:F2( CallID ) + + local ErrorHandler = function( errmsg ) + env.info( "Error in timer function: " .. errmsg ) + if debug ~= nil then + env.info( debug.traceback() ) + end + return errmsg + end + + local Scheduler = self.ObjectSchedulers[CallID] + if not Scheduler then + Scheduler = self.PersistentSchedulers[CallID] + end + + self:T3( { Scheduler = Scheduler } ) + + if Scheduler then + + local Schedule = self.Schedule[Scheduler][CallID] + + self:T3( { Schedule = Schedule } ) + + local ScheduleObject = Scheduler.SchedulerObject + --local ScheduleObjectName = Scheduler.SchedulerObject:GetNameAndClassID() + local ScheduleFunction = Schedule.Function + local ScheduleArguments = Schedule.Arguments + local Start = Schedule.Start + local Repeat = Schedule.Repeat or 0 + local Randomize = Schedule.Randomize or 0 + local Stop = Schedule.Stop or 0 + local ScheduleID = Schedule.ScheduleID + + local Status, Result + if ScheduleObject then + local function Timer() + return ScheduleFunction( ScheduleObject, unpack( ScheduleArguments ) ) + end + Status, Result = xpcall( Timer, ErrorHandler ) + else + local function Timer() + return ScheduleFunction( unpack( ScheduleArguments ) ) + end + Status, Result = xpcall( Timer, ErrorHandler ) + end + + local CurrentTime = timer.getTime() + local StartTime = CurrentTime + Start + + if Status and (( Result == nil ) or ( Result and Result ~= false ) ) then + if Repeat ~= 0 and ( Stop == 0 ) or ( Stop ~= 0 and CurrentTime <= StartTime + Stop ) then + local ScheduleTime = + CurrentTime + + Repeat + + math.random( + - ( Randomize * Repeat / 2 ), + ( Randomize * Repeat / 2 ) + ) + + 0.01 + self:T3( { Repeat = CallID, CurrentTime, ScheduleTime, ScheduleArguments } ) + return ScheduleTime -- returns the next time the function needs to be called. + else + self:Stop( Scheduler, CallID ) + end + else + self:Stop( Scheduler, CallID ) + end + else + --self:E( "Scheduled obscolete call for CallID: " .. CallID ) + end + + return nil + end + + self:Start( Scheduler, self.CallID ) + + return self.CallID +end + +function SCHEDULEDISPATCHER:RemoveSchedule( Scheduler, CallID ) + self:F2( { Remove = CallID, Scheduler = Scheduler } ) + + if CallID then + self:Stop( Scheduler, CallID ) + self.Schedule[Scheduler][CallID] = nil + end +end + +function SCHEDULEDISPATCHER:Start( Scheduler, CallID ) + self:F2( { Start = CallID, Scheduler = Scheduler } ) + + if CallID then + local Schedule = self.Schedule[Scheduler] + Schedule[CallID].ScheduleID = timer.scheduleFunction( + Schedule[CallID].CallHandler, + CallID, + timer.getTime() + Schedule[CallID].Start + ) + else + for CallID, Schedule in pairs( self.Schedule[Scheduler] ) do + self:Start( Scheduler, CallID ) -- Recursive + end + end +end + +function SCHEDULEDISPATCHER:Stop( Scheduler, CallID ) + self:F2( { Stop = CallID, Scheduler = Scheduler } ) + + if CallID then + local Schedule = self.Schedule[Scheduler] + timer.removeFunction( Schedule[CallID].ScheduleID ) + else + for CallID, Schedule in pairs( self.Schedule[Scheduler] ) do + self:Stop( Scheduler, CallID ) -- Recursive + end + end +end + + + diff --git a/Moose Development/Moose/Core/Scheduler.lua b/Moose Development/Moose/Core/Scheduler.lua new file mode 100644 index 000000000..6047240aa --- /dev/null +++ b/Moose Development/Moose/Core/Scheduler.lua @@ -0,0 +1,157 @@ +--- This module contains the SCHEDULER class. +-- +-- # 1) @{Core.Scheduler#SCHEDULER} class, extends @{Core.Base#BASE} +-- +-- The @{Core.Scheduler#SCHEDULER} class creates schedule. +-- +-- ## 1.1) SCHEDULER constructor +-- +-- The SCHEDULER class is quite easy to use, but note that the New constructor has variable parameters: +-- +-- * @{Core.Scheduler#SCHEDULER.New}( nil ): Setup a new SCHEDULER object, which is persistently executed after garbage collection. +-- * @{Core.Scheduler#SCHEDULER.New}( Object ): Setup a new SCHEDULER object, which is linked to the Object. When the Object is nillified or destroyed, the SCHEDULER object will also be destroyed and stopped after garbage collection. +-- * @{Core.Scheduler#SCHEDULER.New}( nil, Function, FunctionArguments, Start, ... ): Setup a new persistent SCHEDULER object, and start a new schedule for the Function with the defined FunctionArguments according the Start and sequent parameters. +-- * @{Core.Scheduler#SCHEDULER.New}( Object, Function, FunctionArguments, Start, ... ): Setup a new SCHEDULER object, linked to Object, and start a new schedule for the Function with the defined FunctionArguments according the Start and sequent parameters. +-- +-- ## 1.2) SCHEDULER timer stopping and (re-)starting. +-- +-- The SCHEDULER can be stopped and restarted with the following methods: +-- +-- * @{Core.Scheduler#SCHEDULER.Start}(): (Re-)Start the schedules within the SCHEDULER object. If a CallID is provided to :Start(), only the schedule referenced by CallID will be (re-)started. +-- * @{Core.Scheduler#SCHEDULER.Stop}(): Stop the schedules within the SCHEDULER object. If a CallID is provided to :Stop(), then only the schedule referenced by CallID will be stopped. +-- +-- ## 1.3) Create a new schedule +-- +-- With @{Core.Scheduler#SCHEDULER.Schedule}() a new time event can be scheduled. This function is used by the :New() constructor when a new schedule is planned. +-- +-- === +-- +-- ### Contributions: +-- +-- * FlightControl : Concept & Testing +-- +-- ### Authors: +-- +-- * FlightControl : Design & Programming +-- +-- ### Test Missions: +-- +-- * SCH - Scheduler +-- +-- === +-- +-- @module Scheduler + + +--- The SCHEDULER class +-- @type SCHEDULER +-- @field #number ScheduleID the ID of the scheduler. +-- @extends Core.Base#BASE +SCHEDULER = { + ClassName = "SCHEDULER", + Schedules = {}, +} + +--- SCHEDULER constructor. +-- @param #SCHEDULER self +-- @param #table SchedulerObject Specified for which Moose object the timer is setup. If a value of nil is provided, a scheduler will be setup without an object reference. +-- @param #function SchedulerFunction The event function to be called when a timer event occurs. The event function needs to accept the parameters specified in SchedulerArguments. +-- @param #table SchedulerArguments Optional arguments that can be given as part of scheduler. The arguments need to be given as a table { param1, param 2, ... }. +-- @param #number Start Specifies the amount of seconds that will be waited before the scheduling is started, and the event function is called. +-- @param #number Repeat Specifies the interval in seconds when the scheduler will call the event function. +-- @param #number RandomizeFactor Specifies a randomization factor between 0 and 1 to randomize the Repeat. +-- @param #number Stop Specifies the amount of seconds when the scheduler will be stopped. +-- @return #SCHEDULER self +-- @return #number The ScheduleID of the planned schedule. +function SCHEDULER:New( SchedulerObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop ) + local self = BASE:Inherit( self, BASE:New() ) + self:F2( { Start, Repeat, RandomizeFactor, Stop } ) + + local ScheduleID = nil + + if SchedulerFunction then + ScheduleID = self:Schedule( SchedulerObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop ) + end + + return self, ScheduleID +end + +--function SCHEDULER:_Destructor() +-- --self:E("_Destructor") +-- +-- _SCHEDULEDISPATCHER:RemoveSchedule( self.CallID ) +--end + +--- Schedule a new time event. Note that the schedule will only take place if the scheduler is *started*. Even for a single schedule event, the scheduler needs to be started also. +-- @param #SCHEDULER self +-- @param #table SchedulerObject Specified for which Moose object the timer is setup. If a value of nil is provided, a scheduler will be setup without an object reference. +-- @param #function SchedulerFunction The event function to be called when a timer event occurs. The event function needs to accept the parameters specified in SchedulerArguments. +-- @param #table SchedulerArguments Optional arguments that can be given as part of scheduler. The arguments need to be given as a table { param1, param 2, ... }. +-- @param #number Start Specifies the amount of seconds that will be waited before the scheduling is started, and the event function is called. +-- @param #number Repeat Specifies the interval in seconds when the scheduler will call the event function. +-- @param #number RandomizeFactor Specifies a randomization factor between 0 and 1 to randomize the Repeat. +-- @param #number Stop Specifies the amount of seconds when the scheduler will be stopped. +-- @return #number The ScheduleID of the planned schedule. +function SCHEDULER:Schedule( SchedulerObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop ) + self:F2( { Start, Repeat, RandomizeFactor, Stop } ) + self:T3( { SchedulerArguments } ) + + + self.SchedulerObject = SchedulerObject + + local ScheduleID = _SCHEDULEDISPATCHER:AddSchedule( + self, + SchedulerFunction, + SchedulerArguments, + Start, + Repeat, + RandomizeFactor, + Stop + ) + + self.Schedules[#self.Schedules+1] = ScheduleID + + return ScheduleID +end + +--- (Re-)Starts the schedules or a specific schedule if a valid ScheduleID is provided. +-- @param #SCHEDULER self +-- @param #number ScheduleID (optional) The ScheduleID of the planned (repeating) schedule. +function SCHEDULER:Start( ScheduleID ) + self:F3( { ScheduleID } ) + + _SCHEDULEDISPATCHER:Start( self, ScheduleID ) +end + +--- Stops the schedules or a specific schedule if a valid ScheduleID is provided. +-- @param #SCHEDULER self +-- @param #number ScheduleID (optional) The ScheduleID of the planned (repeating) schedule. +function SCHEDULER:Stop( ScheduleID ) + self:F3( { ScheduleID } ) + + _SCHEDULEDISPATCHER:Stop( self, ScheduleID ) +end + +--- Removes a specific schedule if a valid ScheduleID is provided. +-- @param #SCHEDULER self +-- @param #number ScheduleID (optional) The ScheduleID of the planned (repeating) schedule. +function SCHEDULER:Remove( ScheduleID ) + self:F3( { ScheduleID } ) + + _SCHEDULEDISPATCHER:Remove( self, ScheduleID ) +end + + + + + + + + + + + + + + + diff --git a/Moose Development/Moose/Set.lua b/Moose Development/Moose/Core/Set.lua similarity index 91% rename from Moose Development/Moose/Set.lua rename to Moose Development/Moose/Core/Set.lua index afcc0ff2a..ebe26cc70 100644 --- a/Moose Development/Moose/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -2,9 +2,9 @@ -- -- === -- --- 1) @{Set#SET_BASE} class, extends @{Base#BASE} +-- 1) @{Core.Set#SET_BASE} class, extends @{Core.Base#BASE} -- ============================================== --- The @{Set#SET_BASE} class defines the core functions that define a collection of objects. +-- The @{Core.Set#SET_BASE} class defines the core functions that define a collection of objects. -- A SET provides iterators to iterate the SET, but will **temporarily** yield the ForEach interator loop at defined **"intervals"** to the mail simulator loop. -- In this way, large loops can be done while not blocking the simulator main processing loop. -- The default **"yield interval"** is after 10 objects processed. @@ -12,18 +12,18 @@ -- -- 1.1) Add or remove objects from the SET -- --------------------------------------- --- Some key core functions are @{Set#SET_BASE.Add} and @{Set#SET_BASE.Remove} to add or remove objects from the SET in your logic. +-- Some key core functions are @{Core.Set#SET_BASE.Add} and @{Core.Set#SET_BASE.Remove} to add or remove objects from the SET in your logic. -- -- 1.2) Define the SET iterator **"yield interval"** and the **"time interval"** -- ----------------------------------------------------------------------------- --- Modify the iterator intervals with the @{Set#SET_BASE.SetInteratorIntervals} method. +-- Modify the iterator intervals with the @{Core.Set#SET_BASE.SetInteratorIntervals} method. -- You can set the **"yield interval"**, and the **"time interval"**. (See above). -- -- === -- --- 2) @{Set#SET_GROUP} class, extends @{Set#SET_BASE} +-- 2) @{Core.Set#SET_GROUP} class, extends @{Core.Set#SET_BASE} -- ================================================== --- Mission designers can use the @{Set#SET_GROUP} class to build sets of groups belonging to certain: +-- Mission designers can use the @{Core.Set#SET_GROUP} class to build sets of groups belonging to certain: -- -- * Coalitions -- * Categories @@ -38,7 +38,7 @@ -- -- 2.2) Add or Remove GROUP(s) from SET_GROUP: -- ------------------------------------------- --- GROUPS can be added and removed using the @{Set#SET_GROUP.AddGroupsByName} and @{Set#SET_GROUP.RemoveGroupsByName} respectively. +-- GROUPS can be added and removed using the @{Core.Set#SET_GROUP.AddGroupsByName} and @{Core.Set#SET_GROUP.RemoveGroupsByName} respectively. -- These methods take a single GROUP name or an array of GROUP names to be added or removed from SET_GROUP. -- -- 2.3) SET_GROUP filter criteria: @@ -57,7 +57,7 @@ -- -- Planned filter criteria within development are (so these are not yet available): -- --- * @{#SET_GROUP.FilterZones}: Builds the SET_GROUP with the groups within a @{Zone#ZONE}. +-- * @{#SET_GROUP.FilterZones}: Builds the SET_GROUP with the groups within a @{Core.Zone#ZONE}. -- -- 2.4) SET_GROUP iterators: -- ------------------------- @@ -72,9 +72,9 @@ -- -- ==== -- --- 3) @{Set#SET_UNIT} class, extends @{Set#SET_BASE} +-- 3) @{Core.Set#SET_UNIT} class, extends @{Core.Set#SET_BASE} -- =================================================== --- Mission designers can use the @{Set#SET_UNIT} class to build sets of units belonging to certain: +-- Mission designers can use the @{Core.Set#SET_UNIT} class to build sets of units belonging to certain: -- -- * Coalitions -- * Categories @@ -90,7 +90,7 @@ -- -- 3.2) Add or Remove UNIT(s) from SET_UNIT: -- ----------------------------------------- --- UNITs can be added and removed using the @{Set#SET_UNIT.AddUnitsByName} and @{Set#SET_UNIT.RemoveUnitsByName} respectively. +-- UNITs can be added and removed using the @{Core.Set#SET_UNIT.AddUnitsByName} and @{Core.Set#SET_UNIT.RemoveUnitsByName} respectively. -- These methods take a single UNIT name or an array of UNIT names to be added or removed from SET_UNIT. -- -- 3.3) SET_UNIT filter criteria: @@ -110,7 +110,7 @@ -- -- Planned filter criteria within development are (so these are not yet available): -- --- * @{#SET_UNIT.FilterZones}: Builds the SET_UNIT with the units within a @{Zone#ZONE}. +-- * @{#SET_UNIT.FilterZones}: Builds the SET_UNIT with the units within a @{Core.Zone#ZONE}. -- -- 3.4) SET_UNIT iterators: -- ------------------------ @@ -130,9 +130,9 @@ -- -- === -- --- 4) @{Set#SET_CLIENT} class, extends @{Set#SET_BASE} +-- 4) @{Core.Set#SET_CLIENT} class, extends @{Core.Set#SET_BASE} -- =================================================== --- Mission designers can use the @{Set#SET_CLIENT} class to build sets of units belonging to certain: +-- Mission designers can use the @{Core.Set#SET_CLIENT} class to build sets of units belonging to certain: -- -- * Coalitions -- * Categories @@ -148,7 +148,7 @@ -- -- 4.2) Add or Remove CLIENT(s) from SET_CLIENT: -- ----------------------------------------- --- CLIENTs can be added and removed using the @{Set#SET_CLIENT.AddClientsByName} and @{Set#SET_CLIENT.RemoveClientsByName} respectively. +-- CLIENTs can be added and removed using the @{Core.Set#SET_CLIENT.AddClientsByName} and @{Core.Set#SET_CLIENT.RemoveClientsByName} respectively. -- These methods take a single CLIENT name or an array of CLIENT names to be added or removed from SET_CLIENT. -- -- 4.3) SET_CLIENT filter criteria: @@ -168,7 +168,7 @@ -- -- Planned filter criteria within development are (so these are not yet available): -- --- * @{#SET_CLIENT.FilterZones}: Builds the SET_CLIENT with the clients within a @{Zone#ZONE}. +-- * @{#SET_CLIENT.FilterZones}: Builds the SET_CLIENT with the clients within a @{Core.Zone#ZONE}. -- -- 4.4) SET_CLIENT iterators: -- ------------------------ @@ -180,9 +180,9 @@ -- -- ==== -- --- 5) @{Set#SET_AIRBASE} class, extends @{Set#SET_BASE} +-- 5) @{Core.Set#SET_AIRBASE} class, extends @{Core.Set#SET_BASE} -- ==================================================== --- Mission designers can use the @{Set#SET_AIRBASE} class to build sets of airbases optionally belonging to certain: +-- Mission designers can use the @{Core.Set#SET_AIRBASE} class to build sets of airbases optionally belonging to certain: -- -- * Coalitions -- @@ -194,7 +194,7 @@ -- -- 5.2) Add or Remove AIRBASEs from SET_AIRBASE -- -------------------------------------------- --- AIRBASEs can be added and removed using the @{Set#SET_AIRBASE.AddAirbasesByName} and @{Set#SET_AIRBASE.RemoveAirbasesByName} respectively. +-- AIRBASEs can be added and removed using the @{Core.Set#SET_AIRBASE.AddAirbasesByName} and @{Core.Set#SET_AIRBASE.RemoveAirbasesByName} respectively. -- These methods take a single AIRBASE name or an array of AIRBASE names to be added or removed from SET_AIRBASE. -- -- 5.3) SET_AIRBASE filter criteria @@ -218,13 +218,12 @@ -- -- ==== -- --- ### Contributions: --- --- * Mechanist : Concept & Testing --- -- ### Authors: -- -- * FlightControl : Design & Programming +-- +-- ### Contributions: +-- -- -- @module Set @@ -234,7 +233,7 @@ -- @field #table Filter -- @field #table Set -- @field #table List --- @extends Base#BASE +-- @extends Core.Base#BASE SET_BASE = { ClassName = "SET_BASE", Filter = {}, @@ -265,10 +264,10 @@ function SET_BASE:New( Database ) return self end ---- Finds an @{Base#BASE} object based on the object Name. +--- Finds an @{Core.Base#BASE} object based on the object Name. -- @param #SET_BASE self -- @param #string ObjectName --- @return Base#BASE The Object found. +-- @return Core.Base#BASE The Object found. function SET_BASE:_Find( ObjectName ) local ObjectFound = self.Set[ObjectName] @@ -285,11 +284,11 @@ function SET_BASE:GetSet() return self.Set end ---- Adds a @{Base#BASE} object in the @{Set#SET_BASE}, using the Object Name as the index. +--- Adds a @{Core.Base#BASE} object in the @{Core.Set#SET_BASE}, using a given ObjectName as the index. -- @param #SET_BASE self -- @param #string ObjectName --- @param Base#BASE Object --- @return Base#BASE The added BASE Object. +-- @param Core.Base#BASE Object +-- @return Core.Base#BASE The added BASE Object. function SET_BASE:Add( ObjectName, Object ) self:F2( ObjectName ) @@ -311,7 +310,22 @@ function SET_BASE:Add( ObjectName, Object ) end ---- Removes a @{Base#BASE} object from the @{Set#SET_BASE} and derived classes, based on the Object Name. +--- Adds a @{Core.Base#BASE} object in the @{Core.Set#SET_BASE}, using the Object Name as the index. +-- @param #SET_BASE self +-- @param Wrapper.Object#OBJECT Object +-- @return Core.Base#BASE The added BASE Object. +function SET_BASE:AddObject( Object ) + self:F2( Object.ObjectName ) + + self:T( Object.UnitName ) + self:T( Object.ObjectName ) + self:Add( Object.ObjectName, Object ) + +end + + + +--- Removes a @{Core.Base#BASE} object from the @{Core.Set#SET_BASE} and derived classes, based on the Object Name. -- @param #SET_BASE self -- @param #string ObjectName function SET_BASE:Remove( ObjectName ) @@ -350,7 +364,22 @@ function SET_BASE:Remove( ObjectName ) end ---- Retrieves the amount of objects in the @{Set#SET_BASE} and derived classes. +--- Gets a @{Core.Base#BASE} object from the @{Core.Set#SET_BASE} and derived classes, based on the Object Name. +-- @param #SET_BASE self +-- @param #string ObjectName +-- @return Core.Base#BASE +function SET_BASE:Get( ObjectName ) + self:F( ObjectName ) + + local t = self.Set[ObjectName] + + self:T3( { ObjectName, t } ) + + return t + +end + +--- Retrieves the amount of objects in the @{Core.Set#SET_BASE} and derived classes. -- @param #SET_BASE self -- @return #number Count function SET_BASE:Count() @@ -443,10 +472,10 @@ function SET_BASE:FilterStop() return self end ---- Iterate the SET_BASE while identifying the nearest object from a @{Point#POINT_VEC2}. +--- Iterate the SET_BASE while identifying the nearest object from a @{Core.Point#POINT_VEC2}. -- @param #SET_BASE self --- @param Point#POINT_VEC2 PointVec2 A @{Point#POINT_VEC2} object from where to evaluate the closest object in the set. --- @return Base#BASE The closest object. +-- @param Core.Point#POINT_VEC2 PointVec2 A @{Core.Point#POINT_VEC2} object from where to evaluate the closest object in the set. +-- @return Core.Base#BASE The closest object. function SET_BASE:FindNearestObjectFromPointVec2( PointVec2 ) self:F2( PointVec2 ) @@ -497,7 +526,7 @@ end --- Handles the OnBirth event for the Set. -- @param #SET_BASE self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event function SET_BASE:_EventOnBirth( Event ) self:F3( { Event } ) @@ -513,7 +542,7 @@ end --- Handles the OnDead or OnCrash event for alive units set. -- @param #SET_BASE self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event function SET_BASE:_EventOnDeadOrCrash( Event ) self:F3( { Event } ) @@ -527,7 +556,7 @@ end --- Handles the OnPlayerEnterUnit event to fill the active players table (with the unit filter applied). -- @param #SET_BASE self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event function SET_BASE:_EventOnPlayerEnterUnit( Event ) self:F3( { Event } ) @@ -543,7 +572,7 @@ end --- Handles the OnPlayerLeaveUnit event to clean the active players table. -- @param #SET_BASE self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event function SET_BASE:_EventOnPlayerLeaveUnit( Event ) self:F3( { Event } ) @@ -576,6 +605,9 @@ end function SET_BASE:ForEach( IteratorFunction, arg, Set, Function, FunctionArguments ) self:F3( arg ) + Set = Set or self:GetSet() + arg = arg or {} + local function CoRoutine() local Count = 0 for ObjectID, ObjectData in pairs( Set ) do @@ -688,7 +720,7 @@ end --- SET_GROUP class -- @type SET_GROUP --- @extends Set#SET_BASE +-- @extends #SET_BASE SET_GROUP = { ClassName = "SET_GROUP", Filter = { @@ -729,7 +761,7 @@ function SET_GROUP:New() end --- Add GROUP(s) to SET_GROUP. --- @param Set#SET_GROUP self +-- @param Core.Set#SET_GROUP self -- @param #string AddGroupNames A single name or an array of GROUP names. -- @return self function SET_GROUP:AddGroupsByName( AddGroupNames ) @@ -744,8 +776,8 @@ function SET_GROUP:AddGroupsByName( AddGroupNames ) end --- Remove GROUP(s) from SET_GROUP. --- @param Set#SET_GROUP self --- @param Group#GROUP RemoveGroupNames A single name or an array of GROUP names. +-- @param Core.Set#SET_GROUP self +-- @param Wrapper.Group#GROUP RemoveGroupNames A single name or an array of GROUP names. -- @return self function SET_GROUP:RemoveGroupsByName( RemoveGroupNames ) @@ -764,7 +796,7 @@ end --- Finds a Group based on the Group Name. -- @param #SET_GROUP self -- @param #string GroupName --- @return Group#GROUP The found Group. +-- @return Wrapper.Group#GROUP The found Group. function SET_GROUP:FindGroup( GroupName ) local GroupFound = self.Set[GroupName] @@ -865,7 +897,7 @@ end --- Handles the Database to check on an event (birth) that the Object was added in the Database. -- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! -- @param #SET_GROUP self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event -- @return #string The name of the GROUP -- @return #table The GROUP function SET_GROUP:AddInDatabase( Event ) @@ -882,7 +914,7 @@ end --- Handles the Database to check on any event that Object exists in the Database. -- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa! -- @param #SET_GROUP self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event -- @return #string The name of the GROUP -- @return #table The GROUP function SET_GROUP:FindInDatabase( Event ) @@ -905,15 +937,15 @@ end --- Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence completely in a @{Zone}, providing the GROUP and optional parameters to the called function. -- @param #SET_GROUP self --- @param Zone#ZONE ZoneObject The Zone to be tested for. +-- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. -- @param #function IteratorFunction The function that will be called when there is an alive GROUP in the SET_GROUP. The function needs to accept a GROUP parameter. -- @return #SET_GROUP self function SET_GROUP:ForEachGroupCompletelyInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) self:ForEach( IteratorFunction, arg, self.Set, - --- @param Zone#ZONE_BASE ZoneObject - -- @param Group#GROUP GroupObject + --- @param Core.Zone#ZONE_BASE ZoneObject + -- @param Wrapper.Group#GROUP GroupObject function( ZoneObject, GroupObject ) if GroupObject:IsCompletelyInZone( ZoneObject ) then return true @@ -927,15 +959,15 @@ end --- Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence partly in a @{Zone}, providing the GROUP and optional parameters to the called function. -- @param #SET_GROUP self --- @param Zone#ZONE ZoneObject The Zone to be tested for. +-- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. -- @param #function IteratorFunction The function that will be called when there is an alive GROUP in the SET_GROUP. The function needs to accept a GROUP parameter. -- @return #SET_GROUP self function SET_GROUP:ForEachGroupPartlyInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) self:ForEach( IteratorFunction, arg, self.Set, - --- @param Zone#ZONE_BASE ZoneObject - -- @param Group#GROUP GroupObject + --- @param Core.Zone#ZONE_BASE ZoneObject + -- @param Wrapper.Group#GROUP GroupObject function( ZoneObject, GroupObject ) if GroupObject:IsPartlyInZone( ZoneObject ) then return true @@ -949,15 +981,15 @@ end --- Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence not in a @{Zone}, providing the GROUP and optional parameters to the called function. -- @param #SET_GROUP self --- @param Zone#ZONE ZoneObject The Zone to be tested for. +-- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. -- @param #function IteratorFunction The function that will be called when there is an alive GROUP in the SET_GROUP. The function needs to accept a GROUP parameter. -- @return #SET_GROUP self function SET_GROUP:ForEachGroupNotInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) self:ForEach( IteratorFunction, arg, self.Set, - --- @param Zone#ZONE_BASE ZoneObject - -- @param Group#GROUP GroupObject + --- @param Core.Zone#ZONE_BASE ZoneObject + -- @param Wrapper.Group#GROUP GroupObject function( ZoneObject, GroupObject ) if GroupObject:IsNotInZone( ZoneObject ) then return true @@ -998,7 +1030,7 @@ end --- -- @param #SET_GROUP self --- @param Group#GROUP MooseGroup +-- @param Wrapper.Group#GROUP MooseGroup -- @return #SET_GROUP self function SET_GROUP:IsIncludeObject( MooseGroup ) self:F2( MooseGroup ) @@ -1054,7 +1086,7 @@ end --- SET_UNIT class -- @type SET_UNIT --- @extends Set#SET_BASE +-- @extends Core.Set#SET_BASE SET_UNIT = { ClassName = "SET_UNIT", Units = {}, @@ -1126,8 +1158,8 @@ function SET_UNIT:AddUnitsByName( AddUnitNames ) end --- Remove UNIT(s) from SET_UNIT. --- @param Set#SET_UNIT self --- @param Unit#UNIT RemoveUnitNames A single name or an array of UNIT names. +-- @param Core.Set#SET_UNIT self +-- @param Wrapper.Unit#UNIT RemoveUnitNames A single name or an array of UNIT names. -- @return self function SET_UNIT:RemoveUnitsByName( RemoveUnitNames ) @@ -1144,7 +1176,7 @@ end --- Finds a Unit based on the Unit Name. -- @param #SET_UNIT self -- @param #string UnitName --- @return Unit#UNIT The found Unit. +-- @return Wrapper.Unit#UNIT The found Unit. function SET_UNIT:FindUnit( UnitName ) local UnitFound = self.Set[UnitName] @@ -1290,7 +1322,7 @@ end --- Handles the Database to check on an event (birth) that the Object was added in the Database. -- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! -- @param #SET_UNIT self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event -- @return #string The name of the UNIT -- @return #table The UNIT function SET_UNIT:AddInDatabase( Event ) @@ -1307,7 +1339,7 @@ end --- Handles the Database to check on any event that Object exists in the Database. -- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa! -- @param #SET_UNIT self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event -- @return #string The name of the UNIT -- @return #table The UNIT function SET_UNIT:FindInDatabase( Event ) @@ -1331,15 +1363,15 @@ end --- Iterate the SET_UNIT and call an iterator function for each **alive** UNIT presence completely in a @{Zone}, providing the UNIT and optional parameters to the called function. -- @param #SET_UNIT self --- @param Zone#ZONE ZoneObject The Zone to be tested for. +-- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. -- @param #function IteratorFunction The function that will be called when there is an alive UNIT in the SET_UNIT. The function needs to accept a UNIT parameter. -- @return #SET_UNIT self function SET_UNIT:ForEachUnitCompletelyInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) self:ForEach( IteratorFunction, arg, self.Set, - --- @param Zone#ZONE_BASE ZoneObject - -- @param Unit#UNIT UnitObject + --- @param Core.Zone#ZONE_BASE ZoneObject + -- @param Wrapper.Unit#UNIT UnitObject function( ZoneObject, UnitObject ) if UnitObject:IsCompletelyInZone( ZoneObject ) then return true @@ -1353,15 +1385,15 @@ end --- Iterate the SET_UNIT and call an iterator function for each **alive** UNIT presence not in a @{Zone}, providing the UNIT and optional parameters to the called function. -- @param #SET_UNIT self --- @param Zone#ZONE ZoneObject The Zone to be tested for. +-- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. -- @param #function IteratorFunction The function that will be called when there is an alive UNIT in the SET_UNIT. The function needs to accept a UNIT parameter. -- @return #SET_UNIT self function SET_UNIT:ForEachUnitNotInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) self:ForEach( IteratorFunction, arg, self.Set, - --- @param Zone#ZONE_BASE ZoneObject - -- @param Unit#UNIT UnitObject + --- @param Core.Zone#ZONE_BASE ZoneObject + -- @param Wrapper.Unit#UNIT UnitObject function( ZoneObject, UnitObject ) if UnitObject:IsNotInZone( ZoneObject ) then return true @@ -1383,7 +1415,7 @@ function SET_UNIT:GetUnitTypes() local UnitTypes = {} for UnitID, UnitData in pairs( self:GetSet() ) do - local TextUnit = UnitData -- Unit#UNIT + local TextUnit = UnitData -- Wrapper.Unit#UNIT if TextUnit:IsAlive() then local UnitType = TextUnit:GetTypeName() @@ -1428,7 +1460,7 @@ function SET_UNIT:GetUnitThreatLevels() local UnitThreatLevels = {} for UnitID, UnitData in pairs( self:GetSet() ) do - local ThreatUnit = UnitData -- Unit#UNIT + local ThreatUnit = UnitData -- Wrapper.Unit#UNIT if ThreatUnit:IsAlive() then local UnitThreatLevel, UnitThreatLevelText = ThreatUnit:GetThreatLevel() local ThreatUnitName = ThreatUnit:GetName() @@ -1445,14 +1477,14 @@ end --- Returns if the @{Set} has targets having a radar (of a given type). -- @param #SET_UNIT self --- @param DCSUnit#Unit.RadarType RadarType +-- @param Dcs.DCSWrapper.Unit#Unit.RadarType RadarType -- @return #number The amount of radars in the Set with the given type function SET_UNIT:HasRadar( RadarType ) self:F2( RadarType ) local RadarCount = 0 for UnitID, UnitData in pairs( self:GetSet()) do - local UnitSensorTest = UnitData -- Unit#UNIT + local UnitSensorTest = UnitData -- Wrapper.Unit#UNIT local HasSensors if RadarType then HasSensors = UnitSensorTest:HasSensors( Unit.SensorType.RADAR, RadarType ) @@ -1476,7 +1508,7 @@ function SET_UNIT:HasSEAD() local SEADCount = 0 for UnitID, UnitData in pairs( self:GetSet()) do - local UnitSEAD = UnitData -- Unit#UNIT + local UnitSEAD = UnitData -- Wrapper.Unit#UNIT if UnitSEAD:IsAlive() then local UnitSEADAttributes = UnitSEAD:GetDesc().attributes @@ -1500,7 +1532,7 @@ function SET_UNIT:HasGroundUnits() local GroundUnitCount = 0 for UnitID, UnitData in pairs( self:GetSet()) do - local UnitTest = UnitData -- Unit#UNIT + local UnitTest = UnitData -- Wrapper.Unit#UNIT if UnitTest:IsGround() then GroundUnitCount = GroundUnitCount + 1 end @@ -1517,7 +1549,7 @@ function SET_UNIT:HasFriendlyUnits( FriendlyCoalition ) local FriendlyUnitCount = 0 for UnitID, UnitData in pairs( self:GetSet()) do - local UnitTest = UnitData -- Unit#UNIT + local UnitTest = UnitData -- Wrapper.Unit#UNIT if UnitTest:IsFriendly( FriendlyCoalition ) then FriendlyUnitCount = FriendlyUnitCount + 1 end @@ -1556,7 +1588,7 @@ end --- -- @param #SET_UNIT self --- @param Unit#UNIT MUnit +-- @param Wrapper.Unit#UNIT MUnit -- @return #SET_UNIT self function SET_UNIT:IsIncludeObject( MUnit ) self:F2( MUnit ) @@ -1649,7 +1681,7 @@ end --- SET_CLIENT class -- @type SET_CLIENT --- @extends Set#SET_BASE +-- @extends Core.Set#SET_BASE SET_CLIENT = { ClassName = "SET_CLIENT", Clients = {}, @@ -1691,7 +1723,7 @@ function SET_CLIENT:New() end --- Add CLIENT(s) to SET_CLIENT. --- @param Set#SET_CLIENT self +-- @param Core.Set#SET_CLIENT self -- @param #string AddClientNames A single name or an array of CLIENT names. -- @return self function SET_CLIENT:AddClientsByName( AddClientNames ) @@ -1706,8 +1738,8 @@ function SET_CLIENT:AddClientsByName( AddClientNames ) end --- Remove CLIENT(s) from SET_CLIENT. --- @param Set#SET_CLIENT self --- @param Client#CLIENT RemoveClientNames A single name or an array of CLIENT names. +-- @param Core.Set#SET_CLIENT self +-- @param Wrapper.Client#CLIENT RemoveClientNames A single name or an array of CLIENT names. -- @return self function SET_CLIENT:RemoveClientsByName( RemoveClientNames ) @@ -1724,7 +1756,7 @@ end --- Finds a Client based on the Client Name. -- @param #SET_CLIENT self -- @param #string ClientName --- @return Client#CLIENT The found Client. +-- @return Wrapper.Client#CLIENT The found Client. function SET_CLIENT:FindClient( ClientName ) local ClientFound = self.Set[ClientName] @@ -1845,7 +1877,7 @@ end --- Handles the Database to check on an event (birth) that the Object was added in the Database. -- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! -- @param #SET_CLIENT self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event -- @return #string The name of the CLIENT -- @return #table The CLIENT function SET_CLIENT:AddInDatabase( Event ) @@ -1857,7 +1889,7 @@ end --- Handles the Database to check on any event that Object exists in the Database. -- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa! -- @param #SET_CLIENT self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event -- @return #string The name of the CLIENT -- @return #table The CLIENT function SET_CLIENT:FindInDatabase( Event ) @@ -1880,15 +1912,15 @@ end --- Iterate the SET_CLIENT and call an iterator function for each **alive** CLIENT presence completely in a @{Zone}, providing the CLIENT and optional parameters to the called function. -- @param #SET_CLIENT self --- @param Zone#ZONE ZoneObject The Zone to be tested for. +-- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. -- @param #function IteratorFunction The function that will be called when there is an alive CLIENT in the SET_CLIENT. The function needs to accept a CLIENT parameter. -- @return #SET_CLIENT self function SET_CLIENT:ForEachClientInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) self:ForEach( IteratorFunction, arg, self.Set, - --- @param Zone#ZONE_BASE ZoneObject - -- @param Client#CLIENT ClientObject + --- @param Core.Zone#ZONE_BASE ZoneObject + -- @param Wrapper.Client#CLIENT ClientObject function( ZoneObject, ClientObject ) if ClientObject:IsInZone( ZoneObject ) then return true @@ -1902,15 +1934,15 @@ end --- Iterate the SET_CLIENT and call an iterator function for each **alive** CLIENT presence not in a @{Zone}, providing the CLIENT and optional parameters to the called function. -- @param #SET_CLIENT self --- @param Zone#ZONE ZoneObject The Zone to be tested for. +-- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. -- @param #function IteratorFunction The function that will be called when there is an alive CLIENT in the SET_CLIENT. The function needs to accept a CLIENT parameter. -- @return #SET_CLIENT self function SET_CLIENT:ForEachClientNotInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) self:ForEach( IteratorFunction, arg, self.Set, - --- @param Zone#ZONE_BASE ZoneObject - -- @param Client#CLIENT ClientObject + --- @param Core.Zone#ZONE_BASE ZoneObject + -- @param Wrapper.Client#CLIENT ClientObject function( ZoneObject, ClientObject ) if ClientObject:IsNotInZone( ZoneObject ) then return true @@ -1924,7 +1956,7 @@ end --- -- @param #SET_CLIENT self --- @param Client#CLIENT MClient +-- @param Wrapper.Client#CLIENT MClient -- @return #SET_CLIENT self function SET_CLIENT:IsIncludeObject( MClient ) self:F2( MClient ) @@ -2006,7 +2038,7 @@ end --- SET_AIRBASE class -- @type SET_AIRBASE --- @extends Set#SET_BASE +-- @extends Core.Set#SET_BASE SET_AIRBASE = { ClassName = "SET_AIRBASE", Airbases = {}, @@ -2042,7 +2074,7 @@ function SET_AIRBASE:New() end --- Add AIRBASEs to SET_AIRBASE. --- @param Set#SET_AIRBASE self +-- @param Core.Set#SET_AIRBASE self -- @param #string AddAirbaseNames A single name or an array of AIRBASE names. -- @return self function SET_AIRBASE:AddAirbasesByName( AddAirbaseNames ) @@ -2057,8 +2089,8 @@ function SET_AIRBASE:AddAirbasesByName( AddAirbaseNames ) end --- Remove AIRBASEs from SET_AIRBASE. --- @param Set#SET_AIRBASE self --- @param Airbase#AIRBASE RemoveAirbaseNames A single name or an array of AIRBASE names. +-- @param Core.Set#SET_AIRBASE self +-- @param Wrapper.Airbase#AIRBASE RemoveAirbaseNames A single name or an array of AIRBASE names. -- @return self function SET_AIRBASE:RemoveAirbasesByName( RemoveAirbaseNames ) @@ -2075,7 +2107,7 @@ end --- Finds a Airbase based on the Airbase Name. -- @param #SET_AIRBASE self -- @param #string AirbaseName --- @return Airbase#AIRBASE The found Airbase. +-- @return Wrapper.Airbase#AIRBASE The found Airbase. function SET_AIRBASE:FindAirbase( AirbaseName ) local AirbaseFound = self.Set[AirbaseName] @@ -2137,7 +2169,7 @@ end --- Handles the Database to check on an event (birth) that the Object was added in the Database. -- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! -- @param #SET_AIRBASE self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event -- @return #string The name of the AIRBASE -- @return #table The AIRBASE function SET_AIRBASE:AddInDatabase( Event ) @@ -2149,7 +2181,7 @@ end --- Handles the Database to check on any event that Object exists in the Database. -- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa! -- @param #SET_AIRBASE self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event -- @return #string The name of the AIRBASE -- @return #table The AIRBASE function SET_AIRBASE:FindInDatabase( Event ) @@ -2170,10 +2202,10 @@ function SET_AIRBASE:ForEachAirbase( IteratorFunction, ... ) return self end ---- Iterate the SET_AIRBASE while identifying the nearest @{Airbase#AIRBASE} from a @{Point#POINT_VEC2}. +--- Iterate the SET_AIRBASE while identifying the nearest @{Wrapper.Airbase#AIRBASE} from a @{Core.Point#POINT_VEC2}. -- @param #SET_AIRBASE self --- @param Point#POINT_VEC2 PointVec2 A @{Point#POINT_VEC2} object from where to evaluate the closest @{Airbase#AIRBASE}. --- @return Airbase#AIRBASE The closest @{Airbase#AIRBASE}. +-- @param Core.Point#POINT_VEC2 PointVec2 A @{Core.Point#POINT_VEC2} object from where to evaluate the closest @{Wrapper.Airbase#AIRBASE}. +-- @return Wrapper.Airbase#AIRBASE The closest @{Wrapper.Airbase#AIRBASE}. function SET_AIRBASE:FindNearestAirbaseFromPointVec2( PointVec2 ) self:F2( PointVec2 ) @@ -2185,7 +2217,7 @@ end --- -- @param #SET_AIRBASE self --- @param Airbase#AIRBASE MAirbase +-- @param Wrapper.Airbase#AIRBASE MAirbase -- @return #SET_AIRBASE self function SET_AIRBASE:IsIncludeObject( MAirbase ) self:F2( MAirbase ) diff --git a/Moose Development/Moose/Zone.lua b/Moose Development/Moose/Core/Zone.lua similarity index 55% rename from Moose Development/Moose/Zone.lua rename to Moose Development/Moose/Core/Zone.lua index 651185eba..d0b8c635c 100644 --- a/Moose Development/Moose/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -1,4 +1,4 @@ ---- This module contains the ZONE classes, inherited from @{Zone#ZONE_BASE}. +--- This module contains the ZONE classes, inherited from @{Core.Zone#ZONE_BASE}. -- There are essentially two core functions that zones accomodate: -- -- * Test if an object is within the zone boundaries. @@ -7,7 +7,7 @@ -- The object classes are using the zone classes to test the zone boundaries, which can take various forms: -- -- * Test if completely within the zone. --- * Test if partly within the zone (for @{Group#GROUP} objects). +-- * Test if partly within the zone (for @{Wrapper.Group#GROUP} objects). -- * Test if not in the zone. -- * Distance to the nearest intersecting point of the zone. -- * Distance to the center of the zone. @@ -15,53 +15,125 @@ -- -- Each of these ZONE classes have a zone name, and specific parameters defining the zone type: -- --- * @{Zone#ZONE_BASE}: The ZONE_BASE class defining the base for all other zone classes. --- * @{Zone#ZONE_RADIUS}: The ZONE_RADIUS class defined by a zone name, a location and a radius. --- * @{Zone#ZONE}: The ZONE class, defined by the zone name as defined within the Mission Editor. --- * @{Zone#ZONE_UNIT}: The ZONE_UNIT class defines by a zone around a @{Unit#UNIT} with a radius. --- * @{Zone#ZONE_GROUP}: The ZONE_GROUP class defines by a zone around a @{Group#GROUP} with a radius. --- * @{Zone#ZONE_POLYGON}: The ZONE_POLYGON class defines by a sequence of @{Group#GROUP} waypoints within the Mission Editor, forming a polygon. --- --- Each zone implements two polymorphic functions defined in @{Zone#ZONE_BASE}: --- --- * @{#ZONE_BASE.IsPointVec2InZone}: Returns if a location is within the zone. --- * @{#ZONE_BASE.IsPointVec3InZone}: Returns if a point is within the zone. +-- * @{Core.Zone#ZONE_BASE}: The ZONE_BASE class defining the base for all other zone classes. +-- * @{Core.Zone#ZONE_RADIUS}: The ZONE_RADIUS class defined by a zone name, a location and a radius. +-- * @{Core.Zone#ZONE}: The ZONE class, defined by the zone name as defined within the Mission Editor. +-- * @{Core.Zone#ZONE_UNIT}: The ZONE_UNIT class defines by a zone around a @{Wrapper.Unit#UNIT} with a radius. +-- * @{Core.Zone#ZONE_GROUP}: The ZONE_GROUP class defines by a zone around a @{Wrapper.Group#GROUP} with a radius. +-- * @{Core.Zone#ZONE_POLYGON}: The ZONE_POLYGON class defines by a sequence of @{Wrapper.Group#GROUP} waypoints within the Mission Editor, forming a polygon. -- -- === -- --- 1) @{Zone#ZONE_BASE} class, extends @{Base#BASE} +-- 1) @{Core.Zone#ZONE_BASE} class, extends @{Core.Base#BASE} -- ================================================ --- The ZONE_BASE class defining the base for all other zone classes. +-- This class is an abstract BASE class for derived classes, and is not meant to be instantiated. +-- +-- ### 1.1) Each zone has a name: +-- +-- * @{#ZONE_BASE.GetName}(): Returns the name of the zone. +-- +-- ### 1.2) Each zone implements two polymorphic functions defined in @{Core.Zone#ZONE_BASE}: +-- +-- * @{#ZONE_BASE.IsPointVec2InZone}(): Returns if a @{Core.Point#POINT_VEC2} is within the zone. +-- * @{#ZONE_BASE.IsPointVec3InZone}(): Returns if a @{Core.Point#POINT_VEC3} is within the zone. +-- +-- ### 1.3) A zone has a probability factor that can be set to randomize a selection between zones: +-- +-- * @{#ZONE_BASE.SetRandomizeProbability}(): Set the randomization probability of a zone to be selected, taking a value between 0 and 1 ( 0 = 0%, 1 = 100% ) +-- * @{#ZONE_BASE.GetRandomizeProbability}(): Get the randomization probability of a zone to be selected, passing a value between 0 and 1 ( 0 = 0%, 1 = 100% ) +-- * @{#ZONE_BASE.GetZoneMaybe}(): Get the zone taking into account the randomization probability. nil is returned if this zone is not a candidate. +-- +-- ### 1.4) A zone manages Vectors: +-- +-- * @{#ZONE_BASE.GetVec2}(): Returns the @{Dcs.DCSTypes#Vec2} coordinate of the zone. +-- * @{#ZONE_BASE.GetRandomVec2}(): Define a random @{Dcs.DCSTypes#Vec2} within the zone. +-- +-- ### 1.5) A zone has a bounding square: +-- +-- * @{#ZONE_BASE.GetBoundingSquare}(): Get the outer most bounding square of the zone. +-- +-- ### 1.6) A zone can be marked: +-- +-- * @{#ZONE_BASE.SmokeZone}(): Smokes the zone boundaries in a color. +-- * @{#ZONE_BASE.FlareZone}(): Flares the zone boundaries in a color. -- -- === -- --- 2) @{Zone#ZONE_RADIUS} class, extends @{Zone#ZONE_BASE} +-- 2) @{Core.Zone#ZONE_RADIUS} class, extends @{Core.Zone#ZONE_BASE} -- ======================================================= -- The ZONE_RADIUS class defined by a zone name, a location and a radius. +-- This class implements the inherited functions from Core.Zone#ZONE_BASE taking into account the own zone format and properties. +-- +-- ### 2.1) @{Core.Zone#ZONE_RADIUS} constructor: +-- +-- * @{#ZONE_BASE.New}(): Constructor. +-- +-- ### 2.2) Manage the radius of the zone: +-- +-- * @{#ZONE_BASE.SetRadius}(): Sets the radius of the zone. +-- * @{#ZONE_BASE.GetRadius}(): Returns the radius of the zone. +-- +-- ### 2.3) Manage the location of the zone: +-- +-- * @{#ZONE_BASE.SetVec2}(): Sets the @{Dcs.DCSTypes#Vec2} of the zone. +-- * @{#ZONE_BASE.GetVec2}(): Returns the @{Dcs.DCSTypes#Vec2} of the zone. +-- * @{#ZONE_BASE.GetVec3}(): Returns the @{Dcs.DCSTypes#Vec3} of the zone, taking an additional height parameter. -- -- === -- --- 3) @{Zone#ZONE} class, extends @{Zone#ZONE_RADIUS} +-- 3) @{Core.Zone#ZONE} class, extends @{Core.Zone#ZONE_RADIUS} -- ========================================== -- The ZONE class, defined by the zone name as defined within the Mission Editor. +-- This class implements the inherited functions from {Core.Zone#ZONE_RADIUS} taking into account the own zone format and properties. -- -- === -- --- 4) @{Zone#ZONE_UNIT} class, extends @{Zone#ZONE_RADIUS} +-- 4) @{Core.Zone#ZONE_UNIT} class, extends @{Core.Zone#ZONE_RADIUS} -- ======================================================= --- The ZONE_UNIT class defined by a zone around a @{Unit#UNIT} with a radius. +-- The ZONE_UNIT class defined by a zone around a @{Wrapper.Unit#UNIT} with a radius. +-- This class implements the inherited functions from @{Core.Zone#ZONE_RADIUS} taking into account the own zone format and properties. -- -- === -- --- 5) @{Zone#ZONE_GROUP} class, extends @{Zone#ZONE_RADIUS} +-- 5) @{Core.Zone#ZONE_GROUP} class, extends @{Core.Zone#ZONE_RADIUS} -- ======================================================= --- The ZONE_GROUP class defines by a zone around a @{Group#GROUP} with a radius. The current leader of the group defines the center of the zone. +-- The ZONE_GROUP class defines by a zone around a @{Wrapper.Group#GROUP} with a radius. The current leader of the group defines the center of the zone. +-- This class implements the inherited functions from @{Core.Zone#ZONE_RADIUS} taking into account the own zone format and properties. -- -- === -- --- 6) @{Zone#ZONE_POLYGON} class, extends @{Zone#ZONE_BASE} +-- 6) @{Core.Zone#ZONE_POLYGON_BASE} class, extends @{Core.Zone#ZONE_BASE} -- ======================================================== --- The ZONE_POLYGON class defined by a sequence of @{Group#GROUP} waypoints within the Mission Editor, forming a polygon. +-- The ZONE_POLYGON_BASE class defined by a sequence of @{Wrapper.Group#GROUP} waypoints within the Mission Editor, forming a polygon. +-- This class implements the inherited functions from @{Core.Zone#ZONE_RADIUS} taking into account the own zone format and properties. +-- This class is an abstract BASE class for derived classes, and is not meant to be instantiated. +-- +-- === +-- +-- 7) @{Core.Zone#ZONE_POLYGON} class, extends @{Core.Zone#ZONE_POLYGON_BASE} +-- ================================================================ +-- The ZONE_POLYGON class defined by a sequence of @{Wrapper.Group#GROUP} waypoints within the Mission Editor, forming a polygon. +-- This class implements the inherited functions from @{Core.Zone#ZONE_RADIUS} taking into account the own zone format and properties. +-- +-- ==== +-- +-- **API CHANGE HISTORY** +-- ====================== +-- +-- The underlying change log documents the API changes. Please read this carefully. The following notation is used: +-- +-- * **Added** parts are expressed in bold type face. +-- * _Removed_ parts are expressed in italic type face. +-- +-- Hereby the change log: +-- +-- 2016-08-15: ZONE_BASE:**GetName()** added. +-- +-- 2016-08-15: ZONE_BASE:**SetZoneProbability( ZoneProbability )** added. +-- +-- 2016-08-15: ZONE_BASE:**GetZoneProbability()** added. +-- +-- 2016-08-15: ZONE_BASE:**GetZoneMaybe()** added. -- -- === -- @@ -72,18 +144,21 @@ --- The ZONE_BASE class -- @type ZONE_BASE -- @field #string ZoneName Name of the zone. --- @extends Base#BASE +-- @field #number ZoneProbability A value between 0 and 1. 0 = 0% and 1 = 100% probability. +-- @extends Core.Base#BASE ZONE_BASE = { ClassName = "ZONE_BASE", + ZoneName = "", + ZoneProbability = 1, } --- The ZONE_BASE.BoundingSquare -- @type ZONE_BASE.BoundingSquare --- @field DCSTypes#Distance x1 The lower x coordinate (left down) --- @field DCSTypes#Distance y1 The lower y coordinate (left down) --- @field DCSTypes#Distance x2 The higher x coordinate (right up) --- @field DCSTypes#Distance y2 The higher y coordinate (right up) +-- @field Dcs.DCSTypes#Distance x1 The lower x coordinate (left down) +-- @field Dcs.DCSTypes#Distance y1 The lower y coordinate (left down) +-- @field Dcs.DCSTypes#Distance x2 The higher x coordinate (right up) +-- @field Dcs.DCSTypes#Distance y2 The higher y coordinate (right up) --- ZONE_BASE constructor @@ -99,9 +174,17 @@ function ZONE_BASE:New( ZoneName ) return self end +--- Returns the name of the zone. +-- @param #ZONE_BASE self +-- @return #string The name of the zone. +function ZONE_BASE:GetName() + self:F2() + + return self.ZoneName +end --- Returns if a location is within the zone. -- @param #ZONE_BASE self --- @param DCSTypes#Vec2 Vec2 The location to test. +-- @param Dcs.DCSTypes#Vec2 Vec2 The location to test. -- @return #boolean true if the location is within the zone. function ZONE_BASE:IsPointVec2InZone( Vec2 ) self:F2( Vec2 ) @@ -111,7 +194,7 @@ end --- Returns if a point is within the zone. -- @param #ZONE_BASE self --- @param DCSTypes#Vec3 Vec3 The point to test. +-- @param Dcs.DCSTypes#Vec3 Vec3 The point to test. -- @return #boolean true if the point is within the zone. function ZONE_BASE:IsPointVec3InZone( Vec3 ) self:F2( Vec3 ) @@ -121,7 +204,7 @@ function ZONE_BASE:IsPointVec3InZone( Vec3 ) return InZone end ---- Returns the Vec2 coordinate of the zone. +--- Returns the @{Dcs.DCSTypes#Vec2} coordinate of the zone. -- @param #ZONE_BASE self -- @return #nil. function ZONE_BASE:GetVec2() @@ -129,9 +212,9 @@ function ZONE_BASE:GetVec2() return nil end ---- Define a random @{DCSTypes#Vec2} within the zone. +--- Define a random @{Dcs.DCSTypes#Vec2} within the zone. -- @param #ZONE_BASE self --- @return #nil The Vec2 coordinates. +-- @return Dcs.DCSTypes#Vec2 The Vec2 coordinates. function ZONE_BASE:GetRandomVec2() return nil end @@ -147,27 +230,61 @@ end --- Smokes the zone boundaries in a color. -- @param #ZONE_BASE self --- @param SmokeColor The smoke color. +-- @param Utilities.Utils#SMOKECOLOR SmokeColor The smoke color. function ZONE_BASE:SmokeZone( SmokeColor ) self:F2( SmokeColor ) end +--- Set the randomization probability of a zone to be selected. +-- @param #ZONE_BASE self +-- @param ZoneProbability A value between 0 and 1. 0 = 0% and 1 = 100% probability. +function ZONE_BASE:SetZoneProbability( ZoneProbability ) + self:F2( ZoneProbability ) + + self.ZoneProbability = ZoneProbability or 1 + return self +end + +--- Get the randomization probability of a zone to be selected. +-- @param #ZONE_BASE self +-- @return #number A value between 0 and 1. 0 = 0% and 1 = 100% probability. +function ZONE_BASE:GetZoneProbability() + self:F2() + + return self.ZoneProbability +end + +--- Get the zone taking into account the randomization probability of a zone to be selected. +-- @param #ZONE_BASE self +-- @return #ZONE_BASE The zone is selected taking into account the randomization probability factor. +-- @return #nil The zone is not selected taking into account the randomization probability factor. +function ZONE_BASE:GetZoneMaybe() + self:F2() + + local Randomization = math.random() + if Randomization <= self.ZoneProbability then + return self + else + return nil + end +end + --- The ZONE_RADIUS class, defined by a zone name, a location and a radius. -- @type ZONE_RADIUS --- @field DCSTypes#Vec2 Vec2 The current location of the zone. --- @field DCSTypes#Distance Radius The radius of the zone. --- @extends Zone#ZONE_BASE +-- @field Dcs.DCSTypes#Vec2 Vec2 The current location of the zone. +-- @field Dcs.DCSTypes#Distance Radius The radius of the zone. +-- @extends Core.Zone#ZONE_BASE ZONE_RADIUS = { ClassName="ZONE_RADIUS", } ---- Constructor of ZONE_RADIUS, taking the zone name, the zone location and a radius. +--- Constructor of @{#ZONE_RADIUS}, taking the zone name, the zone location and a radius. -- @param #ZONE_RADIUS self -- @param #string ZoneName Name of the zone. --- @param DCSTypes#Vec2 Vec2 The location of the zone. --- @param DCSTypes#Distance Radius The radius of the zone. +-- @param Dcs.DCSTypes#Vec2 Vec2 The location of the zone. +-- @param Dcs.DCSTypes#Distance Radius The radius of the zone. -- @return #ZONE_RADIUS self function ZONE_RADIUS:New( ZoneName, Vec2, Radius ) local self = BASE:Inherit( self, ZONE_BASE:New( ZoneName ) ) @@ -181,7 +298,7 @@ end --- Smokes the zone boundaries in a color. -- @param #ZONE_RADIUS self --- @param #POINT_VEC3.SmokeColor SmokeColor The smoke color. +-- @param Utilities.Utils#SMOKECOLOR SmokeColor The smoke color. -- @param #number Points (optional) The amount of points in the circle. -- @return #ZONE_RADIUS self function ZONE_RADIUS:SmokeZone( SmokeColor, Points ) @@ -208,9 +325,9 @@ end --- Flares the zone boundaries in a color. -- @param #ZONE_RADIUS self --- @param #POINT_VEC3.FlareColor FlareColor The flare color. +-- @param Utilities.Utils#FLARECOLOR FlareColor The flare color. -- @param #number Points (optional) The amount of points in the circle. --- @param DCSTypes#Azimuth Azimuth (optional) Azimuth The azimuth of the flare. +-- @param Dcs.DCSTypes#Azimuth Azimuth (optional) Azimuth The azimuth of the flare. -- @return #ZONE_RADIUS self function ZONE_RADIUS:FlareZone( FlareColor, Points, Azimuth ) self:F2( { FlareColor, Azimuth } ) @@ -235,7 +352,7 @@ end --- Returns the radius of the zone. -- @param #ZONE_RADIUS self --- @return DCSTypes#Distance The radius of the zone. +-- @return Dcs.DCSTypes#Distance The radius of the zone. function ZONE_RADIUS:GetRadius() self:F2( self.ZoneName ) @@ -246,8 +363,8 @@ end --- Sets the radius of the zone. -- @param #ZONE_RADIUS self --- @param DCSTypes#Distance Radius The radius of the zone. --- @return DCSTypes#Distance The radius of the zone. +-- @param Dcs.DCSTypes#Distance Radius The radius of the zone. +-- @return Dcs.DCSTypes#Distance The radius of the zone. function ZONE_RADIUS:SetRadius( Radius ) self:F2( self.ZoneName ) @@ -257,9 +374,9 @@ function ZONE_RADIUS:SetRadius( Radius ) return self.Radius end ---- Returns the location of the zone. +--- Returns the @{Dcs.DCSTypes#Vec2} of the zone. -- @param #ZONE_RADIUS self --- @return DCSTypes#Vec2 The location of the zone. +-- @return Dcs.DCSTypes#Vec2 The location of the zone. function ZONE_RADIUS:GetVec2() self:F2( self.ZoneName ) @@ -268,11 +385,11 @@ function ZONE_RADIUS:GetVec2() return self.Vec2 end ---- Sets the location of the zone. +--- Sets the @{Dcs.DCSTypes#Vec2} of the zone. -- @param #ZONE_RADIUS self --- @param DCSTypes#Vec2 Vec2 The new location of the zone. --- @return DCSTypes#Vec2 The new location of the zone. -function ZONE_RADIUS:SetPointVec2( Vec2 ) +-- @param Dcs.DCSTypes#Vec2 Vec2 The new location of the zone. +-- @return Dcs.DCSTypes#Vec2 The new location of the zone. +function ZONE_RADIUS:SetVec2( Vec2 ) self:F2( self.ZoneName ) self.Vec2 = Vec2 @@ -282,10 +399,10 @@ function ZONE_RADIUS:SetPointVec2( Vec2 ) return self.Vec2 end ---- Returns the @{DCSTypes#Vec3} of the ZONE_RADIUS. +--- Returns the @{Dcs.DCSTypes#Vec3} of the ZONE_RADIUS. -- @param #ZONE_RADIUS self --- @param DCSTypes#Distance Height The height to add to the land height where the center of the zone is located. --- @return DCSTypes#Vec3 The point of the zone. +-- @param Dcs.DCSTypes#Distance Height The height to add to the land height where the center of the zone is located. +-- @return Dcs.DCSTypes#Vec3 The point of the zone. function ZONE_RADIUS:GetVec3( Height ) self:F2( { self.ZoneName, Height } ) @@ -302,7 +419,7 @@ end --- Returns if a location is within the zone. -- @param #ZONE_RADIUS self --- @param DCSTypes#Vec2 Vec2 The location to test. +-- @param Dcs.DCSTypes#Vec2 Vec2 The location to test. -- @return #boolean true if the location is within the zone. function ZONE_RADIUS:IsPointVec2InZone( Vec2 ) self:F2( Vec2 ) @@ -320,7 +437,7 @@ end --- Returns if a point is within the zone. -- @param #ZONE_RADIUS self --- @param DCSTypes#Vec3 Vec3 The point to test. +-- @param Dcs.DCSTypes#Vec3 Vec3 The point to test. -- @return #boolean true if the point is within the zone. function ZONE_RADIUS:IsPointVec3InZone( Vec3 ) self:F2( Vec3 ) @@ -332,7 +449,7 @@ end --- Returns a random location within the zone. -- @param #ZONE_RADIUS self --- @return DCSTypes#Vec2 The random location within the zone. +-- @return Dcs.DCSTypes#Vec2 The random location within the zone. function ZONE_RADIUS:GetRandomVec2() self:F( self.ZoneName ) @@ -352,7 +469,7 @@ end --- The ZONE class, defined by the zone name as defined within the Mission Editor. The location and the radius are automatically collected from the mission settings. -- @type ZONE --- @extends Zone#ZONE_RADIUS +-- @extends Core.Zone#ZONE_RADIUS ZONE = { ClassName="ZONE", } @@ -380,10 +497,10 @@ function ZONE:New( ZoneName ) end ---- The ZONE_UNIT class defined by a zone around a @{Unit#UNIT} with a radius. +--- The ZONE_UNIT class defined by a zone around a @{Wrapper.Unit#UNIT} with a radius. -- @type ZONE_UNIT --- @field Unit#UNIT ZoneUNIT --- @extends Zone#ZONE_RADIUS +-- @field Wrapper.Unit#UNIT ZoneUNIT +-- @extends Core.Zone#ZONE_RADIUS ZONE_UNIT = { ClassName="ZONE_UNIT", } @@ -391,8 +508,8 @@ ZONE_UNIT = { --- Constructor to create a ZONE_UNIT instance, taking the zone name, a zone unit and a radius. -- @param #ZONE_UNIT self -- @param #string ZoneName Name of the zone. --- @param Unit#UNIT ZoneUNIT The unit as the center of the zone. --- @param DCSTypes#Distance Radius The radius of the zone. +-- @param Wrapper.Unit#UNIT ZoneUNIT The unit as the center of the zone. +-- @param Dcs.DCSTypes#Distance Radius The radius of the zone. -- @return #ZONE_UNIT self function ZONE_UNIT:New( ZoneName, ZoneUNIT, Radius ) local self = BASE:Inherit( self, ZONE_RADIUS:New( ZoneName, ZoneUNIT:GetVec2(), Radius ) ) @@ -405,9 +522,9 @@ function ZONE_UNIT:New( ZoneName, ZoneUNIT, Radius ) end ---- Returns the current location of the @{Unit#UNIT}. +--- Returns the current location of the @{Wrapper.Unit#UNIT}. -- @param #ZONE_UNIT self --- @return DCSTypes#Vec2 The location of the zone based on the @{Unit#UNIT}location. +-- @return Dcs.DCSTypes#Vec2 The location of the zone based on the @{Wrapper.Unit#UNIT}location. function ZONE_UNIT:GetVec2() self:F( self.ZoneName ) @@ -426,29 +543,30 @@ end --- Returns a random location within the zone. -- @param #ZONE_UNIT self --- @return DCSTypes#Vec2 The random location within the zone. +-- @return Dcs.DCSTypes#Vec2 The random location within the zone. function ZONE_UNIT:GetRandomVec2() self:F( self.ZoneName ) - local Point = {} - local PointVec2 = self.ZoneUNIT:GetPointVec2() - if not PointVec2 then - PointVec2 = self.LastVec2 + local RandomVec2 = {} + local Vec2 = self.ZoneUNIT:GetVec2() + + if not Vec2 then + Vec2 = self.LastVec2 end local angle = math.random() * math.pi*2; - Point.x = PointVec2.x + math.cos( angle ) * math.random() * self:GetRadius(); - Point.y = PointVec2.y + math.sin( angle ) * math.random() * self:GetRadius(); + RandomVec2.x = Vec2.x + math.cos( angle ) * math.random() * self:GetRadius(); + RandomVec2.y = Vec2.y + math.sin( angle ) * math.random() * self:GetRadius(); - self:T( { Point } ) + self:T( { RandomVec2 } ) - return Point + return RandomVec2 end ---- Returns the @{DCSTypes#Vec3} of the ZONE_UNIT. +--- Returns the @{Dcs.DCSTypes#Vec3} of the ZONE_UNIT. -- @param #ZONE_UNIT self --- @param DCSTypes#Distance Height The height to add to the land height where the center of the zone is located. --- @return DCSTypes#Vec3 The point of the zone. +-- @param Dcs.DCSTypes#Distance Height The height to add to the land height where the center of the zone is located. +-- @return Dcs.DCSTypes#Vec3 The point of the zone. function ZONE_UNIT:GetVec3( Height ) self:F2( self.ZoneName ) @@ -465,17 +583,17 @@ end --- The ZONE_GROUP class defined by a zone around a @{Group}, taking the average center point of all the units within the Group, with a radius. -- @type ZONE_GROUP --- @field Group#GROUP ZoneGROUP --- @extends Zone#ZONE_RADIUS +-- @field Wrapper.Group#GROUP ZoneGROUP +-- @extends Core.Zone#ZONE_RADIUS ZONE_GROUP = { ClassName="ZONE_GROUP", } ---- Constructor to create a ZONE_GROUP instance, taking the zone name, a zone @{Group#GROUP} and a radius. +--- Constructor to create a ZONE_GROUP instance, taking the zone name, a zone @{Wrapper.Group#GROUP} and a radius. -- @param #ZONE_GROUP self -- @param #string ZoneName Name of the zone. --- @param Group#GROUP ZoneGROUP The @{Group} as the center of the zone. --- @param DCSTypes#Distance Radius The radius of the zone. +-- @param Wrapper.Group#GROUP ZoneGROUP The @{Group} as the center of the zone. +-- @param Dcs.DCSTypes#Distance Radius The radius of the zone. -- @return #ZONE_GROUP self function ZONE_GROUP:New( ZoneName, ZoneGROUP, Radius ) local self = BASE:Inherit( self, ZONE_RADIUS:New( ZoneName, ZoneGROUP:GetVec2(), Radius ) ) @@ -489,7 +607,7 @@ end --- Returns the current location of the @{Group}. -- @param #ZONE_GROUP self --- @return DCSTypes#Vec2 The location of the zone based on the @{Group} location. +-- @return Dcs.DCSTypes#Vec2 The location of the zone based on the @{Group} location. function ZONE_GROUP:GetVec2() self:F( self.ZoneName ) @@ -502,7 +620,7 @@ end --- Returns a random location within the zone of the @{Group}. -- @param #ZONE_GROUP self --- @return DCSTypes#Vec2 The random location of the zone based on the @{Group} location. +-- @return Dcs.DCSTypes#Vec2 The random location of the zone based on the @{Group} location. function ZONE_GROUP:GetRandomVec2() self:F( self.ZoneName ) @@ -522,23 +640,23 @@ end -- Polygons ---- The ZONE_POLYGON_BASE class defined by an array of @{DCSTypes#Vec2}, forming a polygon. +--- The ZONE_POLYGON_BASE class defined by an array of @{Dcs.DCSTypes#Vec2}, forming a polygon. -- @type ZONE_POLYGON_BASE --- @field #ZONE_POLYGON_BASE.ListVec2 Polygon The polygon defined by an array of @{DCSTypes#Vec2}. --- @extends Zone#ZONE_BASE +-- @field #ZONE_POLYGON_BASE.ListVec2 Polygon The polygon defined by an array of @{Dcs.DCSTypes#Vec2}. +-- @extends Core.Zone#ZONE_BASE ZONE_POLYGON_BASE = { ClassName="ZONE_POLYGON_BASE", } --- A points array. -- @type ZONE_POLYGON_BASE.ListVec2 --- @list +-- @list ---- Constructor to create a ZONE_POLYGON_BASE instance, taking the zone name and an array of @{DCSTypes#Vec2}, forming a polygon. --- The @{Group#GROUP} waypoints define the polygon corners. The first and the last point are automatically connected. +--- Constructor to create a ZONE_POLYGON_BASE instance, taking the zone name and an array of @{Dcs.DCSTypes#Vec2}, forming a polygon. +-- The @{Wrapper.Group#GROUP} waypoints define the polygon corners. The first and the last point are automatically connected. -- @param #ZONE_POLYGON_BASE self -- @param #string ZoneName Name of the zone. --- @param #ZONE_POLYGON_BASE.ListVec2 PointsArray An array of @{DCSTypes#Vec2}, forming a polygon.. +-- @param #ZONE_POLYGON_BASE.ListVec2 PointsArray An array of @{Dcs.DCSTypes#Vec2}, forming a polygon.. -- @return #ZONE_POLYGON_BASE self function ZONE_POLYGON_BASE:New( ZoneName, PointsArray ) local self = BASE:Inherit( self, ZONE_BASE:New( ZoneName ) ) @@ -571,7 +689,7 @@ end --- Smokes the zone boundaries in a color. -- @param #ZONE_POLYGON_BASE self --- @param #POINT_VEC3.SmokeColor SmokeColor The smoke color. +-- @param Utilities.Utils#SMOKECOLOR SmokeColor The smoke color. -- @return #ZONE_POLYGON_BASE self function ZONE_POLYGON_BASE:SmokeZone( SmokeColor ) self:F2( SmokeColor ) @@ -607,7 +725,7 @@ end --- Returns if a location is within the zone. -- Source learned and taken from: https://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html -- @param #ZONE_POLYGON_BASE self --- @param DCSTypes#Vec2 Vec2 The location to test. +-- @param Dcs.DCSTypes#Vec2 Vec2 The location to test. -- @return #boolean true if the location is within the zone. function ZONE_POLYGON_BASE:IsPointVec2InZone( Vec2 ) self:F2( Vec2 ) @@ -635,9 +753,9 @@ function ZONE_POLYGON_BASE:IsPointVec2InZone( Vec2 ) return InPolygon end ---- Define a random @{DCSTypes#Vec2} within the zone. +--- Define a random @{Dcs.DCSTypes#Vec2} within the zone. -- @param #ZONE_POLYGON_BASE self --- @return DCSTypes#Vec2 The Vec2 coordinate. +-- @return Dcs.DCSTypes#Vec2 The Vec2 coordinate. function ZONE_POLYGON_BASE:GetRandomVec2() self:F2() @@ -687,18 +805,18 @@ end ---- The ZONE_POLYGON class defined by a sequence of @{Group#GROUP} waypoints within the Mission Editor, forming a polygon. +--- The ZONE_POLYGON class defined by a sequence of @{Wrapper.Group#GROUP} waypoints within the Mission Editor, forming a polygon. -- @type ZONE_POLYGON --- @extends Zone#ZONE_POLYGON_BASE +-- @extends Core.Zone#ZONE_POLYGON_BASE ZONE_POLYGON = { ClassName="ZONE_POLYGON", } ---- Constructor to create a ZONE_POLYGON instance, taking the zone name and the name of the @{Group#GROUP} defined within the Mission Editor. --- The @{Group#GROUP} waypoints define the polygon corners. The first and the last point are automatically connected by ZONE_POLYGON. +--- Constructor to create a ZONE_POLYGON instance, taking the zone name and the name of the @{Wrapper.Group#GROUP} defined within the Mission Editor. +-- The @{Wrapper.Group#GROUP} waypoints define the polygon corners. The first and the last point are automatically connected by ZONE_POLYGON. -- @param #ZONE_POLYGON self -- @param #string ZoneName Name of the zone. --- @param Group#GROUP ZoneGroup The GROUP waypoints as defined within the Mission Editor define the polygon shape. +-- @param Wrapper.Group#GROUP ZoneGroup The GROUP waypoints as defined within the Mission Editor define the polygon shape. -- @return #ZONE_POLYGON self function ZONE_POLYGON:New( ZoneName, ZoneGroup ) diff --git a/Moose Development/Dcs/DCSAirbase.lua b/Moose Development/Moose/Dcs/DCSAirbase.lua similarity index 95% rename from Moose Development/Dcs/DCSAirbase.lua rename to Moose Development/Moose/Dcs/DCSAirbase.lua index 85ed697df..d47ddc067 100644 --- a/Moose Development/Dcs/DCSAirbase.lua +++ b/Moose Development/Moose/Dcs/DCSAirbase.lua @@ -4,7 +4,7 @@ --- Represents airbases: airdromes, helipads and ships with flying decks or landing pads. -- @type Airbase --- @extends DCSCoalitionObject#CoalitionObject +-- @extends Dcs.DCSCoalitionWrapper.Object#CoalitionObject -- @field #Airbase.ID ID Identifier of an airbase. It assigned to an airbase by the Mission Editor automatically. This identifier is used in AI tasks to refer an airbase that exists (spawned and not dead) or not. -- @field #Airbase.Category Category enum contains identifiers of airbase categories. -- @field #Airbase.Desc Desc Airbase descriptor. Airdromes are unique and their types are unique, but helipads and ships are not always unique and may have the same type. @@ -33,7 +33,7 @@ --- Returns Unit that is corresponded to the airbase. Works only for ships. -- @function [parent=#Airbase] getUnit -- @param self --- @return Unit#Unit +-- @return Wrapper.Unit#Unit --- Returns identifier of the airbase. -- @function [parent=#Airbase] getID diff --git a/Moose Development/Dcs/DCSCoalitionObject.lua b/Moose Development/Moose/Dcs/DCSCoalitionObject.lua similarity index 88% rename from Moose Development/Dcs/DCSCoalitionObject.lua rename to Moose Development/Moose/Dcs/DCSCoalitionObject.lua index 92b201349..206ec75d8 100644 --- a/Moose Development/Dcs/DCSCoalitionObject.lua +++ b/Moose Development/Moose/Dcs/DCSCoalitionObject.lua @@ -2,7 +2,7 @@ -- @module DCSCoalitionObject --- @type CoalitionObject --- @extends DCSObject#Object +-- @extends Dcs.DCSWrapper.Object#Object --- @type coalition -- @field #coalition.side side @@ -17,7 +17,7 @@ coalition = {} --#coalition --- Returns coalition of the object. -- @function [parent=#CoalitionObject] getCoalition -- @param #CoalitionObject self --- @return DCSTypes#coalition.side +-- @return Dcs.DCSTypes#coalition.side --- Returns object country. -- @function [parent=#CoalitionObject] getCountry diff --git a/Moose Development/Dcs/DCSCommand.lua b/Moose Development/Moose/Dcs/DCSCommand.lua similarity index 100% rename from Moose Development/Dcs/DCSCommand.lua rename to Moose Development/Moose/Dcs/DCSCommand.lua diff --git a/Moose Development/Dcs/DCSController.lua b/Moose Development/Moose/Dcs/DCSController.lua similarity index 97% rename from Moose Development/Dcs/DCSController.lua rename to Moose Development/Moose/Dcs/DCSController.lua index 602e97d5d..09b8d4b1e 100644 --- a/Moose Development/Dcs/DCSController.lua +++ b/Moose Development/Moose/Dcs/DCSController.lua @@ -78,7 +78,7 @@ --- Detected target. -- @type DetectedTarget --- @field Object#Object object The target +-- @field Wrapper.Object#Object object The target -- @field #boolean visible The target is visible -- @field #boolean type The target type is known -- @field #boolean distance Distance to the target is known @@ -87,7 +87,7 @@ --- Checks if the target is detected or not. If one or more detection method is specified the function will return true if the target is detected by at least one of these methods. If no detection methods are specified the function will return true if the target is detected by any method. -- @function [parent=#Controller] isTargetDetected -- @param self --- @param Object#Object target Target to check +-- @param Wrapper.Object#Object target Target to check -- @param #Controller.Detection detection Controller.Detection detection1, Controller.Detection detection2, ... Controller.Detection detectionN -- @return #boolean detected True if the target is detected. -- @return #boolean visible Has effect only if detected is true. True if the target is visible now. @@ -107,7 +107,7 @@ --- Know a target. -- @function [parent=#Controller] knowTarget -- @param self --- @param Object#Object object The target. +-- @param Wrapper.Object#Object object The target. -- @param #boolean type Target type is known. -- @param #boolean distance Distance to target is known. diff --git a/Moose Development/Dcs/DCSGroup.lua b/Moose Development/Moose/Dcs/DCSGroup.lua similarity index 94% rename from Moose Development/Dcs/DCSGroup.lua rename to Moose Development/Moose/Dcs/DCSGroup.lua index 46e212f2f..04f7818bf 100644 --- a/Moose Development/Dcs/DCSGroup.lua +++ b/Moose Development/Moose/Dcs/DCSGroup.lua @@ -41,7 +41,7 @@ --- Returns the coalition of the group. -- @function [parent=#Group] getCoalition -- @param #Group self --- @return DCSCoalitionObject#coalition.side +-- @return Dcs.DCSCoalitionWrapper.Object#coalition.side --- Returns the group's name. This is the same name assigned to the group in Mission Editor. -- @function [parent=#Group] getName @@ -57,7 +57,7 @@ -- @function [parent=#Group] getUnit -- @param #Group self -- @param #number unitNumber --- @return DCSUnit#Unit +-- @return Dcs.DCSWrapper.Unit#Unit --- Returns current size of the group. If some of the units will be destroyed, As units are destroyed the size of the group will be changed. -- @function [parent=#Group] getSize @@ -72,7 +72,7 @@ --- Returns array of the units present in the group now. Destroyed units will not be enlisted at all. -- @function [parent=#Group] getUnits -- @param #Group self --- @return #list array of Units +-- @return #list array of Units --- Returns controller of the group. -- @function [parent=#Group] getController diff --git a/Moose Development/Dcs/DCSObject.lua b/Moose Development/Moose/Dcs/DCSObject.lua similarity index 100% rename from Moose Development/Dcs/DCSObject.lua rename to Moose Development/Moose/Dcs/DCSObject.lua diff --git a/Moose Development/Dcs/DCSStaticObject.lua b/Moose Development/Moose/Dcs/DCSStaticObject.lua similarity index 93% rename from Moose Development/Dcs/DCSStaticObject.lua rename to Moose Development/Moose/Dcs/DCSStaticObject.lua index e8e76964f..5dc220412 100644 --- a/Moose Development/Dcs/DCSStaticObject.lua +++ b/Moose Development/Moose/Dcs/DCSStaticObject.lua @@ -4,7 +4,7 @@ ------------------------------------------------------------------------------- -- @module StaticObject --- @extends CoalitionObject#CoalitionObject +-- @extends CoalitionWrapper.Object#CoalitionObject --- Represents static object added in the Mission Editor. -- @type StaticObject @@ -13,7 +13,7 @@ --- StaticObject descriptor. Airdromes are unique and their types are unique, but helipads and ships are not always unique and may have the same type. -- @type StaticObject.Desc --- @extends Unit#Unit.Desc +-- @extends Wrapper.Unit#Unit.Desc --- Returns static object by its name. If no static object found nil will be returned. -- @function [parent=#StaticObject] getByName diff --git a/Moose Development/Dcs/DCSTask.lua b/Moose Development/Moose/Dcs/DCSTask.lua similarity index 100% rename from Moose Development/Dcs/DCSTask.lua rename to Moose Development/Moose/Dcs/DCSTask.lua diff --git a/Moose Development/Dcs/DCSTime.lua b/Moose Development/Moose/Dcs/DCSTime.lua similarity index 100% rename from Moose Development/Dcs/DCSTime.lua rename to Moose Development/Moose/Dcs/DCSTime.lua diff --git a/Moose Development/Dcs/DCSTypes.lua b/Moose Development/Moose/Dcs/DCSTypes.lua similarity index 100% rename from Moose Development/Dcs/DCSTypes.lua rename to Moose Development/Moose/Dcs/DCSTypes.lua diff --git a/Moose Development/Dcs/DCSUnit.lua b/Moose Development/Moose/Dcs/DCSUnit.lua similarity index 94% rename from Moose Development/Dcs/DCSUnit.lua rename to Moose Development/Moose/Dcs/DCSUnit.lua index d94e301d7..e5e86aba2 100644 --- a/Moose Development/Dcs/DCSUnit.lua +++ b/Moose Development/Moose/Dcs/DCSUnit.lua @@ -2,7 +2,7 @@ -- @module DCSUnit --- @type Unit --- @extends DCSCoalitionObject#CoalitionObject +-- @extends Dcs.DCSCoalitionWrapper.Object#CoalitionObject -- @field ID Identifier of an unit. It assigned to an unit by the Mission Editor automatically. -- @field #Unit.Category Category -- @field #Unit.RefuelingSystem RefuelingSystem @@ -57,14 +57,14 @@ --- A unit descriptor. -- @type Unit.Desc --- @extends Object#Object.Desc +-- @extends Wrapper.Object#Object.Desc -- @field #Unit.Category category Unit Category -- @field #Mass massEmpty mass of empty unit -- @field #number speedMax istance / Time, --maximal velocity --- An aircraft descriptor. -- @type Unit.DescAircraft --- @extends Unit#Unit.Desc +-- @extends Wrapper.Unit#Unit.Desc -- @field #Mass fuelMassMax maximal inner fuel mass -- @field #Distance range Operational range -- @field #Distance Hmax Ceiling @@ -75,18 +75,18 @@ --- An airplane descriptor. -- @type Unit.DescAirplane --- @extends Unit#Unit.DescAircraft +-- @extends Wrapper.Unit#Unit.DescAircraft -- @field #number speedMax0 Distance / Time maximal TAS at ground level -- @field #number speedMax10K Distance / Time maximal TAS at altitude of 10 km --- A helicopter descriptor. -- @type Unit.DescHelicopter --- @extends Unit#Unit.DescAircraft +-- @extends Wrapper.Unit#Unit.DescAircraft -- @field #Distance HmaxStat static ceiling --- A vehicle descriptor. -- @type Unit.DescVehicle --- @extends Unit#Unit.Desc +-- @extends Wrapper.Unit#Unit.Desc -- @field #Angle maxSlopeAngle maximal slope angle -- @field #boolean riverCrossing can the vehicle cross a rivers @@ -106,12 +106,12 @@ --- An optic sensor. -- @type Unit.Optic --- @extends Unit#Unit.Sensor +-- @extends Wrapper.Unit#Unit.Sensor -- @field #Unit.OpticType opticType --- A radar. -- @type Unit.Radar --- @extends Unit#Unit.Sensor +-- @extends Wrapper.Unit#Unit.Sensor -- @field #Distance detectionDistanceRBM detection distance for RCS=1m^2 in real-beam mapping mode, nil if radar doesn't support surface/land search -- @field #Distance detectionDistanceHRM detection distance for RCS=1m^2 in high-resolution mapping mode, nil if radar has no HRM -- @field #Unit.Radar.detectionDistanceAir detectionDistanceAir detection distance for RCS=1m^2 airborne target, nil if radar doesn't support air search @@ -129,7 +129,7 @@ -- @field #Distance tailOn --- An IRST. --- @type Unit#Unit.IRST +-- @type Wrapper.Unit#Unit.IRST -- @extends Unit.Sensor -- @field #Distance detectionDistanceIdle detection of tail-on target with heat signature = 1 in upper hemisphere, engines are in idle -- @field #Distance detectionDistanceMaximal ..., engines are in maximal mode @@ -137,7 +137,7 @@ --- An RWR. -- @type Unit.RWR --- @extends Unit#Unit.Sensor +-- @extends Wrapper.Unit#Unit.Sensor --- table that stores all unit sensors. -- TODO @type Sensors @@ -178,7 +178,7 @@ --- Returns the unit's group if it exist and nil otherwise -- @function [parent=#Unit] getGroup -- @param #Unit self --- @return DCSGroup#Group +-- @return Dcs.DCSWrapper.Group#Group --- Returns the unit's callsign - the localized string. -- @function [parent=#Unit] getCallsign @@ -230,7 +230,7 @@ -- Second value is the object of the radar's interest. Not nil only if at least one radar of the unit is tracking a target. -- @function [parent=#Unit] getRadar -- @param #Unit self --- @return #boolean, Object#Object +-- @return #boolean, Wrapper.Object#Object --- Returns unit descriptor. Descriptor type depends on unit category. -- @function [parent=#Unit] getDesc diff --git a/Moose Development/Dcs/DCScountry.lua b/Moose Development/Moose/Dcs/DCScountry.lua similarity index 100% rename from Moose Development/Dcs/DCScountry.lua rename to Moose Development/Moose/Dcs/DCScountry.lua diff --git a/Moose Development/Dcs/DCSenv.lua b/Moose Development/Moose/Dcs/DCSenv.lua similarity index 100% rename from Moose Development/Dcs/DCSenv.lua rename to Moose Development/Moose/Dcs/DCSenv.lua diff --git a/Moose Development/Dcs/DCSland.lua b/Moose Development/Moose/Dcs/DCSland.lua similarity index 94% rename from Moose Development/Dcs/DCSland.lua rename to Moose Development/Moose/Dcs/DCSland.lua index 1f79991bc..6391142af 100644 --- a/Moose Development/Dcs/DCSland.lua +++ b/Moose Development/Moose/Dcs/DCSland.lua @@ -15,7 +15,7 @@ --- Returns altitude MSL of the point. -- @function [parent=#land] getHeight -- @param #Vec2 point point on the ground. --- @return DCSTypes#Distance +-- @return Dcs.DCSTypes#Distance --- returns surface type at the given point. -- @function [parent=#land] getSurfaceType diff --git a/Moose Development/Dcs/DCStimer.lua b/Moose Development/Moose/Dcs/DCStimer.lua similarity index 100% rename from Moose Development/Dcs/DCStimer.lua rename to Moose Development/Moose/Dcs/DCStimer.lua diff --git a/Moose Development/Dcs/DCStrigger.lua b/Moose Development/Moose/Dcs/DCStrigger.lua similarity index 96% rename from Moose Development/Dcs/DCStrigger.lua rename to Moose Development/Moose/Dcs/DCStrigger.lua index 408e75689..7bf5360be 100644 --- a/Moose Development/Dcs/DCStrigger.lua +++ b/Moose Development/Moose/Dcs/DCStrigger.lua @@ -3,3 +3,6 @@ trigger = {} --#timer + + + \ No newline at end of file diff --git a/Moose Development/Dcs/DCSworld.lua b/Moose Development/Moose/Dcs/DCSworld.lua similarity index 100% rename from Moose Development/Dcs/DCSworld.lua rename to Moose Development/Moose/Dcs/DCSworld.lua diff --git a/Moose Development/Moose/AirbasePolice.lua b/Moose Development/Moose/Functional/AirbasePolice.lua similarity index 89% rename from Moose Development/Moose/AirbasePolice.lua rename to Moose Development/Moose/Functional/AirbasePolice.lua index 45a4dac05..45af50d9f 100644 --- a/Moose Development/Moose/AirbasePolice.lua +++ b/Moose Development/Moose/Functional/AirbasePolice.lua @@ -2,9 +2,9 @@ -- -- === -- --- 1) @{AirbasePolice#AIRBASEPOLICE_BASE} class, extends @{Base#BASE} +-- 1) @{Functional.AirbasePolice#AIRBASEPOLICE_BASE} class, extends @{Core.Base#BASE} -- ================================================================== --- The @{AirbasePolice#AIRBASEPOLICE_BASE} class provides the main methods to monitor CLIENT behaviour at airbases. +-- The @{Functional.AirbasePolice#AIRBASEPOLICE_BASE} class provides the main methods to monitor CLIENT behaviour at airbases. -- CLIENTS should not be allowed to: -- -- * Don't taxi faster than 40 km/h. @@ -12,7 +12,7 @@ -- * Avoid to hit other planes on the airbase. -- * Obey ground control orders. -- --- 2) @{AirbasePolice#AIRBASEPOLICE_CAUCASUS} class, extends @{AirbasePolice#AIRBASEPOLICE_BASE} +-- 2) @{Functional.AirbasePolice#AIRBASEPOLICE_CAUCASUS} class, extends @{Functional.AirbasePolice#AIRBASEPOLICE_BASE} -- ============================================================================================= -- All the airbases on the caucasus map can be monitored using this class. -- If you want to monitor specific airbases, you need to use the @{#AIRBASEPOLICE_BASE.Monitor}() method, which takes a table or airbase names. @@ -39,7 +39,7 @@ -- * TbilisiLochini -- * Vaziani -- --- 3) @{AirbasePolice#AIRBASEPOLICE_NEVADA} class, extends @{AirbasePolice#AIRBASEPOLICE_BASE} +-- 3) @{Functional.AirbasePolice#AIRBASEPOLICE_NEVADA} class, extends @{Functional.AirbasePolice#AIRBASEPOLICE_BASE} -- ============================================================================================= -- All the airbases on the NEVADA map can be monitored using this class. -- If you want to monitor specific airbases, you need to use the @{#AIRBASEPOLICE_BASE.Monitor}() method, which takes a table or airbase names. @@ -59,8 +59,8 @@ --- @type AIRBASEPOLICE_BASE --- @field Set#SET_CLIENT SetClient --- @extends Base#BASE +-- @field Core.Set#SET_CLIENT SetClient +-- @extends Core.Base#BASE AIRBASEPOLICE_BASE = { ClassName = "AIRBASEPOLICE_BASE", @@ -85,21 +85,21 @@ function AIRBASEPOLICE_BASE:New( SetClient, Airbases ) self.Airbases = Airbases for AirbaseID, Airbase in pairs( self.Airbases ) do - Airbase.ZoneBoundary = ZONE_POLYGON_BASE:New( "Boundary", Airbase.PointsBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + Airbase.ZoneBoundary = ZONE_POLYGON_BASE:New( "Boundary", Airbase.PointsBoundary ):SmokeZone(SMOKECOLOR.White):Flush() for PointsRunwayID, PointsRunway in pairs( Airbase.PointsRunways ) do - Airbase.ZoneRunways[PointsRunwayID] = ZONE_POLYGON_BASE:New( "Runway " .. PointsRunwayID, PointsRunway ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + Airbase.ZoneRunways[PointsRunwayID] = ZONE_POLYGON_BASE:New( "Runway " .. PointsRunwayID, PointsRunway ):SmokeZone(SMOKECOLOR.Red):Flush() end end -- -- Template -- local TemplateBoundary = GROUP:FindByName( "Template Boundary" ) --- self.Airbases.Template.ZoneBoundary = ZONE_POLYGON:New( "Template Boundary", TemplateBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() +-- self.Airbases.Template.ZoneBoundary = ZONE_POLYGON:New( "Template Boundary", TemplateBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local TemplateRunway1 = GROUP:FindByName( "Template Runway 1" ) --- self.Airbases.Template.ZoneRunways[1] = ZONE_POLYGON:New( "Template Runway 1", TemplateRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() +-- self.Airbases.Template.ZoneRunways[1] = ZONE_POLYGON:New( "Template Runway 1", TemplateRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() self.SetClient:ForEachClient( - --- @param Client#CLIENT Client + --- @param Wrapper.Client#CLIENT Client function( Client ) Client:SetState( self, "Speeding", false ) Client:SetState( self, "Warnings", 0) @@ -141,7 +141,7 @@ function AIRBASEPOLICE_BASE:_AirbaseMonitor() self.SetClient:ForEachClientInZone( Airbase.ZoneBoundary, - --- @param Client#CLIENT Client + --- @param Wrapper.Client#CLIENT Client function( Client ) self:E( Client.UnitName ) @@ -219,7 +219,7 @@ end --- @type AIRBASEPOLICE_CAUCASUS --- @field Set#SET_CLIENT SetClient +-- @field Core.Set#SET_CLIENT SetClient -- @extends #AIRBASEPOLICE_BASE AIRBASEPOLICE_CAUCASUS = { @@ -749,197 +749,197 @@ function AIRBASEPOLICE_CAUCASUS:New( SetClient ) -- -- AnapaVityazevo -- local AnapaVityazevoBoundary = GROUP:FindByName( "AnapaVityazevo Boundary" ) - -- self.Airbases.AnapaVityazevo.ZoneBoundary = ZONE_POLYGON:New( "AnapaVityazevo Boundary", AnapaVityazevoBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.AnapaVityazevo.ZoneBoundary = ZONE_POLYGON:New( "AnapaVityazevo Boundary", AnapaVityazevoBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local AnapaVityazevoRunway1 = GROUP:FindByName( "AnapaVityazevo Runway 1" ) - -- self.Airbases.AnapaVityazevo.ZoneRunways[1] = ZONE_POLYGON:New( "AnapaVityazevo Runway 1", AnapaVityazevoRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.AnapaVityazevo.ZoneRunways[1] = ZONE_POLYGON:New( "AnapaVityazevo Runway 1", AnapaVityazevoRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- Batumi -- local BatumiBoundary = GROUP:FindByName( "Batumi Boundary" ) - -- self.Airbases.Batumi.ZoneBoundary = ZONE_POLYGON:New( "Batumi Boundary", BatumiBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.Batumi.ZoneBoundary = ZONE_POLYGON:New( "Batumi Boundary", BatumiBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local BatumiRunway1 = GROUP:FindByName( "Batumi Runway 1" ) - -- self.Airbases.Batumi.ZoneRunways[1] = ZONE_POLYGON:New( "Batumi Runway 1", BatumiRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.Batumi.ZoneRunways[1] = ZONE_POLYGON:New( "Batumi Runway 1", BatumiRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- Beslan -- local BeslanBoundary = GROUP:FindByName( "Beslan Boundary" ) - -- self.Airbases.Beslan.ZoneBoundary = ZONE_POLYGON:New( "Beslan Boundary", BeslanBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.Beslan.ZoneBoundary = ZONE_POLYGON:New( "Beslan Boundary", BeslanBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local BeslanRunway1 = GROUP:FindByName( "Beslan Runway 1" ) - -- self.Airbases.Beslan.ZoneRunways[1] = ZONE_POLYGON:New( "Beslan Runway 1", BeslanRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.Beslan.ZoneRunways[1] = ZONE_POLYGON:New( "Beslan Runway 1", BeslanRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- Gelendzhik -- local GelendzhikBoundary = GROUP:FindByName( "Gelendzhik Boundary" ) - -- self.Airbases.Gelendzhik.ZoneBoundary = ZONE_POLYGON:New( "Gelendzhik Boundary", GelendzhikBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.Gelendzhik.ZoneBoundary = ZONE_POLYGON:New( "Gelendzhik Boundary", GelendzhikBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local GelendzhikRunway1 = GROUP:FindByName( "Gelendzhik Runway 1" ) - -- self.Airbases.Gelendzhik.ZoneRunways[1] = ZONE_POLYGON:New( "Gelendzhik Runway 1", GelendzhikRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.Gelendzhik.ZoneRunways[1] = ZONE_POLYGON:New( "Gelendzhik Runway 1", GelendzhikRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- Gudauta -- local GudautaBoundary = GROUP:FindByName( "Gudauta Boundary" ) - -- self.Airbases.Gudauta.ZoneBoundary = ZONE_POLYGON:New( "Gudauta Boundary", GudautaBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.Gudauta.ZoneBoundary = ZONE_POLYGON:New( "Gudauta Boundary", GudautaBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local GudautaRunway1 = GROUP:FindByName( "Gudauta Runway 1" ) - -- self.Airbases.Gudauta.ZoneRunways[1] = ZONE_POLYGON:New( "Gudauta Runway 1", GudautaRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.Gudauta.ZoneRunways[1] = ZONE_POLYGON:New( "Gudauta Runway 1", GudautaRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- Kobuleti -- local KobuletiBoundary = GROUP:FindByName( "Kobuleti Boundary" ) - -- self.Airbases.Kobuleti.ZoneBoundary = ZONE_POLYGON:New( "Kobuleti Boundary", KobuletiBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.Kobuleti.ZoneBoundary = ZONE_POLYGON:New( "Kobuleti Boundary", KobuletiBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local KobuletiRunway1 = GROUP:FindByName( "Kobuleti Runway 1" ) - -- self.Airbases.Kobuleti.ZoneRunways[1] = ZONE_POLYGON:New( "Kobuleti Runway 1", KobuletiRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.Kobuleti.ZoneRunways[1] = ZONE_POLYGON:New( "Kobuleti Runway 1", KobuletiRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- KrasnodarCenter -- local KrasnodarCenterBoundary = GROUP:FindByName( "KrasnodarCenter Boundary" ) - -- self.Airbases.KrasnodarCenter.ZoneBoundary = ZONE_POLYGON:New( "KrasnodarCenter Boundary", KrasnodarCenterBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.KrasnodarCenter.ZoneBoundary = ZONE_POLYGON:New( "KrasnodarCenter Boundary", KrasnodarCenterBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local KrasnodarCenterRunway1 = GROUP:FindByName( "KrasnodarCenter Runway 1" ) - -- self.Airbases.KrasnodarCenter.ZoneRunways[1] = ZONE_POLYGON:New( "KrasnodarCenter Runway 1", KrasnodarCenterRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.KrasnodarCenter.ZoneRunways[1] = ZONE_POLYGON:New( "KrasnodarCenter Runway 1", KrasnodarCenterRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- KrasnodarPashkovsky -- local KrasnodarPashkovskyBoundary = GROUP:FindByName( "KrasnodarPashkovsky Boundary" ) - -- self.Airbases.KrasnodarPashkovsky.ZoneBoundary = ZONE_POLYGON:New( "KrasnodarPashkovsky Boundary", KrasnodarPashkovskyBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.KrasnodarPashkovsky.ZoneBoundary = ZONE_POLYGON:New( "KrasnodarPashkovsky Boundary", KrasnodarPashkovskyBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local KrasnodarPashkovskyRunway1 = GROUP:FindByName( "KrasnodarPashkovsky Runway 1" ) - -- self.Airbases.KrasnodarPashkovsky.ZoneRunways[1] = ZONE_POLYGON:New( "KrasnodarPashkovsky Runway 1", KrasnodarPashkovskyRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.KrasnodarPashkovsky.ZoneRunways[1] = ZONE_POLYGON:New( "KrasnodarPashkovsky Runway 1", KrasnodarPashkovskyRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- local KrasnodarPashkovskyRunway2 = GROUP:FindByName( "KrasnodarPashkovsky Runway 2" ) - -- self.Airbases.KrasnodarPashkovsky.ZoneRunways[2] = ZONE_POLYGON:New( "KrasnodarPashkovsky Runway 2", KrasnodarPashkovskyRunway2 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.KrasnodarPashkovsky.ZoneRunways[2] = ZONE_POLYGON:New( "KrasnodarPashkovsky Runway 2", KrasnodarPashkovskyRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- Krymsk -- local KrymskBoundary = GROUP:FindByName( "Krymsk Boundary" ) - -- self.Airbases.Krymsk.ZoneBoundary = ZONE_POLYGON:New( "Krymsk Boundary", KrymskBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.Krymsk.ZoneBoundary = ZONE_POLYGON:New( "Krymsk Boundary", KrymskBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local KrymskRunway1 = GROUP:FindByName( "Krymsk Runway 1" ) - -- self.Airbases.Krymsk.ZoneRunways[1] = ZONE_POLYGON:New( "Krymsk Runway 1", KrymskRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.Krymsk.ZoneRunways[1] = ZONE_POLYGON:New( "Krymsk Runway 1", KrymskRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- Kutaisi -- local KutaisiBoundary = GROUP:FindByName( "Kutaisi Boundary" ) - -- self.Airbases.Kutaisi.ZoneBoundary = ZONE_POLYGON:New( "Kutaisi Boundary", KutaisiBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.Kutaisi.ZoneBoundary = ZONE_POLYGON:New( "Kutaisi Boundary", KutaisiBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local KutaisiRunway1 = GROUP:FindByName( "Kutaisi Runway 1" ) - -- self.Airbases.Kutaisi.ZoneRunways[1] = ZONE_POLYGON:New( "Kutaisi Runway 1", KutaisiRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.Kutaisi.ZoneRunways[1] = ZONE_POLYGON:New( "Kutaisi Runway 1", KutaisiRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- MaykopKhanskaya -- local MaykopKhanskayaBoundary = GROUP:FindByName( "MaykopKhanskaya Boundary" ) - -- self.Airbases.MaykopKhanskaya.ZoneBoundary = ZONE_POLYGON:New( "MaykopKhanskaya Boundary", MaykopKhanskayaBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.MaykopKhanskaya.ZoneBoundary = ZONE_POLYGON:New( "MaykopKhanskaya Boundary", MaykopKhanskayaBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local MaykopKhanskayaRunway1 = GROUP:FindByName( "MaykopKhanskaya Runway 1" ) - -- self.Airbases.MaykopKhanskaya.ZoneRunways[1] = ZONE_POLYGON:New( "MaykopKhanskaya Runway 1", MaykopKhanskayaRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.MaykopKhanskaya.ZoneRunways[1] = ZONE_POLYGON:New( "MaykopKhanskaya Runway 1", MaykopKhanskayaRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- MineralnyeVody -- local MineralnyeVodyBoundary = GROUP:FindByName( "MineralnyeVody Boundary" ) - -- self.Airbases.MineralnyeVody.ZoneBoundary = ZONE_POLYGON:New( "MineralnyeVody Boundary", MineralnyeVodyBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.MineralnyeVody.ZoneBoundary = ZONE_POLYGON:New( "MineralnyeVody Boundary", MineralnyeVodyBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local MineralnyeVodyRunway1 = GROUP:FindByName( "MineralnyeVody Runway 1" ) - -- self.Airbases.MineralnyeVody.ZoneRunways[1] = ZONE_POLYGON:New( "MineralnyeVody Runway 1", MineralnyeVodyRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.MineralnyeVody.ZoneRunways[1] = ZONE_POLYGON:New( "MineralnyeVody Runway 1", MineralnyeVodyRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- Mozdok -- local MozdokBoundary = GROUP:FindByName( "Mozdok Boundary" ) - -- self.Airbases.Mozdok.ZoneBoundary = ZONE_POLYGON:New( "Mozdok Boundary", MozdokBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.Mozdok.ZoneBoundary = ZONE_POLYGON:New( "Mozdok Boundary", MozdokBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local MozdokRunway1 = GROUP:FindByName( "Mozdok Runway 1" ) - -- self.Airbases.Mozdok.ZoneRunways[1] = ZONE_POLYGON:New( "Mozdok Runway 1", MozdokRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.Mozdok.ZoneRunways[1] = ZONE_POLYGON:New( "Mozdok Runway 1", MozdokRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- Nalchik -- local NalchikBoundary = GROUP:FindByName( "Nalchik Boundary" ) - -- self.Airbases.Nalchik.ZoneBoundary = ZONE_POLYGON:New( "Nalchik Boundary", NalchikBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.Nalchik.ZoneBoundary = ZONE_POLYGON:New( "Nalchik Boundary", NalchikBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local NalchikRunway1 = GROUP:FindByName( "Nalchik Runway 1" ) - -- self.Airbases.Nalchik.ZoneRunways[1] = ZONE_POLYGON:New( "Nalchik Runway 1", NalchikRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.Nalchik.ZoneRunways[1] = ZONE_POLYGON:New( "Nalchik Runway 1", NalchikRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- Novorossiysk -- local NovorossiyskBoundary = GROUP:FindByName( "Novorossiysk Boundary" ) - -- self.Airbases.Novorossiysk.ZoneBoundary = ZONE_POLYGON:New( "Novorossiysk Boundary", NovorossiyskBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.Novorossiysk.ZoneBoundary = ZONE_POLYGON:New( "Novorossiysk Boundary", NovorossiyskBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local NovorossiyskRunway1 = GROUP:FindByName( "Novorossiysk Runway 1" ) - -- self.Airbases.Novorossiysk.ZoneRunways[1] = ZONE_POLYGON:New( "Novorossiysk Runway 1", NovorossiyskRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.Novorossiysk.ZoneRunways[1] = ZONE_POLYGON:New( "Novorossiysk Runway 1", NovorossiyskRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- SenakiKolkhi -- local SenakiKolkhiBoundary = GROUP:FindByName( "SenakiKolkhi Boundary" ) - -- self.Airbases.SenakiKolkhi.ZoneBoundary = ZONE_POLYGON:New( "SenakiKolkhi Boundary", SenakiKolkhiBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.SenakiKolkhi.ZoneBoundary = ZONE_POLYGON:New( "SenakiKolkhi Boundary", SenakiKolkhiBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local SenakiKolkhiRunway1 = GROUP:FindByName( "SenakiKolkhi Runway 1" ) - -- self.Airbases.SenakiKolkhi.ZoneRunways[1] = ZONE_POLYGON:New( "SenakiKolkhi Runway 1", SenakiKolkhiRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.SenakiKolkhi.ZoneRunways[1] = ZONE_POLYGON:New( "SenakiKolkhi Runway 1", SenakiKolkhiRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- SochiAdler -- local SochiAdlerBoundary = GROUP:FindByName( "SochiAdler Boundary" ) - -- self.Airbases.SochiAdler.ZoneBoundary = ZONE_POLYGON:New( "SochiAdler Boundary", SochiAdlerBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.SochiAdler.ZoneBoundary = ZONE_POLYGON:New( "SochiAdler Boundary", SochiAdlerBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local SochiAdlerRunway1 = GROUP:FindByName( "SochiAdler Runway 1" ) - -- self.Airbases.SochiAdler.ZoneRunways[1] = ZONE_POLYGON:New( "SochiAdler Runway 1", SochiAdlerRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.SochiAdler.ZoneRunways[1] = ZONE_POLYGON:New( "SochiAdler Runway 1", SochiAdlerRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- local SochiAdlerRunway2 = GROUP:FindByName( "SochiAdler Runway 2" ) - -- self.Airbases.SochiAdler.ZoneRunways[2] = ZONE_POLYGON:New( "SochiAdler Runway 2", SochiAdlerRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.SochiAdler.ZoneRunways[2] = ZONE_POLYGON:New( "SochiAdler Runway 2", SochiAdlerRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- Soganlug -- local SoganlugBoundary = GROUP:FindByName( "Soganlug Boundary" ) - -- self.Airbases.Soganlug.ZoneBoundary = ZONE_POLYGON:New( "Soganlug Boundary", SoganlugBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.Soganlug.ZoneBoundary = ZONE_POLYGON:New( "Soganlug Boundary", SoganlugBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local SoganlugRunway1 = GROUP:FindByName( "Soganlug Runway 1" ) - -- self.Airbases.Soganlug.ZoneRunways[1] = ZONE_POLYGON:New( "Soganlug Runway 1", SoganlugRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.Soganlug.ZoneRunways[1] = ZONE_POLYGON:New( "Soganlug Runway 1", SoganlugRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- SukhumiBabushara -- local SukhumiBabusharaBoundary = GROUP:FindByName( "SukhumiBabushara Boundary" ) - -- self.Airbases.SukhumiBabushara.ZoneBoundary = ZONE_POLYGON:New( "SukhumiBabushara Boundary", SukhumiBabusharaBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.SukhumiBabushara.ZoneBoundary = ZONE_POLYGON:New( "SukhumiBabushara Boundary", SukhumiBabusharaBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local SukhumiBabusharaRunway1 = GROUP:FindByName( "SukhumiBabushara Runway 1" ) - -- self.Airbases.SukhumiBabushara.ZoneRunways[1] = ZONE_POLYGON:New( "SukhumiBabushara Runway 1", SukhumiBabusharaRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.SukhumiBabushara.ZoneRunways[1] = ZONE_POLYGON:New( "SukhumiBabushara Runway 1", SukhumiBabusharaRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- TbilisiLochini -- local TbilisiLochiniBoundary = GROUP:FindByName( "TbilisiLochini Boundary" ) - -- self.Airbases.TbilisiLochini.ZoneBoundary = ZONE_POLYGON:New( "TbilisiLochini Boundary", TbilisiLochiniBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.TbilisiLochini.ZoneBoundary = ZONE_POLYGON:New( "TbilisiLochini Boundary", TbilisiLochiniBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local TbilisiLochiniRunway1 = GROUP:FindByName( "TbilisiLochini Runway 1" ) - -- self.Airbases.TbilisiLochini.ZoneRunways[1] = ZONE_POLYGON:New( "TbilisiLochini Runway 1", TbilisiLochiniRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.TbilisiLochini.ZoneRunways[1] = ZONE_POLYGON:New( "TbilisiLochini Runway 1", TbilisiLochiniRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- local TbilisiLochiniRunway2 = GROUP:FindByName( "TbilisiLochini Runway 2" ) - -- self.Airbases.TbilisiLochini.ZoneRunways[2] = ZONE_POLYGON:New( "TbilisiLochini Runway 2", TbilisiLochiniRunway2 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.TbilisiLochini.ZoneRunways[2] = ZONE_POLYGON:New( "TbilisiLochini Runway 2", TbilisiLochiniRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- Vaziani -- local VazianiBoundary = GROUP:FindByName( "Vaziani Boundary" ) - -- self.Airbases.Vaziani.ZoneBoundary = ZONE_POLYGON:New( "Vaziani Boundary", VazianiBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.Vaziani.ZoneBoundary = ZONE_POLYGON:New( "Vaziani Boundary", VazianiBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local VazianiRunway1 = GROUP:FindByName( "Vaziani Runway 1" ) - -- self.Airbases.Vaziani.ZoneRunways[1] = ZONE_POLYGON:New( "Vaziani Runway 1", VazianiRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.Vaziani.ZoneRunways[1] = ZONE_POLYGON:New( "Vaziani Runway 1", VazianiRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- @@ -947,10 +947,10 @@ function AIRBASEPOLICE_CAUCASUS:New( SetClient ) -- Template -- local TemplateBoundary = GROUP:FindByName( "Template Boundary" ) - -- self.Airbases.Template.ZoneBoundary = ZONE_POLYGON:New( "Template Boundary", TemplateBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.Template.ZoneBoundary = ZONE_POLYGON:New( "Template Boundary", TemplateBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local TemplateRunway1 = GROUP:FindByName( "Template Runway 1" ) - -- self.Airbases.Template.ZoneRunways[1] = ZONE_POLYGON:New( "Template Runway 1", TemplateRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.Template.ZoneRunways[1] = ZONE_POLYGON:New( "Template Runway 1", TemplateRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() return self @@ -960,7 +960,7 @@ end --- @type AIRBASEPOLICE_NEVADA --- @extends AirbasePolice#AIRBASEPOLICE_BASE +-- @extends Functional.AirbasePolice#AIRBASEPOLICE_BASE AIRBASEPOLICE_NEVADA = { ClassName = "AIRBASEPOLICE_NEVADA", Airbases = { @@ -1143,49 +1143,49 @@ function AIRBASEPOLICE_NEVADA:New( SetClient ) -- -- Nellis -- local NellisBoundary = GROUP:FindByName( "Nellis Boundary" ) --- self.Airbases.Nellis.ZoneBoundary = ZONE_POLYGON:New( "Nellis Boundary", NellisBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() +-- self.Airbases.Nellis.ZoneBoundary = ZONE_POLYGON:New( "Nellis Boundary", NellisBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local NellisRunway1 = GROUP:FindByName( "Nellis Runway 1" ) --- self.Airbases.Nellis.ZoneRunways[1] = ZONE_POLYGON:New( "Nellis Runway 1", NellisRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() +-- self.Airbases.Nellis.ZoneRunways[1] = ZONE_POLYGON:New( "Nellis Runway 1", NellisRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- local NellisRunway2 = GROUP:FindByName( "Nellis Runway 2" ) --- self.Airbases.Nellis.ZoneRunways[2] = ZONE_POLYGON:New( "Nellis Runway 2", NellisRunway2 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() +-- self.Airbases.Nellis.ZoneRunways[2] = ZONE_POLYGON:New( "Nellis Runway 2", NellisRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- McCarran -- local McCarranBoundary = GROUP:FindByName( "McCarran Boundary" ) --- self.Airbases.McCarran.ZoneBoundary = ZONE_POLYGON:New( "McCarran Boundary", McCarranBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() +-- self.Airbases.McCarran.ZoneBoundary = ZONE_POLYGON:New( "McCarran Boundary", McCarranBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local McCarranRunway1 = GROUP:FindByName( "McCarran Runway 1" ) --- self.Airbases.McCarran.ZoneRunways[1] = ZONE_POLYGON:New( "McCarran Runway 1", McCarranRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() +-- self.Airbases.McCarran.ZoneRunways[1] = ZONE_POLYGON:New( "McCarran Runway 1", McCarranRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- local McCarranRunway2 = GROUP:FindByName( "McCarran Runway 2" ) --- self.Airbases.McCarran.ZoneRunways[2] = ZONE_POLYGON:New( "McCarran Runway 2", McCarranRunway2 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() +-- self.Airbases.McCarran.ZoneRunways[2] = ZONE_POLYGON:New( "McCarran Runway 2", McCarranRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- local McCarranRunway3 = GROUP:FindByName( "McCarran Runway 3" ) --- self.Airbases.McCarran.ZoneRunways[3] = ZONE_POLYGON:New( "McCarran Runway 3", McCarranRunway3 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() +-- self.Airbases.McCarran.ZoneRunways[3] = ZONE_POLYGON:New( "McCarran Runway 3", McCarranRunway3 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- local McCarranRunway4 = GROUP:FindByName( "McCarran Runway 4" ) --- self.Airbases.McCarran.ZoneRunways[4] = ZONE_POLYGON:New( "McCarran Runway 4", McCarranRunway4 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() +-- self.Airbases.McCarran.ZoneRunways[4] = ZONE_POLYGON:New( "McCarran Runway 4", McCarranRunway4 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- Creech -- local CreechBoundary = GROUP:FindByName( "Creech Boundary" ) --- self.Airbases.Creech.ZoneBoundary = ZONE_POLYGON:New( "Creech Boundary", CreechBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() +-- self.Airbases.Creech.ZoneBoundary = ZONE_POLYGON:New( "Creech Boundary", CreechBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local CreechRunway1 = GROUP:FindByName( "Creech Runway 1" ) --- self.Airbases.Creech.ZoneRunways[1] = ZONE_POLYGON:New( "Creech Runway 1", CreechRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() +-- self.Airbases.Creech.ZoneRunways[1] = ZONE_POLYGON:New( "Creech Runway 1", CreechRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- local CreechRunway2 = GROUP:FindByName( "Creech Runway 2" ) --- self.Airbases.Creech.ZoneRunways[2] = ZONE_POLYGON:New( "Creech Runway 2", CreechRunway2 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() +-- self.Airbases.Creech.ZoneRunways[2] = ZONE_POLYGON:New( "Creech Runway 2", CreechRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- Groom Lake -- local GroomLakeBoundary = GROUP:FindByName( "GroomLake Boundary" ) --- self.Airbases.GroomLake.ZoneBoundary = ZONE_POLYGON:New( "GroomLake Boundary", GroomLakeBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() +-- self.Airbases.GroomLake.ZoneBoundary = ZONE_POLYGON:New( "GroomLake Boundary", GroomLakeBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local GroomLakeRunway1 = GROUP:FindByName( "GroomLake Runway 1" ) --- self.Airbases.GroomLake.ZoneRunways[1] = ZONE_POLYGON:New( "GroomLake Runway 1", GroomLakeRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() +-- self.Airbases.GroomLake.ZoneRunways[1] = ZONE_POLYGON:New( "GroomLake Runway 1", GroomLakeRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- local GroomLakeRunway2 = GROUP:FindByName( "GroomLake Runway 2" ) --- self.Airbases.GroomLake.ZoneRunways[2] = ZONE_POLYGON:New( "GroomLake Runway 2", GroomLakeRunway2 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() +-- self.Airbases.GroomLake.ZoneRunways[2] = ZONE_POLYGON:New( "GroomLake Runway 2", GroomLakeRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush() end diff --git a/Moose Development/Moose/CleanUp.lua b/Moose Development/Moose/Functional/CleanUp.lua similarity index 95% rename from Moose Development/Moose/CleanUp.lua rename to Moose Development/Moose/Functional/CleanUp.lua index b8525ebce..c9fb36b23 100644 --- a/Moose Development/Moose/CleanUp.lua +++ b/Moose Development/Moose/Functional/CleanUp.lua @@ -10,7 +10,7 @@ --- The CLEANUP class. -- @type CLEANUP --- @extends Base#BASE +-- @extends Core.Base#BASE CLEANUP = { ClassName = "CLEANUP", ZoneNames = {}, @@ -51,7 +51,7 @@ end --- Destroys a group from the simulator, but checks first if it is still existing! -- @param #CLEANUP self --- @param DCSGroup#Group GroupObject The object to be destroyed. +-- @param Dcs.DCSWrapper.Group#Group GroupObject The object to be destroyed. -- @param #string CleanUpGroupName The groupname... function CLEANUP:_DestroyGroup( GroupObject, CleanUpGroupName ) self:F( { GroupObject, CleanUpGroupName } ) @@ -62,9 +62,9 @@ function CLEANUP:_DestroyGroup( GroupObject, CleanUpGroupName ) end end ---- Destroys a @{DCSUnit#Unit} from the simulator, but checks first if it is still existing! +--- Destroys a @{Dcs.DCSWrapper.Unit#Unit} from the simulator, but checks first if it is still existing! -- @param #CLEANUP self --- @param DCSUnit#Unit CleanUpUnit The object to be destroyed. +-- @param Dcs.DCSWrapper.Unit#Unit CleanUpUnit The object to be destroyed. -- @param #string CleanUpUnitName The Unit name ... function CLEANUP:_DestroyUnit( CleanUpUnit, CleanUpUnitName ) self:F( { CleanUpUnit, CleanUpUnitName } ) @@ -89,10 +89,10 @@ function CLEANUP:_DestroyUnit( CleanUpUnit, CleanUpUnitName ) end end --- TODO check DCSTypes#Weapon +-- TODO check Dcs.DCSTypes#Weapon --- Destroys a missile from the simulator, but checks first if it is still existing! -- @param #CLEANUP self --- @param DCSTypes#Weapon MissileObject +-- @param Dcs.DCSTypes#Weapon MissileObject function CLEANUP:_DestroyMissile( MissileObject ) self:F( { MissileObject } ) @@ -134,7 +134,7 @@ end --- Detects if a crash event occurs. -- Crashed units go into a CleanUpList for removal. -- @param #CLEANUP self --- @param DCSTypes#Event event +-- @param Dcs.DCSTypes#Event event function CLEANUP:_EventCrash( Event ) self:F( { Event } ) @@ -157,7 +157,7 @@ end --- Detects if a unit shoots a missile. -- If this occurs within one of the zones, then the weapon used must be destroyed. -- @param #CLEANUP self --- @param DCSTypes#Event event +-- @param Dcs.DCSTypes#Event event function CLEANUP:_EventShot( Event ) self:F( { Event } ) @@ -174,7 +174,7 @@ end --- Detects if the Unit has an S_EVENT_HIT within the given ZoneNames. If this is the case, destroy the unit. -- @param #CLEANUP self --- @param DCSTypes#Event event +-- @param Dcs.DCSTypes#Event event function CLEANUP:_EventHitCleanUp( Event ) self:F( { Event } ) @@ -199,7 +199,7 @@ function CLEANUP:_EventHitCleanUp( Event ) end end ---- Add the @{DCSUnit#Unit} to the CleanUpList for CleanUp. +--- Add the @{Dcs.DCSWrapper.Unit#Unit} to the CleanUpList for CleanUp. function CLEANUP:_AddForCleanUp( CleanUpUnit, CleanUpUnitName ) self:F( { CleanUpUnit, CleanUpUnitName } ) @@ -217,7 +217,7 @@ end --- Detects if the Unit has an S_EVENT_ENGINE_SHUTDOWN or an S_EVENT_HIT within the given ZoneNames. If this is the case, add the Group to the CLEANUP List. -- @param #CLEANUP self --- @param DCSTypes#Event event +-- @param Dcs.DCSTypes#Event event function CLEANUP:_EventAddForCleanUp( Event ) if Event.IniDCSUnit then diff --git a/Moose Development/Moose/Detection.lua b/Moose Development/Moose/Functional/Detection.lua similarity index 84% rename from Moose Development/Moose/Detection.lua rename to Moose Development/Moose/Functional/Detection.lua index a62b8e664..bd5df0de6 100644 --- a/Moose Development/Moose/Detection.lua +++ b/Moose Development/Moose/Functional/Detection.lua @@ -2,14 +2,14 @@ -- -- === -- --- 1) @{Detection#DETECTION_BASE} class, extends @{Base#BASE} +-- 1) @{Functional.Detection#DETECTION_BASE} class, extends @{Core.Base#BASE} -- ========================================================== --- The @{Detection#DETECTION_BASE} class defines the core functions to administer detected objects. --- The @{Detection#DETECTION_BASE} class will detect objects within the battle zone for a list of @{Group}s detecting targets following (a) detection method(s). +-- The @{Functional.Detection#DETECTION_BASE} class defines the core functions to administer detected objects. +-- The @{Functional.Detection#DETECTION_BASE} class will detect objects within the battle zone for a list of @{Group}s detecting targets following (a) detection method(s). -- -- 1.1) DETECTION_BASE constructor -- ------------------------------- --- Construct a new DETECTION_BASE instance using the @{Detection#DETECTION_BASE.New}() method. +-- Construct a new DETECTION_BASE instance using the @{Functional.Detection#DETECTION_BASE.New}() method. -- -- 1.2) DETECTION_BASE initialization -- ---------------------------------- @@ -20,46 +20,46 @@ -- -- Use the following functions to report the objects it detected using the methods Visual, Optical, Radar, IRST, RWR, DLINK: -- --- * @{Detection#DETECTION_BASE.InitDetectVisual}(): Detected using Visual. --- * @{Detection#DETECTION_BASE.InitDetectOptical}(): Detected using Optical. --- * @{Detection#DETECTION_BASE.InitDetectRadar}(): Detected using Radar. --- * @{Detection#DETECTION_BASE.InitDetectIRST}(): Detected using IRST. --- * @{Detection#DETECTION_BASE.InitDetectRWR}(): Detected using RWR. --- * @{Detection#DETECTION_BASE.InitDetectDLINK}(): Detected using DLINK. +-- * @{Functional.Detection#DETECTION_BASE.InitDetectVisual}(): Detected using Visual. +-- * @{Functional.Detection#DETECTION_BASE.InitDetectOptical}(): Detected using Optical. +-- * @{Functional.Detection#DETECTION_BASE.InitDetectRadar}(): Detected using Radar. +-- * @{Functional.Detection#DETECTION_BASE.InitDetectIRST}(): Detected using IRST. +-- * @{Functional.Detection#DETECTION_BASE.InitDetectRWR}(): Detected using RWR. +-- * @{Functional.Detection#DETECTION_BASE.InitDetectDLINK}(): Detected using DLINK. -- -- 1.3) Obtain objects detected by DETECTION_BASE -- ---------------------------------------------- --- DETECTION_BASE builds @{Set}s of objects detected. These @{Set#SET_BASE}s can be retrieved using the method @{Detection#DETECTION_BASE.GetDetectedSets}(). --- The method will return a list (table) of @{Set#SET_BASE} objects. +-- DETECTION_BASE builds @{Set}s of objects detected. These @{Core.Set#SET_BASE}s can be retrieved using the method @{Functional.Detection#DETECTION_BASE.GetDetectedSets}(). +-- The method will return a list (table) of @{Core.Set#SET_BASE} objects. -- -- === -- --- 2) @{Detection#DETECTION_AREAS} class, extends @{Detection#DETECTION_BASE} +-- 2) @{Functional.Detection#DETECTION_AREAS} class, extends @{Functional.Detection#DETECTION_BASE} -- =============================================================================== --- The @{Detection#DETECTION_AREAS} class will detect units within the battle zone for a list of @{Group}s detecting targets following (a) detection method(s), --- and will build a list (table) of @{Set#SET_UNIT}s containing the @{Unit#UNIT}s detected. +-- The @{Functional.Detection#DETECTION_AREAS} class will detect units within the battle zone for a list of @{Group}s detecting targets following (a) detection method(s), +-- and will build a list (table) of @{Core.Set#SET_UNIT}s containing the @{Wrapper.Unit#UNIT}s detected. -- The class is group the detected units within zones given a DetectedZoneRange parameter. -- A set with multiple detected zones will be created as there are groups of units detected. -- -- 2.1) Retrieve the Detected Unit sets and Detected Zones -- ------------------------------------------------------- --- The DetectedUnitSets methods are implemented in @{Detection#DECTECTION_BASE} and the DetectedZones methods is implemented in @{Detection#DETECTION_AREAS}. +-- The DetectedUnitSets methods are implemented in @{Functional.Detection#DECTECTION_BASE} and the DetectedZones methods is implemented in @{Functional.Detection#DETECTION_AREAS}. -- --- Retrieve the DetectedUnitSets with the method @{Detection#DETECTION_BASE.GetDetectedSets}(). A table will be return of @{Set#SET_UNIT}s. --- To understand the amount of sets created, use the method @{Detection#DETECTION_BASE.GetDetectedSetCount}(). --- If you want to obtain a specific set from the DetectedSets, use the method @{Detection#DETECTION_BASE.GetDetectedSet}() with a given index. +-- Retrieve the DetectedUnitSets with the method @{Functional.Detection#DETECTION_BASE.GetDetectedSets}(). A table will be return of @{Core.Set#SET_UNIT}s. +-- To understand the amount of sets created, use the method @{Functional.Detection#DETECTION_BASE.GetDetectedSetCount}(). +-- If you want to obtain a specific set from the DetectedSets, use the method @{Functional.Detection#DETECTION_BASE.GetDetectedSet}() with a given index. -- --- Retrieve the formed @{Zone@ZONE_UNIT}s as a result of the grouping the detected units within the DetectionZoneRange, use the method @{Detection#DETECTION_BASE.GetDetectionZones}(). --- To understand the amount of zones created, use the method @{Detection#DETECTION_BASE.GetDetectionZoneCount}(). --- If you want to obtain a specific zone from the DetectedZones, use the method @{Detection#DETECTION_BASE.GetDetectionZone}() with a given index. +-- Retrieve the formed @{Zone@ZONE_UNIT}s as a result of the grouping the detected units within the DetectionZoneRange, use the method @{Functional.Detection#DETECTION_BASE.GetDetectionZones}(). +-- To understand the amount of zones created, use the method @{Functional.Detection#DETECTION_BASE.GetDetectionZoneCount}(). +-- If you want to obtain a specific zone from the DetectedZones, use the method @{Functional.Detection#DETECTION_BASE.GetDetectionZone}() with a given index. -- -- 1.4) Flare or Smoke detected units -- ---------------------------------- --- Use the methods @{Detection#DETECTION_AREAS.FlareDetectedUnits}() or @{Detection#DETECTION_AREAS.SmokeDetectedUnits}() to flare or smoke the detected units when a new detection has taken place. +-- Use the methods @{Functional.Detection#DETECTION_AREAS.FlareDetectedUnits}() or @{Functional.Detection#DETECTION_AREAS.SmokeDetectedUnits}() to flare or smoke the detected units when a new detection has taken place. -- -- 1.5) Flare or Smoke detected zones -- ---------------------------------- --- Use the methods @{Detection#DETECTION_AREAS.FlareDetectedZones}() or @{Detection#DETECTION_AREAS.SmokeDetectedZones}() to flare or smoke the detected zones when a new detection has taken place. +-- Use the methods @{Functional.Detection#DETECTION_AREAS.FlareDetectedZones}() or @{Functional.Detection#DETECTION_AREAS.SmokeDetectedZones}() to flare or smoke the detected zones when a new detection has taken place. -- -- === -- @@ -77,12 +77,12 @@ --- DETECTION_BASE class -- @type DETECTION_BASE --- @field Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Forward Air Controller role. --- @field DCSTypes#Distance DetectionRange The range till which targets are accepted to be detected. +-- @field Core.Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Forward Air Controller role. +-- @field Dcs.DCSTypes#Distance DetectionRange The range till which targets are accepted to be detected. -- @field #DETECTION_BASE.DetectedObjects DetectedObjects The list of detected objects. -- @field #table DetectedObjectsIdentified Map of the DetectedObjects identified. -- @field #number DetectionRun --- @extends Base#BASE +-- @extends Core.Base#BASE DETECTION_BASE = { ClassName = "DETECTION_BASE", DetectionSetGroup = nil, @@ -104,8 +104,8 @@ DETECTION_BASE = { --- DETECTION constructor. -- @param #DETECTION_BASE self --- @param Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Forward Air Controller role. --- @param DCSTypes#Distance DetectionRange The range till which targets are accepted to be detected. +-- @param Core.Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Forward Air Controller role. +-- @param Dcs.DCSTypes#Distance DetectionRange The range till which targets are accepted to be detected. -- @return #DETECTION_BASE self function DETECTION_BASE:New( DetectionSetGroup, DetectionRange ) @@ -245,7 +245,7 @@ function DETECTION_BASE:GetDetectedObject( ObjectName ) return nil end ---- Get the detected @{Set#SET_BASE}s. +--- Get the detected @{Core.Set#SET_BASE}s. -- @param #DETECTION_BASE self -- @return #DETECTION_BASE.DetectedSets DetectedSets function DETECTION_BASE:GetDetectedSets() @@ -266,7 +266,7 @@ end --- Get a SET of detected objects using a given numeric index. -- @param #DETECTION_BASE self -- @param #number Index --- @return Set#SET_BASE +-- @return Core.Set#SET_BASE function DETECTION_BASE:GetDetectedSet( Index ) local DetectionSet = self.DetectedSets[Index] @@ -279,7 +279,7 @@ end --- Get the detection Groups. -- @param #DETECTION_BASE self --- @return Group#GROUP +-- @return Wrapper.Group#GROUP function DETECTION_BASE:GetDetectionSetGroup() local DetectionSetGroup = self.DetectionSetGroup @@ -313,7 +313,7 @@ function DETECTION_BASE:Schedule( DelayTime, RepeatInterval ) end ---- Form @{Set}s of detected @{Unit#UNIT}s in an array of @{Set#SET_BASE}s. +--- Form @{Set}s of detected @{Wrapper.Unit#UNIT}s in an array of @{Core.Set#SET_BASE}s. -- @param #DETECTION_BASE self function DETECTION_BASE:_DetectionScheduler( SchedulerName ) self:F2( { SchedulerName } ) @@ -323,7 +323,7 @@ function DETECTION_BASE:_DetectionScheduler( SchedulerName ) self:UnIdentifyAllDetectedObjects() -- Resets the DetectedObjectsIdentified table for DetectionGroupID, DetectionGroupData in pairs( self.DetectionSetGroup:GetSet() ) do - local DetectionGroup = DetectionGroupData -- Group#GROUP + local DetectionGroup = DetectionGroupData -- Wrapper.Group#GROUP if DetectionGroup:IsAlive() then @@ -339,7 +339,7 @@ function DETECTION_BASE:_DetectionScheduler( SchedulerName ) ) for DetectionDetectedTargetID, DetectionDetectedTarget in pairs( DetectionDetectedTargets ) do - local DetectionObject = DetectionDetectedTarget.object -- DCSObject#Object + local DetectionObject = DetectionDetectedTarget.object -- Dcs.DCSWrapper.Object#Object self:T2( DetectionObject ) if DetectionObject and DetectionObject:isExist() and DetectionObject.id_ < 50000000 then @@ -393,9 +393,9 @@ end --- DETECTION_AREAS class -- @type DETECTION_AREAS --- @field DCSTypes#Distance DetectionZoneRange The range till which targets are grouped upon the first detected target. +-- @field Dcs.DCSTypes#Distance DetectionZoneRange The range till which targets are grouped upon the first detected target. -- @field #DETECTION_AREAS.DetectedAreas DetectedAreas A list of areas containing the set of @{Unit}s, @{Zone}s, the center @{Unit} within the zone, and ID of each area that was detected within a DetectionZoneRange. --- @extends Detection#DETECTION_BASE +-- @extends Functional.Detection#DETECTION_BASE DETECTION_AREAS = { ClassName = "DETECTION_AREAS", DetectedAreas = { n = 0 }, @@ -406,21 +406,21 @@ DETECTION_AREAS = { -- @list <#DETECTION_AREAS.DetectedArea> --- @type DETECTION_AREAS.DetectedArea --- @field Set#SET_UNIT Set -- The Set of Units in the detected area. --- @field Zone#ZONE_UNIT Zone -- The Zone of the detected area. +-- @field Core.Set#SET_UNIT Set -- The Set of Units in the detected area. +-- @field Core.Zone#ZONE_UNIT Zone -- The Zone of the detected area. -- @field #boolean Changed Documents if the detected area has changes. -- @field #table Changes A list of the changes reported on the detected area. (It is up to the user of the detected area to consume those changes). -- @field #number AreaID -- The identifier of the detected area. -- @field #boolean FriendliesNearBy Indicates if there are friendlies within the detected area. --- @field Unit#UNIT NearestFAC The nearest FAC near the Area. +-- @field Wrapper.Unit#UNIT NearestFAC The nearest FAC near the Area. --- DETECTION_AREAS constructor. --- @param Detection#DETECTION_AREAS self --- @param Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Forward Air Controller role. --- @param DCSTypes#Distance DetectionRange The range till which targets are accepted to be detected. --- @param DCSTypes#Distance DetectionZoneRange The range till which targets are grouped upon the first detected target. --- @return Detection#DETECTION_AREAS self +-- @param Functional.Detection#DETECTION_AREAS self +-- @param Core.Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Forward Air Controller role. +-- @param Dcs.DCSTypes#Distance DetectionRange The range till which targets are accepted to be detected. +-- @param Dcs.DCSTypes#Distance DetectionZoneRange The range till which targets are grouped upon the first detected target. +-- @return Functional.Detection#DETECTION_AREAS self function DETECTION_AREAS:New( DetectionSetGroup, DetectionRange, DetectionZoneRange ) -- Inherits from DETECTION_BASE @@ -439,8 +439,8 @@ function DETECTION_AREAS:New( DetectionSetGroup, DetectionRange, DetectionZoneRa end --- Add a detected @{#DETECTION_AREAS.DetectedArea}. --- @param Set#SET_UNIT Set -- The Set of Units in the detected area. --- @param Zone#ZONE_UNIT Zone -- The Zone of the detected area. +-- @param Core.Set#SET_UNIT Set -- The Set of Units in the detected area. +-- @param Core.Zone#ZONE_UNIT Zone -- The Zone of the detected area. -- @return #DETECTION_AREAS.DetectedArea DetectedArea function DETECTION_AREAS:AddDetectedArea( Set, Zone ) local DetectedAreas = self:GetDetectedAreas() @@ -487,10 +487,10 @@ function DETECTION_AREAS:GetDetectedAreaCount() return DetectedAreaCount end ---- Get the @{Set#SET_UNIT} of a detecttion area using a given numeric index. +--- Get the @{Core.Set#SET_UNIT} of a detecttion area using a given numeric index. -- @param #DETECTION_AREAS self -- @param #number Index --- @return Set#SET_UNIT DetectedSet +-- @return Core.Set#SET_UNIT DetectedSet function DETECTION_AREAS:GetDetectedSet( Index ) local DetectedSetUnit = self.DetectedAreas[Index].Set @@ -501,10 +501,10 @@ function DETECTION_AREAS:GetDetectedSet( Index ) return nil end ---- Get the @{Zone#ZONE_UNIT} of a detection area using a given numeric index. +--- Get the @{Core.Zone#ZONE_UNIT} of a detection area using a given numeric index. -- @param #DETECTION_AREAS self -- @param #number Index --- @return Zone#ZONE_UNIT DetectedZone +-- @return Core.Zone#ZONE_UNIT DetectedZone function DETECTION_AREAS:GetDetectedZone( Index ) local DetectedZone = self.DetectedAreas[Index].Zone @@ -517,11 +517,11 @@ end --- Background worker function to determine if there are friendlies nearby ... -- @param #DETECTION_AREAS self --- @param Unit#UNIT ReportUnit +-- @param Wrapper.Unit#UNIT ReportUnit function DETECTION_AREAS:ReportFriendliesNearBy( ReportGroupData ) self:F2() - local DetectedArea = ReportGroupData.DetectedArea -- Detection#DETECTION_AREAS.DetectedArea + local DetectedArea = ReportGroupData.DetectedArea -- Functional.Detection#DETECTION_AREAS.DetectedArea local DetectedSet = ReportGroupData.DetectedArea.Set local DetectedZone = ReportGroupData.DetectedArea.Zone local DetectedZoneUnit = DetectedZone.ZoneUNIT @@ -537,15 +537,15 @@ function DETECTION_AREAS:ReportFriendliesNearBy( ReportGroupData ) } - --- @param DCSUnit#Unit FoundDCSUnit - -- @param Group#GROUP ReportGroup + --- @param Dcs.DCSWrapper.Unit#Unit FoundDCSUnit + -- @param Wrapper.Group#GROUP ReportGroup -- @param Set#SET_GROUP ReportSetGroup local FindNearByFriendlies = function( FoundDCSUnit, ReportGroupData ) - local DetectedArea = ReportGroupData.DetectedArea -- Detection#DETECTION_AREAS.DetectedArea + local DetectedArea = ReportGroupData.DetectedArea -- Functional.Detection#DETECTION_AREAS.DetectedArea local DetectedSet = ReportGroupData.DetectedArea.Set local DetectedZone = ReportGroupData.DetectedArea.Zone - local DetectedZoneUnit = DetectedZone.ZoneUNIT -- Unit#UNIT + local DetectedZoneUnit = DetectedZone.ZoneUNIT -- Wrapper.Unit#UNIT local ReportSetGroup = ReportGroupData.ReportSetGroup local EnemyCoalition = DetectedZoneUnit:GetCoalition() @@ -588,7 +588,7 @@ function DETECTION_AREAS:CalculateThreatLevelA2G( DetectedArea ) local MaxThreatLevelA2G = 0 for UnitName, UnitData in pairs( DetectedArea.Set:GetSet() ) do - local ThreatUnit = UnitData -- Unit#UNIT + local ThreatUnit = UnitData -- Wrapper.Unit#UNIT local ThreatLevelA2G = ThreatUnit:GetThreatLevel() if ThreatLevelA2G > MaxThreatLevelA2G then MaxThreatLevelA2G = ThreatLevelA2G @@ -603,7 +603,7 @@ end --- Find the nearest FAC of the DetectedArea. -- @param #DETECTION_AREAS self -- @param #DETECTION_AREAS.DetectedArea DetectedArea --- @return Unit#UNIT The nearest FAC unit +-- @return Wrapper.Unit#UNIT The nearest FAC unit function DETECTION_AREAS:NearestFAC( DetectedArea ) local NearestFAC = nil @@ -611,7 +611,7 @@ function DETECTION_AREAS:NearestFAC( DetectedArea ) for FACGroupName, FACGroupData in pairs( self.DetectionSetGroup:GetSet() ) do for FACUnit, FACUnitData in pairs( FACGroupData:GetUnits() ) do - local FACUnit = FACUnitData -- Unit#UNIT + local FACUnit = FACUnitData -- Wrapper.Unit#UNIT if FACUnit:IsActive() then local Vec3 = FACUnit:GetVec3() local PointVec3 = POINT_VEC3:NewFromVec3( Vec3 ) @@ -829,7 +829,7 @@ function DETECTION_AREAS:CreateDetectionSets() -- Then search for a new center area unit within the set. Note that the new area unit candidate must be within the area range. for DetectedUnitName, DetectedUnitData in pairs( DetectedSet:GetSet() ) do - local DetectedUnit = DetectedUnitData -- Unit#UNIT + local DetectedUnit = DetectedUnitData -- Wrapper.Unit#UNIT local DetectedObject = self:GetDetectedObject( DetectedUnit.UnitName ) -- The DetectedObject can be nil when the DetectedUnit is not alive anymore or it is not in the DetectedObjects map. @@ -859,7 +859,7 @@ function DETECTION_AREAS:CreateDetectionSets() -- If a unit was not found in the set, remove it from the set. This may be added later to other existing or new sets. for DetectedUnitName, DetectedUnitData in pairs( DetectedSet:GetSet() ) do - local DetectedUnit = DetectedUnitData -- Unit#UNIT + local DetectedUnit = DetectedUnitData -- Wrapper.Unit#UNIT local DetectedObject = nil if DetectedUnit:IsAlive() then --self:E(DetectedUnit:GetName()) @@ -909,7 +909,7 @@ function DETECTION_AREAS:CreateDetectionSets() if DetectedObject then -- We found an unidentified unit outside of any existing detection area. - local DetectedUnit = UNIT:FindByName( DetectedUnitName ) -- Unit#UNIT + local DetectedUnit = UNIT:FindByName( DetectedUnitName ) -- Wrapper.Unit#UNIT local AddedToDetectionArea = false @@ -959,7 +959,7 @@ function DETECTION_AREAS:CreateDetectionSets() DetectedZone.ZoneUNIT:SmokeRed() end DetectedSet:ForEachUnit( - --- @param Unit#UNIT DetectedUnit + --- @param Wrapper.Unit#UNIT DetectedUnit function( DetectedUnit ) if DetectedUnit:IsAlive() then self:T( "Detected Set #" .. DetectedArea.AreaID .. ":" .. DetectedUnit:GetName() ) @@ -973,10 +973,10 @@ function DETECTION_AREAS:CreateDetectionSets() end ) if DETECTION_AREAS._FlareDetectedZones or self._FlareDetectedZones then - DetectedZone:FlareZone( POINT_VEC3.SmokeColor.White, 30, math.random( 0,90 ) ) + DetectedZone:FlareZone( SMOKECOLOR.White, 30, math.random( 0,90 ) ) end if DETECTION_AREAS._SmokeDetectedZones or self._SmokeDetectedZones then - DetectedZone:SmokeZone( POINT_VEC3.SmokeColor.White, 30 ) + DetectedZone:SmokeZone( SMOKECOLOR.White, 30 ) end end diff --git a/Moose Development/Moose/Escort.lua b/Moose Development/Moose/Functional/Escort.lua similarity index 93% rename from Moose Development/Moose/Escort.lua rename to Moose Development/Moose/Functional/Escort.lua index 69247c56e..96a28c453 100644 --- a/Moose Development/Moose/Escort.lua +++ b/Moose Development/Moose/Functional/Escort.lua @@ -80,7 +80,7 @@ -- ============================ -- Create a new SPAWN object with the @{#ESCORT.New} method: -- --- * @{#ESCORT.New}: Creates a new ESCORT object from a @{Group#GROUP} for a @{Client#CLIENT}, with an optional briefing text. +-- * @{#ESCORT.New}: Creates a new ESCORT object from a @{Wrapper.Group#GROUP} for a @{Wrapper.Client#CLIENT}, with an optional briefing text. -- -- ESCORT initialization methods. -- ============================== @@ -117,17 +117,17 @@ --- ESCORT class -- @type ESCORT --- @extends Base#BASE --- @field Client#CLIENT EscortClient --- @field Group#GROUP EscortGroup +-- @extends Core.Base#BASE +-- @field Wrapper.Client#CLIENT EscortClient +-- @field Wrapper.Group#GROUP EscortGroup -- @field #string EscortName -- @field #ESCORT.MODE EscortMode The mode the escort is in. --- @field Scheduler#SCHEDULER FollowScheduler The instance of the SCHEDULER class. +-- @field Core.Scheduler#SCHEDULER FollowScheduler The instance of the SCHEDULER class. -- @field #number FollowDistance The current follow distance. -- @field #boolean ReportTargets If true, nearby targets are reported. --- @Field DCSTypes#AI.Option.Air.val.ROE OptionROE Which ROE is set to the EscortGroup. --- @field DCSTypes#AI.Option.Air.val.REACTION_ON_THREAT OptionReactionOnThreat Which REACTION_ON_THREAT is set to the EscortGroup. --- @field Menu#MENU_CLIENT EscortMenuResumeMission +-- @Field Dcs.DCSTypes#AI.Option.Air.val.ROE OptionROE Which ROE is set to the EscortGroup. +-- @field Dcs.DCSTypes#AI.Option.Air.val.REACTION_ON_THREAT OptionReactionOnThreat Which REACTION_ON_THREAT is set to the EscortGroup. +-- @field Core.Menu#MENU_CLIENT EscortMenuResumeMission ESCORT = { ClassName = "ESCORT", EscortName = nil, -- The Escort Name @@ -161,8 +161,8 @@ ESCORT = { --- ESCORT class constructor for an AI group -- @param #ESCORT self --- @param Client#CLIENT EscortClient The client escorted by the EscortGroup. --- @param Group#GROUP EscortGroup The group AI escorting the EscortClient. +-- @param Wrapper.Client#CLIENT EscortClient The client escorted by the EscortGroup. +-- @param Wrapper.Group#GROUP EscortGroup The group AI escorting the EscortClient. -- @param #string EscortName Name of the escort. -- @param #string EscortBriefing A text showing the ESCORT briefing to the player. Note that if no EscortBriefing is provided, the default briefing will be shown. -- @return #ESCORT self @@ -179,8 +179,8 @@ function ESCORT:New( EscortClient, EscortGroup, EscortName, EscortBriefing ) local self = BASE:Inherit( self, BASE:New() ) self:F( { EscortClient, EscortGroup, EscortName } ) - self.EscortClient = EscortClient -- Client#CLIENT - self.EscortGroup = EscortGroup -- Group#GROUP + self.EscortClient = EscortClient -- Wrapper.Client#CLIENT + self.EscortGroup = EscortGroup -- Wrapper.Group#GROUP self.EscortName = EscortName self.EscortBriefing = EscortBriefing @@ -268,7 +268,7 @@ end --- Defines a menu slot to let the escort Join and Follow you at a certain distance. -- This menu will appear under **Navigation**. -- @param #ESCORT self --- @param DCSTypes#Distance Distance The distance in meters that the escort needs to follow the client. +-- @param Dcs.DCSTypes#Distance Distance The distance in meters that the escort needs to follow the client. -- @return #ESCORT function ESCORT:MenuFollowAt( Distance ) self:F(Distance) @@ -293,8 +293,8 @@ end --- Defines a menu slot to let the escort hold at their current position and stay low with a specified height during a specified time in seconds. -- This menu will appear under **Hold position**. -- @param #ESCORT self --- @param DCSTypes#Distance Height Optional parameter that sets the height in meters to let the escort orbit at the current location. The default value is 30 meters. --- @param DCSTypes#Time Seconds Optional parameter that lets the escort orbit at the current position for a specified time. (not implemented yet). The default value is 0 seconds, meaning, that the escort will orbit forever until a sequent command is given. +-- @param Dcs.DCSTypes#Distance Height Optional parameter that sets the height in meters to let the escort orbit at the current location. The default value is 30 meters. +-- @param Dcs.DCSTypes#Time Seconds Optional parameter that lets the escort orbit at the current position for a specified time. (not implemented yet). The default value is 0 seconds, meaning, that the escort will orbit forever until a sequent command is given. -- @param #string MenuTextFormat Optional parameter that shows the menu option text. The text string is formatted, and should contain two %d tokens in the string. The first for the Height, the second for the Time (if given). If no text is given, the default text will be displayed. -- @return #ESCORT -- TODO: Implement Seconds parameter. Challenge is to first develop the "continue from last activity" function. @@ -355,8 +355,8 @@ end --- Defines a menu slot to let the escort hold at the client position and stay low with a specified height during a specified time in seconds. -- This menu will appear under **Navigation**. -- @param #ESCORT self --- @param DCSTypes#Distance Height Optional parameter that sets the height in meters to let the escort orbit at the current location. The default value is 30 meters. --- @param DCSTypes#Time Seconds Optional parameter that lets the escort orbit at the current position for a specified time. (not implemented yet). The default value is 0 seconds, meaning, that the escort will orbit forever until a sequent command is given. +-- @param Dcs.DCSTypes#Distance Height Optional parameter that sets the height in meters to let the escort orbit at the current location. The default value is 30 meters. +-- @param Dcs.DCSTypes#Time Seconds Optional parameter that lets the escort orbit at the current position for a specified time. (not implemented yet). The default value is 0 seconds, meaning, that the escort will orbit forever until a sequent command is given. -- @param #string MenuTextFormat Optional parameter that shows the menu option text. The text string is formatted, and should contain one or two %d tokens in the string. The first for the Height, the second for the Time (if given). If no text is given, the default text will be displayed. -- @return #ESCORT -- TODO: Implement Seconds parameter. Challenge is to first develop the "continue from last activity" function. @@ -416,8 +416,8 @@ end --- Defines a menu slot to let the escort scan for targets at a certain height for a certain time in seconds. -- This menu will appear under **Scan targets**. -- @param #ESCORT self --- @param DCSTypes#Distance Height Optional parameter that sets the height in meters to let the escort orbit at the current location. The default value is 30 meters. --- @param DCSTypes#Time Seconds Optional parameter that lets the escort orbit at the current position for a specified time. (not implemented yet). The default value is 0 seconds, meaning, that the escort will orbit forever until a sequent command is given. +-- @param Dcs.DCSTypes#Distance Height Optional parameter that sets the height in meters to let the escort orbit at the current location. The default value is 30 meters. +-- @param Dcs.DCSTypes#Time Seconds Optional parameter that lets the escort orbit at the current position for a specified time. (not implemented yet). The default value is 0 seconds, meaning, that the escort will orbit forever until a sequent command is given. -- @param #string MenuTextFormat Optional parameter that shows the menu option text. The text string is formatted, and should contain one or two %d tokens in the string. The first for the Height, the second for the Time (if given). If no text is given, the default text will be displayed. -- @return #ESCORT function ESCORT:MenuScanForTargets( Height, Seconds, MenuTextFormat ) @@ -494,10 +494,10 @@ function ESCORT:MenuFlare( MenuTextFormat ) if not self.EscortMenuFlare then self.EscortMenuFlare = MENU_CLIENT:New( self.EscortClient, MenuText, self.EscortMenuReportNavigation, ESCORT._Flare, { ParamSelf = self } ) - self.EscortMenuFlareGreen = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release green flare", self.EscortMenuFlare, ESCORT._Flare, { ParamSelf = self, ParamColor = UNIT.FlareColor.Green, ParamMessage = "Released a green flare!" } ) - self.EscortMenuFlareRed = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release red flare", self.EscortMenuFlare, ESCORT._Flare, { ParamSelf = self, ParamColor = UNIT.FlareColor.Red, ParamMessage = "Released a red flare!" } ) - self.EscortMenuFlareWhite = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release white flare", self.EscortMenuFlare, ESCORT._Flare, { ParamSelf = self, ParamColor = UNIT.FlareColor.White, ParamMessage = "Released a white flare!" } ) - self.EscortMenuFlareYellow = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release yellow flare", self.EscortMenuFlare, ESCORT._Flare, { ParamSelf = self, ParamColor = UNIT.FlareColor.Yellow, ParamMessage = "Released a yellow flare!" } ) + self.EscortMenuFlareGreen = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release green flare", self.EscortMenuFlare, ESCORT._Flare, { ParamSelf = self, ParamColor = FLARECOLOR.Green, ParamMessage = "Released a green flare!" } ) + self.EscortMenuFlareRed = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release red flare", self.EscortMenuFlare, ESCORT._Flare, { ParamSelf = self, ParamColor = FLARECOLOR.Red, ParamMessage = "Released a red flare!" } ) + self.EscortMenuFlareWhite = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release white flare", self.EscortMenuFlare, ESCORT._Flare, { ParamSelf = self, ParamColor = FLARECOLOR.White, ParamMessage = "Released a white flare!" } ) + self.EscortMenuFlareYellow = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release yellow flare", self.EscortMenuFlare, ESCORT._Flare, { ParamSelf = self, ParamColor = FLARECOLOR.Yellow, ParamMessage = "Released a yellow flare!" } ) end return self @@ -542,7 +542,7 @@ end -- This menu will appear under **Report targets**. -- Note that if a report targets menu is not specified, no targets will be detected by the escort, and the attack and assisted attack menus will not be displayed. -- @param #ESCORT self --- @param DCSTypes#Time Seconds Optional parameter that lets the escort report their current detected targets after specified time interval in seconds. The default time is 30 seconds. +-- @param Dcs.DCSTypes#Time Seconds Optional parameter that lets the escort report their current detected targets after specified time interval in seconds. The default time is 30 seconds. -- @return #ESCORT function ESCORT:MenuReportTargets( Seconds ) self:F( { Seconds } ) @@ -664,8 +664,8 @@ function ESCORT._HoldPosition( MenuParam ) local EscortGroup = self.EscortGroup local EscortClient = self.EscortClient - local OrbitGroup = MenuParam.ParamOrbitGroup -- Group#GROUP - local OrbitUnit = OrbitGroup:GetUnit(1) -- Unit#UNIT + local OrbitGroup = MenuParam.ParamOrbitGroup -- Wrapper.Group#GROUP + local OrbitUnit = OrbitGroup:GetUnit(1) -- Wrapper.Unit#UNIT local OrbitHeight = MenuParam.ParamHeight local OrbitSeconds = MenuParam.ParamSeconds -- Not implemented yet @@ -714,10 +714,10 @@ function ESCORT._JoinUpAndFollow( MenuParam ) end --- JoinsUp and Follows a CLIENT. --- @param Escort#ESCORT self --- @param Group#GROUP EscortGroup --- @param Client#CLIENT EscortClient --- @param DCSTypes#Distance Distance +-- @param Functional.Escort#ESCORT self +-- @param Wrapper.Group#GROUP EscortGroup +-- @param Wrapper.Client#CLIENT EscortClient +-- @param Dcs.DCSTypes#Distance Distance function ESCORT:JoinUpAndFollow( EscortGroup, EscortClient, Distance ) self:F( { EscortGroup, EscortClient, Distance } ) @@ -832,7 +832,7 @@ function ESCORT._ScanTargets( MenuParam ) end ---- @param Group#GROUP EscortGroup +--- @param Wrapper.Group#GROUP EscortGroup function _Resume( EscortGroup ) env.info( '_Resume' ) @@ -851,7 +851,7 @@ function ESCORT._AttackTarget( MenuParam ) local EscortGroup = self.EscortGroup local EscortClient = self.EscortClient - local AttackUnit = MenuParam.ParamUnit -- Unit#UNIT + local AttackUnit = MenuParam.ParamUnit -- Wrapper.Unit#UNIT self.FollowScheduler:Stop() @@ -892,7 +892,7 @@ function ESCORT._AssistTarget( MenuParam ) local EscortGroup = self.EscortGroup local EscortClient = self.EscortClient local EscortGroupAttack = MenuParam.ParamEscortGroup - local AttackUnit = MenuParam.ParamUnit -- Unit#UNIT + local AttackUnit = MenuParam.ParamUnit -- Wrapper.Unit#UNIT self.FollowScheduler:Stop() @@ -981,7 +981,7 @@ end function ESCORT:RegisterRoute() self:F() - local EscortGroup = self.EscortGroup -- Group#GROUP + local EscortGroup = self.EscortGroup -- Wrapper.Group#GROUP local TaskPoints = EscortGroup:GetTaskRoute() @@ -990,7 +990,7 @@ function ESCORT:RegisterRoute() return TaskPoints end ---- @param Escort#ESCORT self +--- @param Functional.Escort#ESCORT self function ESCORT:_FollowScheduler() self:F( { self.FollowDistance } ) diff --git a/Moose Development/Moose/MissileTrainer.lua b/Moose Development/Moose/Functional/MissileTrainer.lua similarity index 99% rename from Moose Development/Moose/MissileTrainer.lua rename to Moose Development/Moose/Functional/MissileTrainer.lua index 35098fe81..aead25452 100644 --- a/Moose Development/Moose/MissileTrainer.lua +++ b/Moose Development/Moose/Functional/MissileTrainer.lua @@ -2,7 +2,7 @@ -- -- === -- --- 1) @{MissileTrainer#MISSILETRAINER} class, extends @{Base#BASE} +-- 1) @{Functional.MissileTrainer#MISSILETRAINER} class, extends @{Core.Base#BASE} -- =============================================================== -- The @{#MISSILETRAINER} class uses the DCS world messaging system to be alerted of any missiles fired, and when a missile would hit your aircraft, -- the class will destroy the missile within a certain range, to avoid damage to your aircraft. @@ -83,8 +83,8 @@ --- The MISSILETRAINER class -- @type MISSILETRAINER --- @field Set#SET_CLIENT DBClients --- @extends Base#BASE +-- @field Core.Set#SET_CLIENT DBClients +-- @extends Core.Base#BASE MISSILETRAINER = { ClassName = "MISSILETRAINER", TrackingMissiles = {}, @@ -191,7 +191,7 @@ function MISSILETRAINER:New( Distance, Briefing ) -- self.DB:ForEachClient( --- --- @param Client#CLIENT Client +-- --- @param Wrapper.Client#CLIENT Client -- function( Client ) -- -- ... actions ... @@ -449,7 +449,7 @@ end --- Detects if an SA site was shot with an anti radiation missile. In this case, take evasive actions based on the skill level set within the ME. -- @param #MISSILETRAINER self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event function MISSILETRAINER:_EventShot( Event ) self:F( { Event } ) diff --git a/Moose Development/Moose/Movement.lua b/Moose Development/Moose/Functional/Movement.lua similarity index 100% rename from Moose Development/Moose/Movement.lua rename to Moose Development/Moose/Functional/Movement.lua diff --git a/Moose Development/Moose/Scoring.lua b/Moose Development/Moose/Functional/Scoring.lua similarity index 97% rename from Moose Development/Moose/Scoring.lua rename to Moose Development/Moose/Functional/Scoring.lua index 7144a07b6..3d1292c74 100644 --- a/Moose Development/Moose/Scoring.lua +++ b/Moose Development/Moose/Functional/Scoring.lua @@ -10,7 +10,7 @@ --- The Scoring class -- @type SCORING -- @field Players A collection of the current players that have joined the game. --- @extends Base#BASE +-- @extends Core.Base#BASE SCORING = { ClassName = "SCORING", ClassID = 0, @@ -100,7 +100,7 @@ end --- Track DEAD or CRASH events for the scoring. -- @param #SCORING self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event function SCORING:_EventOnDeadOrCrash( Event ) self:F( { Event } ) @@ -265,8 +265,8 @@ end --- Registers Scores the players completing a Mission Task. -- @param #SCORING self --- @param Mission#MISSION Mission --- @param Unit#UNIT PlayerUnit +-- @param Tasking.Mission#MISSION Mission +-- @param Wrapper.Unit#UNIT PlayerUnit -- @param #string Text -- @param #number Score function SCORING:_AddMissionTaskScore( Mission, PlayerUnit, Text, Score ) @@ -275,18 +275,20 @@ function SCORING:_AddMissionTaskScore( Mission, PlayerUnit, Text, Score ) local MissionName = Mission:GetName() self:F( { Mission:GetName(), PlayerUnit.UnitName, PlayerName, Text, Score } ) + + local PlayerData = self.Players[PlayerName] - if not self.Players[PlayerName].Mission[MissionName] then - self.Players[PlayerName].Mission[MissionName] = {} - self.Players[PlayerName].Mission[MissionName].ScoreTask = 0 - self.Players[PlayerName].Mission[MissionName].ScoreMission = 0 + if not PlayerData.Mission[MissionName] then + PlayerData.Mission[MissionName] = {} + PlayerData.Mission[MissionName].ScoreTask = 0 + PlayerData.Mission[MissionName].ScoreMission = 0 end self:T( PlayerName ) - self:T( self.Players[PlayerName].Mission[MissionName] ) + self:T( PlayerData.Mission[MissionName] ) - self.Players[PlayerName].Score = self.Players[PlayerName].Score + Score - self.Players[PlayerName].Mission[MissionName].ScoreTask = self.Players[PlayerName].Mission[MissionName].ScoreTask + Score + PlayerData.Score = self.Players[PlayerName].Score + Score + PlayerData.Mission[MissionName].ScoreTask = self.Players[PlayerName].Mission[MissionName].ScoreTask + Score MESSAGE:New( "Player '" .. PlayerName .. "' has " .. Text .. " in Mission '" .. MissionName .. "'. " .. Score .. " task score!", @@ -298,18 +300,20 @@ end --- Registers Mission Scores for possible multiple players that contributed in the Mission. -- @param #SCORING self --- @param Mission#MISSION Mission --- @param Unit#UNIT PlayerUnit +-- @param Tasking.Mission#MISSION Mission +-- @param Wrapper.Unit#UNIT PlayerUnit -- @param #string Text -- @param #number Score function SCORING:_AddMissionScore( Mission, Text, Score ) local MissionName = Mission:GetName() - self:F( { Mission, Text, Score } ) + self:E( { Mission, Text, Score } ) + self:E( self.Players ) for PlayerName, PlayerData in pairs( self.Players ) do + self:E( PlayerData ) if PlayerData.Mission[MissionName] then PlayerData.Score = PlayerData.Score + Score @@ -326,7 +330,7 @@ end --- Handles the OnHit event for the scoring. -- @param #SCORING self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event function SCORING:_EventOnHit( Event ) self:F( { Event } ) diff --git a/Moose Development/Moose/Sead.lua b/Moose Development/Moose/Functional/Sead.lua similarity index 99% rename from Moose Development/Moose/Sead.lua rename to Moose Development/Moose/Functional/Sead.lua index 25e9ceff2..3b05ac9a0 100644 --- a/Moose Development/Moose/Sead.lua +++ b/Moose Development/Moose/Functional/Sead.lua @@ -5,7 +5,7 @@ --- The SEAD class -- @type SEAD --- @extends Base#BASE +-- @extends Core.Base#BASE SEAD = { ClassName = "SEAD", TargetSkill = { diff --git a/Moose Development/Moose/Spawn.lua b/Moose Development/Moose/Functional/Spawn.lua similarity index 63% rename from Moose Development/Moose/Spawn.lua rename to Moose Development/Moose/Functional/Spawn.lua index 721a658e1..2f6b0bec5 100644 --- a/Moose Development/Moose/Spawn.lua +++ b/Moose Development/Moose/Functional/Spawn.lua @@ -1,6 +1,6 @@ --- This module contains the SPAWN class. -- --- 1) @{Spawn#SPAWN} class, extends @{Base#BASE} +-- 1) @{Functional.Spawn#SPAWN} class, extends @{Core.Base#BASE} -- ============================================= -- The @{#SPAWN} class allows to spawn dynamically new groups, based on pre-defined initialization settings, modifying the behaviour when groups are spawned. -- For each group to be spawned, within the mission editor, a group has to be created with the "late activation flag" set. We call this group the *"Spawn Template"* of the SPAWN object. @@ -26,9 +26,10 @@ -- -- 1.1) SPAWN construction methods -- ------------------------------- --- Create a new SPAWN object with the @{#SPAWN.New} or the @{#SPAWN.NewWithAlias} methods: +-- Create a new SPAWN object with the @{#SPAWN.New}() or the @{#SPAWN.NewWithAlias}() methods: -- --- * @{#SPAWN.New}: Creates a new SPAWN object taking the name of the group that represents the GROUP Template (definition). +-- * @{#SPAWN.New}(): Creates a new SPAWN object taking the name of the group that represents the GROUP Template (definition). +-- * @{#SPAWN.NewWithAlias}(): Creates a new SPAWN object taking the name of the group that represents the GROUP Template (definition), and gives each spawned @{Group} an different name. -- -- It is important to understand how the SPAWN class works internally. The SPAWN object created will contain internally a list of groups that will be spawned and that are already spawned. -- The initialization methods will modify this list of groups so that when a group gets spawned, ALL information is already prepared when spawning. This is done for performance reasons. @@ -36,27 +37,29 @@ -- -- 1.2) SPAWN initialization methods -- --------------------------------- --- A spawn object will behave differently based on the usage of initialization methods: +-- A spawn object will behave differently based on the usage of **initialization** methods, which all start with the **Init** prefix: -- --- * @{#SPAWN.Limit}: Limits the amount of groups that can be alive at the same time and that can be dynamically spawned. --- * @{#SPAWN.RandomizeRoute}: Randomize the routes of spawned groups, and for air groups also optionally the height. --- * @{#SPAWN.RandomizeTemplate}: Randomize the group templates so that when a new group is spawned, a random group template is selected from one of the templates defined. --- * @{#SPAWN.Uncontrolled}: Spawn plane groups uncontrolled. --- * @{#SPAWN.Array}: Make groups visible before they are actually activated, and order these groups like a batallion in an array. --- * @{#SPAWN.InitRepeat}: Re-spawn groups when they land at the home base. Similar methods are @{#SPAWN.InitRepeatOnLanding} and @{#SPAWN.InitRepeatOnEngineShutDown}. +-- * @{#SPAWN.InitLimit}(): Limits the amount of groups that can be alive at the same time and that can be dynamically spawned. +-- * @{#SPAWN.InitRandomizeRoute}(): Randomize the routes of spawned groups, and for air groups also optionally the height. +-- * @{#SPAWN.InitRandomizeTemplate}(): Randomize the group templates so that when a new group is spawned, a random group template is selected from one of the templates defined. +-- * @{#SPAWN.InitUncontrolled}(): Spawn plane groups uncontrolled. +-- * @{#SPAWN.InitArray}(): Make groups visible before they are actually activated, and order these groups like a batallion in an array. +-- * @{#SPAWN.InitRepeat}(): Re-spawn groups when they land at the home base. Similar methods are @{#SPAWN.InitRepeatOnLanding} and @{#SPAWN.InitRepeatOnEngineShutDown}. +-- * @{#SPAWN.InitRandomizeUnits}(): Randomizes the @{Unit}s in the @{Group} that is spawned within a **radius band**, given an Outer and Inner radius. +-- * @{#SPAWN.InitRandomizeZones}(): Randomizes the spawning between a predefined list of @{Zone}s that are declared using this function. Each zone can be given a probability factor. -- -- 1.3) SPAWN spawning methods -- --------------------------- -- Groups can be spawned at different times and methods: -- --- * @{#SPAWN.Spawn}: Spawn one new group based on the last spawned index. --- * @{#SPAWN.ReSpawn}: Re-spawn a group based on a given index. --- * @{#SPAWN.SpawnScheduled}: Spawn groups at scheduled but randomized intervals. You can use @{#SPAWN.SpawnScheduleStart} and @{#SPAWN.SpawnScheduleStop} to start and stop the schedule respectively. --- * @{#SPAWN.SpawnFromVec3}: Spawn a new group from a Vec3 coordinate. (The group will can be spawned at a point in the air). --- * @{#SPAWN.SpawnFromVec2}: Spawn a new group from a Vec2 coordinate. (The group will be spawned at land height ). --- * @{#SPAWN.SpawnFromStatic}: Spawn a new group from a structure, taking the position of a @{STATIC}. --- * @{#SPAWN.SpawnFromUnit}: Spawn a new group taking the position of a @{UNIT}. --- * @{#SPAWN.SpawnInZone}: Spawn a new group in a @{ZONE}. +-- * @{#SPAWN.Spawn}(): Spawn one new group based on the last spawned index. +-- * @{#SPAWN.ReSpawn}(): Re-spawn a group based on a given index. +-- * @{#SPAWN.SpawnScheduled}(): Spawn groups at scheduled but randomized intervals. You can use @{#SPAWN.SpawnScheduleStart}() and @{#SPAWN.SpawnScheduleStop}() to start and stop the schedule respectively. +-- * @{#SPAWN.SpawnFromVec3}(): Spawn a new group from a Vec3 coordinate. (The group will can be spawned at a point in the air). +-- * @{#SPAWN.SpawnFromVec2}(): Spawn a new group from a Vec2 coordinate. (The group will be spawned at land height ). +-- * @{#SPAWN.SpawnFromStatic}(): Spawn a new group from a structure, taking the position of a @{Static}. +-- * @{#SPAWN.SpawnFromUnit}(): Spawn a new group taking the position of a @{Unit}. +-- * @{#SPAWN.SpawnInZone}(): Spawn a new group in a @{Zone}. -- -- Note that @{#SPAWN.Spawn} and @{#SPAWN.ReSpawn} return a @{GROUP#GROUP.New} object, that contains a reference to the DCSGroup object. -- You can use the @{GROUP} object to do further actions with the DCSGroup. @@ -67,32 +70,132 @@ -- Every time a SPAWN object spawns a new GROUP object, a reference to the GROUP object is added to an internal table of GROUPS. -- SPAWN provides methods to iterate through that internal GROUP object reference table: -- --- * @{#SPAWN.GetFirstAliveGroup}: Will find the first alive GROUP it has spawned, and return the alive GROUP object and the first Index where the first alive GROUP object has been found. --- * @{#SPAWN.GetNextAliveGroup}: Will find the next alive GROUP object from a given Index, and return a reference to the alive GROUP object and the next Index where the alive GROUP has been found. --- * @{#SPAWN.GetLastAliveGroup}: Will find the last alive GROUP object, and will return a reference to the last live GROUP object and the last Index where the last alive GROUP object has been found. +-- * @{#SPAWN.GetFirstAliveGroup}(): Will find the first alive GROUP it has spawned, and return the alive GROUP object and the first Index where the first alive GROUP object has been found. +-- * @{#SPAWN.GetNextAliveGroup}(): Will find the next alive GROUP object from a given Index, and return a reference to the alive GROUP object and the next Index where the alive GROUP has been found. +-- * @{#SPAWN.GetLastAliveGroup}(): Will find the last alive GROUP object, and will return a reference to the last live GROUP object and the last Index where the last alive GROUP object has been found. -- --- You can use the methods @{#SPAWN.GetFirstAliveGroup} and sequently @{#SPAWN.GetNextAliveGroup} to iterate through the alive GROUPS within the SPAWN object, and to actions... See the respective methods for an example. --- The method @{#SPAWN.GetGroupFromIndex} will return the GROUP object reference from the given Index, dead or alive... +-- You can use the methods @{#SPAWN.GetFirstAliveGroup}() and sequently @{#SPAWN.GetNextAliveGroup}() to iterate through the alive GROUPS within the SPAWN object, and to actions... See the respective methods for an example. +-- The method @{#SPAWN.GetGroupFromIndex}() will return the GROUP object reference from the given Index, dead or alive... -- -- 1.5) SPAWN object cleaning -- -------------------------- -- Sometimes, it will occur during a mission run-time, that ground or especially air objects get damaged, and will while being damged stop their activities, while remaining alive. -- In such cases, the SPAWN object will just sit there and wait until that group gets destroyed, but most of the time it won't, -- and it may occur that no new groups are or can be spawned as limits are reached. --- To prevent this, a @{#SPAWN.CleanUp} initialization method has been defined that will silently monitor the status of each spawned group. +-- To prevent this, a @{#SPAWN.InitCleanUp}() initialization method has been defined that will silently monitor the status of each spawned group. -- Once a group has a velocity = 0, and has been waiting for a defined interval, that group will be cleaned or removed from run-time. -- There is a catch however :-) If a damaged group has returned to an airbase within the coalition, that group will not be considered as "lost"... -- In such a case, when the inactive group is cleaned, a new group will Re-spawned automatically. -- This models AI that has succesfully returned to their airbase, to restart their combat activities. --- Check the @{#SPAWN.CleanUp} for further info. +-- Check the @{#SPAWN.InitCleanUp}() for further info. +-- +-- 1.6) Catch the @{Group} spawn event in a callback function! +-- ----------------------------------------------------------- +-- When using the SpawnScheduled method, new @{Group}s are created following the schedule timing parameters. +-- When a new @{Group} is spawned, you maybe want to execute actions with that group spawned at the spawn event. +-- To SPAWN class supports this functionality through the @{#SPAWN.OnSpawnGroup}( **function( SpawnedGroup ) end ** ) method, which takes a function as a parameter that you can define locally. +-- Whenever a new @{Group} is spawned, the given function is called, and the @{Group} that was just spawned, is given as a parameter. +-- As a result, your spawn event handling function requires one parameter to be declared, which will contain the spawned @{Group} object. +-- A coding example is provided at the description of the @{#SPAWN.OnSpawnGroup}( **function( SpawnedGroup ) end ** ) method. +-- +-- ==== +-- +-- **API CHANGE HISTORY** +-- ====================== +-- +-- The underlying change log documents the API changes. Please read this carefully. The following notation is used: +-- +-- * **Added** parts are expressed in bold type face. +-- * _Removed_ parts are expressed in italic type face. +-- +-- Hereby the change log: +-- +-- 2016-08-15: SPAWN:**InitCleanUp**( SpawnCleanUpInterval ) replaces SPAWN:_CleanUp_( SpawnCleanUpInterval ) +-- +-- * Want to ensure that the methods starting with **Init** are the first called methods before any _Spawn_ method is called! +-- * This notation makes it now more clear which methods are initialization methods and which methods are Spawn enablement methods. +-- +-- 2016-08-15: SPAWN:**InitRandomizeZones( SpawnZones )** added. +-- +-- * This method provides the functionality to randomize the spawning of the Groups at a given list of zones of different types. +-- +-- 2016-08-14: SPAWN:**OnSpawnGroup**( SpawnCallBackFunction, ... ) replaces SPAWN:_SpawnFunction_( SpawnCallBackFunction, ... ). +-- +-- 2016-08-14: SPAWN.SpawnInZone( Zone, __RandomizeGroup__, SpawnIndex ) replaces SpawnInZone( Zone, _RandomizeUnits, OuterRadius, InnerRadius,_ SpawnIndex ). +-- +-- * The RandomizeUnits, OuterRadius and InnerRadius have been replaced with a new method @{#SPAWN.InitRandomizeUnits}( RandomizeUnits, OuterRadius, InnerRadius ). +-- * A new parameter RandomizeGroup to reflect the randomization of the starting position of the Spawned @{Group}. +-- +-- 2016-08-14: SPAWN.SpawnFromVec3( Vec3, SpawnIndex ) replaces SpawnFromVec3( Vec3, _RandomizeUnits, OuterRadius, InnerRadius,_ SpawnIndex ): +-- +-- * The RandomizeUnits, OuterRadius and InnerRadius have been replaced with a new method @{#SPAWN.InitRandomizeUnits}( RandomizeUnits, OuterRadius, InnerRadius ). +-- * A new parameter RandomizeGroup to reflect the randomization of the starting position of the Spawned @{Group}. +-- +-- 2016-08-14: SPAWN.SpawnFromVec2( Vec2, SpawnIndex ) replaces SpawnFromVec2( Vec2, _RandomizeUnits, OuterRadius, InnerRadius,_ SpawnIndex ): +-- +-- * The RandomizeUnits, OuterRadius and InnerRadius have been replaced with a new method @{#SPAWN.InitRandomizeUnits}( RandomizeUnits, OuterRadius, InnerRadius ). +-- * A new parameter RandomizeGroup to reflect the randomization of the starting position of the Spawned @{Group}. +-- +-- 2016-08-14: SPAWN.SpawnFromUnit( SpawnUnit, SpawnIndex ) replaces SpawnFromUnit( SpawnUnit, _RandomizeUnits, OuterRadius, InnerRadius,_ SpawnIndex ): +-- +-- * The RandomizeUnits, OuterRadius and InnerRadius have been replaced with a new method @{#SPAWN.InitRandomizeUnits}( RandomizeUnits, OuterRadius, InnerRadius ). +-- * A new parameter RandomizeGroup to reflect the randomization of the starting position of the Spawned @{Group}. +-- +-- 2016-08-14: SPAWN.SpawnFromUnit( SpawnUnit, SpawnIndex ) replaces SpawnFromStatic( SpawnStatic, _RandomizeUnits, OuterRadius, InnerRadius,_ SpawnIndex ): +-- +-- * The RandomizeUnits, OuterRadius and InnerRadius have been replaced with a new method @{#SPAWN.InitRandomizeUnits}( RandomizeUnits, OuterRadius, InnerRadius ). +-- * A new parameter RandomizeGroup to reflect the randomization of the starting position of the Spawned @{Group}. +-- +-- 2016-08-14: SPAWN.**InitRandomizeUnits( RandomizeUnits, OuterRadius, InnerRadius )** added: +-- +-- * This method enables the randomization of units at the first route point in a radius band at a spawn event. +-- +-- 2016-08-14: SPAWN.**Init**Limit( SpawnMaxUnitsAlive, SpawnMaxGroups ) replaces SPAWN._Limit_( SpawnMaxUnitsAlive, SpawnMaxGroups ): +-- +-- * Want to ensure that the methods starting with **Init** are the first called methods before any _Spawn_ method is called! +-- * This notation makes it now more clear which methods are initialization methods and which methods are Spawn enablement methods. +-- +-- 2016-08-14: SPAWN.**Init**Array( SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY ) replaces SPAWN._Array_( SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY ). +-- +-- * Want to ensure that the methods starting with **Init** are the first called methods before any _Spawn_ method is called! +-- * This notation makes it now more clear which methods are initialization methods and which methods are Spawn enablement methods. +-- +-- 2016-08-14: SPAWN.**Init**RandomizeRoute( SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight ) replaces SPAWN._RandomizeRoute_( SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight ). +-- +-- * Want to ensure that the methods starting with **Init** are the first called methods before any _Spawn_ method is called! +-- * This notation makes it now more clear which methods are initialization methods and which methods are Spawn enablement methods. +-- +-- 2016-08-14: SPAWN.**Init**RandomizeTemplate( SpawnTemplatePrefixTable ) replaces SPAWN._RandomizeTemplate_( SpawnTemplatePrefixTable ). +-- +-- * Want to ensure that the methods starting with **Init** are the first called methods before any _Spawn_ method is called! +-- * This notation makes it now more clear which methods are initialization methods and which methods are Spawn enablement methods. +-- +-- 2016-08-14: SPAWN.**Init**UnControlled() replaces SPAWN._UnControlled_(). +-- +-- * Want to ensure that the methods starting with **Init** are the first called methods before any _Spawn_ method is called! +-- * This notation makes it now more clear which methods are initialization methods and which methods are Spawn enablement methods. +-- +-- === +-- +-- AUTHORS and CONTRIBUTIONS +-- ========================= +-- +-- ### Contributions: +-- +-- * **Aaron**: Posed the idea for Group position randomization at SpawnInZone and make the Unit randomization separate from the Group randomization. +-- +-- ### Authors: +-- +-- * **FlightControl**: Design & Programming -- -- -- @module Spawn --- @author FlightControl + + --- SPAWN Class -- @type SPAWN --- @extends Base#BASE +-- @extends Core.Base#BASE -- @field ClassName -- @field #string SpawnTemplatePrefix -- @field #string SpawnAliasPrefix @@ -100,15 +203,18 @@ -- @field #number MaxAliveUnits -- @field #number SpawnIndex -- @field #number MaxAliveGroups +-- @field #SPAWN.SpawnZoneTable SpawnZoneTable SPAWN = { ClassName = "SPAWN", SpawnTemplatePrefix = nil, SpawnAliasPrefix = nil, } +--- @type SPAWN.SpawnZoneTable +-- @list SpawnZone ---- Creates the main object to spawn a GROUP defined in the DCS ME. +--- Creates the main object to spawn a @{Group} defined in the DCS ME. -- @param #SPAWN self -- @param #string SpawnTemplatePrefix is the name of the Group in the ME that defines the Template. Each new group will have the name starting with SpawnTemplatePrefix. -- @return #SPAWN @@ -183,7 +289,7 @@ end --- Limits the Maximum amount of Units that can be alive at the same time, and the maximum amount of groups that can be spawned. -- Note that this method is exceptionally important to balance the performance of the mission. Depending on the machine etc, a mission can only process a maximum amount of units. --- If the time interval must be short, but there should not be more Units or Groups alive than a maximum amount of units, then this function should be used... +-- If the time interval must be short, but there should not be more Units or Groups alive than a maximum amount of units, then this method should be used... -- When a @{#SPAWN.New} is executed and the limit of the amount of units alive is reached, then no new spawn will happen of the group, until some of these units of the spawn object will be destroyed. -- @param #SPAWN self -- @param #number SpawnMaxUnitsAlive The maximum amount of units that can be alive at runtime. @@ -195,8 +301,8 @@ end -- -- NATO helicopters engaging in the battle field. -- -- This helicopter group consists of one Unit. So, this group will SPAWN maximum 2 groups simultaneously within the DCSRTE. -- -- There will be maximum 24 groups spawned during the whole mission lifetime. --- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):Limit( 2, 24 ) -function SPAWN:Limit( SpawnMaxUnitsAlive, SpawnMaxGroups ) +-- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitLimit( 2, 24 ) +function SPAWN:InitLimit( SpawnMaxUnitsAlive, SpawnMaxGroups ) self:F( { self.SpawnTemplatePrefix, SpawnMaxUnitsAlive, SpawnMaxGroups } ) self.SpawnMaxUnitsAlive = SpawnMaxUnitsAlive -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time. @@ -224,8 +330,8 @@ end -- -- The KA-50 has waypoints Start point ( =0 or SP ), 1, 2, 3, 4, End point (= 5 or DP). -- -- Waypoints 2 and 3 will only be randomized. The others will remain on their original position with each new spawn of the helicopter. -- -- The randomization of waypoint 2 and 3 will take place within a radius of 2000 meters. --- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):RandomizeRoute( 2, 2, 2000 ) -function SPAWN:RandomizeRoute( SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight ) +-- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitRandomizeRoute( 2, 2, 2000 ) +function SPAWN:InitRandomizeRoute( SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight ) self:F( { self.SpawnTemplatePrefix, SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight } ) self.SpawnRandomizeRoute = true @@ -241,9 +347,34 @@ function SPAWN:RandomizeRoute( SpawnStartPoint, SpawnEndPoint, SpawnRadius, Spaw return self end +--- Randomizes the UNITs that are spawned within a radius band given an Outer and Inner radius. +-- @param #SPAWN self +-- @param #boolean RandomizeUnits If true, SPAWN will perform the randomization of the @{UNIT}s position within the group between a given outer and inner radius. +-- @param Dcs.DCSTypes#Distance OuterRadius (optional) The outer radius in meters where the new group will be spawned. +-- @param Dcs.DCSTypes#Distance InnerRadius (optional) The inner radius in meters where the new group will NOT be spawned. +-- @return #SPAWN +-- @usage +-- -- NATO helicopters engaging in the battle field. +-- -- The KA-50 has waypoints Start point ( =0 or SP ), 1, 2, 3, 4, End point (= 5 or DP). +-- -- Waypoints 2 and 3 will only be randomized. The others will remain on their original position with each new spawn of the helicopter. +-- -- The randomization of waypoint 2 and 3 will take place within a radius of 2000 meters. +-- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitRandomizeRoute( 2, 2, 2000 ) +function SPAWN:InitRandomizeUnits( RandomizeUnits, OuterRadius, InnerRadius ) + self:F( { self.SpawnTemplatePrefix, RandomizeUnits, OuterRadius, InnerRadius } ) ---- This function is rather complicated to understand. But I'll try to explain. --- This function becomes useful when you need to spawn groups with random templates of groups defined within the mission editor, + self.SpawnRandomizeUnits = RandomizeUnits or false + self.SpawnOuterRadius = OuterRadius or 0 + self.SpawnInnerRadius = InnerRadius or 0 + + for GroupID = 1, self.SpawnMaxGroups do + self:_RandomizeRoute( GroupID ) + end + + return self +end + +--- This method is rather complicated to understand. But I'll try to explain. +-- This method becomes useful when you need to spawn groups with random templates of groups defined within the mission editor, -- but they will all follow the same Template route and have the same prefix name. -- In other words, this method randomizes between a defined set of groups the template to be used for each new spawn of a group. -- @param #SPAWN self @@ -258,10 +389,10 @@ end -- Spawn_US_Platoon = { 'US Tank Platoon 1', 'US Tank Platoon 2', 'US Tank Platoon 3', 'US Tank Platoon 4', 'US Tank Platoon 5', -- 'US Tank Platoon 6', 'US Tank Platoon 7', 'US Tank Platoon 8', 'US Tank Platoon 9', 'US Tank Platoon 10', -- 'US Tank Platoon 11', 'US Tank Platoon 12', 'US Tank Platoon 13' } --- Spawn_US_Platoon_Left = SPAWN:New( 'US Tank Platoon Left' ):Limit( 12, 150 ):Schedule( 200, 0.4 ):RandomizeTemplate( Spawn_US_Platoon ):RandomizeRoute( 3, 3, 2000 ) --- Spawn_US_Platoon_Middle = SPAWN:New( 'US Tank Platoon Middle' ):Limit( 12, 150 ):Schedule( 200, 0.4 ):RandomizeTemplate( Spawn_US_Platoon ):RandomizeRoute( 3, 3, 2000 ) --- Spawn_US_Platoon_Right = SPAWN:New( 'US Tank Platoon Right' ):Limit( 12, 150 ):Schedule( 200, 0.4 ):RandomizeTemplate( Spawn_US_Platoon ):RandomizeRoute( 3, 3, 2000 ) -function SPAWN:RandomizeTemplate( SpawnTemplatePrefixTable ) +-- Spawn_US_Platoon_Left = SPAWN:New( 'US Tank Platoon Left' ):InitLimit( 12, 150 ):Schedule( 200, 0.4 ):InitRandomizeTemplate( Spawn_US_Platoon ):InitRandomizeRoute( 3, 3, 2000 ) +-- Spawn_US_Platoon_Middle = SPAWN:New( 'US Tank Platoon Middle' ):InitLimit( 12, 150 ):Schedule( 200, 0.4 ):InitRandomizeTemplate( Spawn_US_Platoon ):InitRandomizeRoute( 3, 3, 2000 ) +-- Spawn_US_Platoon_Right = SPAWN:New( 'US Tank Platoon Right' ):InitLimit( 12, 150 ):Schedule( 200, 0.4 ):InitRandomizeTemplate( Spawn_US_Platoon ):InitRandomizeRoute( 3, 3, 2000 ) +function SPAWN:InitRandomizeTemplate( SpawnTemplatePrefixTable ) self:F( { self.SpawnTemplatePrefix, SpawnTemplatePrefixTable } ) self.SpawnTemplatePrefixTable = SpawnTemplatePrefixTable @@ -274,12 +405,33 @@ function SPAWN:RandomizeTemplate( SpawnTemplatePrefixTable ) return self end +--TODO: Add example. +--- This method provides the functionality to randomize the spawning of the Groups at a given list of zones of different types. +-- @param #SPAWN self +-- @param #table SpawnZoneTable A table with @{Zone} objects. If this table is given, then each spawn will be executed within the given list of @{Zone}s objects. +-- @return #SPAWN +-- @usage +-- -- NATO Tank Platoons invading Gori. +-- -- Choose between 3 different zones for each new SPAWN the Group to be executed, regardless of the zone type. +function SPAWN:InitRandomizeZones( SpawnZoneTable ) + self:F( { self.SpawnTemplatePrefix, SpawnZoneTable } ) + + self.SpawnZoneTable = SpawnZoneTable + self.SpawnRandomizeZones = true + + for SpawnGroupID = 1, self.SpawnMaxGroups do + self:_RandomizeZones( SpawnGroupID ) + end + + return self +end + --- For planes and helicopters, when these groups go home and land on their home airbases and farps, they normally would taxi to the parking spot, shut-down their engines and wait forever until the Group is removed by the runtime environment. --- This function is used to re-spawn automatically (so no extra call is needed anymore) the same group after it has landed. +-- This method is used to re-spawn automatically (so no extra call is needed anymore) the same group after it has landed. -- This will enable a spawned group to be re-spawned after it lands, until it is destroyed... -- Note: When the group is respawned, it will re-spawn from the original airbase where it took off. -- So ensure that the routes for groups that respawn, always return to the original airbase, or players may get confused ... @@ -288,7 +440,7 @@ end -- @usage -- -- RU Su-34 - AI Ship Attack -- -- Re-SPAWN the Group(s) after each landing and Engine Shut-Down automatically. --- SpawnRU_SU34 = SPAWN:New( 'TF1 RU Su-34 Krymsk@AI - Attack Ships' ):Schedule( 2, 3, 1800, 0.4 ):SpawnUncontrolled():RandomizeRoute( 1, 1, 3000 ):RepeatOnEngineShutDown() +-- SpawnRU_SU34 = SPAWN:New( 'TF1 RU Su-34 Krymsk@AI - Attack Ships' ):Schedule( 2, 3, 1800, 0.4 ):SpawnUncontrolled():InitRandomizeRoute( 1, 1, 3000 ):RepeatOnEngineShutDown() function SPAWN:InitRepeat() self:F( { self.SpawnTemplatePrefix, self.SpawnIndex } ) @@ -333,11 +485,15 @@ end -- @param #string SpawnCleanUpInterval The interval to check for inactive groups within seconds. -- @return #SPAWN self -- @usage Spawn_Helicopter:CleanUp( 20 ) -- CleanUp the spawning of the helicopters every 20 seconds when they become inactive. -function SPAWN:CleanUp( SpawnCleanUpInterval ) +function SPAWN:InitCleanUp( SpawnCleanUpInterval ) self:F( { self.SpawnTemplatePrefix, SpawnCleanUpInterval } ) self.SpawnCleanUpInterval = SpawnCleanUpInterval self.SpawnCleanUpTimeStamps = {} + + local SpawnGroup, SpawnCursor = self:GetFirstAliveGroup() + self:T( { "CleanUp Scheduler:", SpawnGroup } ) + --self.CleanUpFunction = routines.scheduleFunction( self._SpawnCleanUpScheduler, { self }, timer.getTime() + 1, SpawnCleanUpInterval ) self.CleanUpScheduler = SCHEDULER:New( self, self._SpawnCleanUpScheduler, {}, 1, SpawnCleanUpInterval, 0.2 ) return self @@ -355,8 +511,8 @@ end -- @return #SPAWN self -- @usage -- -- Define an array of Groups. --- Spawn_BE_Ground = SPAWN:New( 'BE Ground' ):Limit( 2, 24 ):Visible( 90, "Diamond", 10, 100, 50 ) -function SPAWN:Array( SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY ) +-- Spawn_BE_Ground = SPAWN:New( 'BE Ground' ):InitLimit( 2, 24 ):InitArray( 90, "Diamond", 10, 100, 50 ) +function SPAWN:InitArray( SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY ) self:F( { self.SpawnTemplatePrefix, SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY } ) self.SpawnVisible = true -- When the first Spawn executes, all the Groups need to be made visible before start. @@ -416,7 +572,7 @@ end --- Will spawn a group based on the internal index. -- Note: Uses @{DATABASE} module defined in MOOSE. -- @param #SPAWN self --- @return Group#GROUP The group that was spawned. You can use this group for further actions. +-- @return Wrapper.Group#GROUP The group that was spawned. You can use this group for further actions. function SPAWN:Spawn() self:F( { self.SpawnTemplatePrefix, self.SpawnIndex, self.AliveUnits } ) @@ -427,7 +583,7 @@ end -- Note: Uses @{DATABASE} module defined in MOOSE. -- @param #SPAWN self -- @param #string SpawnIndex The index of the group to be spawned. --- @return Group#GROUP The group that was spawned. You can use this group for further actions. +-- @return Wrapper.Group#GROUP The group that was spawned. You can use this group for further actions. function SPAWN:ReSpawn( SpawnIndex ) self:F( { self.SpawnTemplatePrefix, SpawnIndex } ) @@ -450,7 +606,8 @@ end --- Will spawn a group with a specified index number. -- Uses @{DATABASE} global object defined in MOOSE. -- @param #SPAWN self --- @return Group#GROUP The group that was spawned. You can use this group for further actions. +-- @param #string SpawnIndex The index of the group to be spawned. +-- @return Wrapper.Group#GROUP The group that was spawned. You can use this group for further actions. function SPAWN:SpawnWithIndex( SpawnIndex ) self:F2( { SpawnTemplatePrefix = self.SpawnTemplatePrefix, SpawnIndex = SpawnIndex, AliveUnits = self.AliveUnits, SpawnMaxGroups = self.SpawnMaxGroups } ) @@ -459,20 +616,40 @@ function SPAWN:SpawnWithIndex( SpawnIndex ) if self.SpawnGroups[self.SpawnIndex].Visible then self.SpawnGroups[self.SpawnIndex].Group:Activate() else - _EVENTDISPATCHER:OnBirthForTemplate( self.SpawnGroups[self.SpawnIndex].SpawnTemplate, self._OnBirth, self ) - _EVENTDISPATCHER:OnCrashForTemplate( self.SpawnGroups[self.SpawnIndex].SpawnTemplate, self._OnDeadOrCrash, self ) - _EVENTDISPATCHER:OnDeadForTemplate( self.SpawnGroups[self.SpawnIndex].SpawnTemplate, self._OnDeadOrCrash, self ) + + local SpawnTemplate = self.SpawnGroups[self.SpawnIndex].SpawnTemplate + self:T( SpawnTemplate.name ) + + if SpawnTemplate then + + local PointVec3 = POINT_VEC3:New( SpawnTemplate.route.points[1].x, SpawnTemplate.route.points[1].alt, SpawnTemplate.route.points[1].y ) + self:T( { "Current point of ", self.SpawnTemplatePrefix, PointVec3 } ) + + -- If RandomizeUnits, then Randomize the formation at the start point. + if self.SpawnRandomizeUnits then + for UnitID = 1, #SpawnTemplate.units do + local RandomVec2 = PointVec3:GetRandomVec2InRadius( self.SpawnOuterRadius, self.SpawnInnerRadius ) + SpawnTemplate.units[UnitID].x = RandomVec2.x + SpawnTemplate.units[UnitID].y = RandomVec2.y + self:T( 'SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y ) + end + end + end + + _EVENTDISPATCHER:OnBirthForTemplate( SpawnTemplate, self._OnBirth, self ) + _EVENTDISPATCHER:OnCrashForTemplate( SpawnTemplate, self._OnDeadOrCrash, self ) + _EVENTDISPATCHER:OnDeadForTemplate( SpawnTemplate, self._OnDeadOrCrash, self ) if self.Repeat then - _EVENTDISPATCHER:OnTakeOffForTemplate( self.SpawnGroups[self.SpawnIndex].SpawnTemplate, self._OnTakeOff, self ) - _EVENTDISPATCHER:OnLandForTemplate( self.SpawnGroups[self.SpawnIndex].SpawnTemplate, self._OnLand, self ) + _EVENTDISPATCHER:OnTakeOffForTemplate( SpawnTemplate, self._OnTakeOff, self ) + _EVENTDISPATCHER:OnLandForTemplate( SpawnTemplate, self._OnLand, self ) end if self.RepeatOnEngineShutDown then - _EVENTDISPATCHER:OnEngineShutDownForTemplate( self.SpawnGroups[self.SpawnIndex].SpawnTemplate, self._OnEngineShutDown, self ) + _EVENTDISPATCHER:OnEngineShutDownForTemplate( SpawnTemplate, self._OnEngineShutDown, self ) end - self:T3( self.SpawnGroups[self.SpawnIndex].SpawnTemplate ) + self:T3( SpawnTemplate.name ) - self.SpawnGroups[self.SpawnIndex].Group = _DATABASE:Spawn( self.SpawnGroups[self.SpawnIndex].SpawnTemplate ) + self.SpawnGroups[self.SpawnIndex].Group = _DATABASE:Spawn( SpawnTemplate ) -- If there is a SpawnFunction hook defined, call it. if self.SpawnFunctionHook then @@ -520,7 +697,7 @@ function SPAWN:SpawnScheduled( SpawnTime, SpawnTimeVariation ) end --- Will re-start the spawning scheduler. --- Note: This function is only required to be called when the schedule was stopped. +-- Note: This method is only required to be called when the schedule was stopped. function SPAWN:SpawnScheduleStart() self:F( { self.SpawnTemplatePrefix } ) @@ -536,16 +713,27 @@ end --- Allows to place a CallFunction hook when a new group spawns. --- The provided function will be called when a new group is spawned, including its given parameters. --- The first parameter of the SpawnFunction is the @{Group#GROUP} that was spawned. +-- The provided method will be called when a new group is spawned, including its given parameters. +-- The first parameter of the SpawnFunction is the @{Wrapper.Group#GROUP} that was spawned. -- @param #SPAWN self --- @param #function SpawnFunctionHook The function to be called when a group spawns. +-- @param #function SpawnCallBackFunction The function to be called when a group spawns. -- @param SpawnFunctionArguments A random amount of arguments to be provided to the function when the group spawns. -- @return #SPAWN -function SPAWN:SpawnFunction( SpawnFunctionHook, ... ) - self:F( SpawnFunction ) +-- @usage +-- -- Declare SpawnObject and call a function when a new Group is spawned. +-- local SpawnObject = SPAWN +-- :New( "SpawnObject" ) +-- :InitLimit( 2, 10 ) +-- :OnSpawnGroup( +-- function( SpawnGroup ) +-- SpawnGroup:E( "I am spawned" ) +-- end +-- ) +-- :SpawnScheduled( 300, 0.3 ) +function SPAWN:OnSpawnGroup( SpawnCallBackFunction, ... ) + self:F( "OnSpawnGroup" ) - self.SpawnFunctionHook = SpawnFunctionHook + self.SpawnFunctionHook = SpawnCallBackFunction self.SpawnFunctionArguments = {} if arg then self.SpawnFunctionArguments = arg @@ -556,18 +744,16 @@ end --- Will spawn a group from a Vec3 in 3D space. --- This function is mostly advisable to be used if you want to simulate spawning units in the air, like helicopters or airplanes. +-- This method is mostly advisable to be used if you want to simulate spawning units in the air, like helicopters or airplanes. -- Note that each point in the route assigned to the spawning group is reset to the point of the spawn. -- You can use the returned group to further define the route to be followed. -- @param #SPAWN self --- @param DCSTypes#Vec3 Vec3 The Vec3 coordinates where to spawn the group. --- @param #number OuterRadius (Optional) The outer radius in meters where the new group will be spawned. --- @param #number InnerRadius (Optional) The inner radius in meters where the new group will NOT be spawned. --- @param #number SpawnIndex (Optional) The index which group to spawn within the given zone. --- @return Group#GROUP that was spawned. +-- @param Dcs.DCSTypes#Vec3 Vec3 The Vec3 coordinates where to spawn the group. +-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. +-- @return Wrapper.Group#GROUP that was spawned. -- @return #nil Nothing was spawned. -function SPAWN:SpawnFromVec3( Vec3, OuterRadius, InnerRadius, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, Vec3, OuterRadius, InnerRadius, SpawnIndex } ) +function SPAWN:SpawnFromVec3( Vec3, SpawnIndex ) + self:F( { self.SpawnTemplatePrefix, Vec3, SpawnIndex } ) local PointVec3 = POINT_VEC3:NewFromVec3( Vec3 ) self:T2(PointVec3) @@ -584,33 +770,30 @@ function SPAWN:SpawnFromVec3( Vec3, OuterRadius, InnerRadius, SpawnIndex ) if SpawnTemplate then self:T( { "Current point of ", self.SpawnTemplatePrefix, Vec3 } ) + + -- Translate the position of the Group Template to the Vec3. + for UnitID = 1, #SpawnTemplate.units do + self:T( 'Before Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y ) + local UnitTemplate = SpawnTemplate.units[UnitID] + local SX = UnitTemplate.x + local SY = UnitTemplate.y + local BX = SpawnTemplate.route.points[1].x + local BY = SpawnTemplate.route.points[1].y + local TX = Vec3.x + ( SX - BX ) + local TY = Vec3.z + ( SY - BY ) + SpawnTemplate.units[UnitID].x = TX + SpawnTemplate.units[UnitID].y = TY + SpawnTemplate.units[UnitID].alt = Vec3.y + self:T( 'After Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y ) + end SpawnTemplate.route.points[1].x = Vec3.x SpawnTemplate.route.points[1].y = Vec3.z SpawnTemplate.route.points[1].alt = Vec3.y - InnerRadius = InnerRadius or 0 - OuterRadius = OuterRadius or 0 - - -- Apply SpawnFormation - for UnitID = 1, #SpawnTemplate.units do - local RandomVec2 = PointVec3:GetRandomVec2InRadius( OuterRadius, InnerRadius ) - SpawnTemplate.units[UnitID].x = RandomVec2.x - SpawnTemplate.units[UnitID].y = RandomVec2.y - SpawnTemplate.units[UnitID].alt = Vec3.y - self:T( 'SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y ) - end - - -- TODO: Need to rework this. A spawn action should always be at the random point to start from. This move is not correct to be here. --- local RandomVec2 = PointVec3:GetRandomVec2InRadius( OuterRadius, InnerRadius ) --- local Point = {} --- Point.type = "Turning Point" --- Point.x = RandomVec2.x --- Point.y = RandomVec2.y --- Point.action = "Cone" --- Point.speed = 5 --- table.insert( SpawnTemplate.route.points, 2, Point ) - + SpawnTemplate.x = Vec3.x + SpawnTemplate.y = Vec3.z + return self:SpawnWithIndex( self.SpawnIndex ) end end @@ -619,93 +802,86 @@ function SPAWN:SpawnFromVec3( Vec3, OuterRadius, InnerRadius, SpawnIndex ) end --- Will spawn a group from a Vec2 in 3D space. --- This function is mostly advisable to be used if you want to simulate spawning groups on the ground from air units, like vehicles. +-- This method is mostly advisable to be used if you want to simulate spawning groups on the ground from air units, like vehicles. -- Note that each point in the route assigned to the spawning group is reset to the point of the spawn. -- You can use the returned group to further define the route to be followed. -- @param #SPAWN self --- @param DCSTypes#Vec2 Vec2 The Vec2 coordinates where to spawn the group. --- @param #number OuterRadius (Optional) The outer radius in meters where the new group will be spawned. --- @param #number InnerRadius (Optional) The inner radius in meters where the new group will NOT be spawned. --- @param #number SpawnIndex (Optional) The index which group to spawn within the given zone. --- @return Group#GROUP that was spawned. +-- @param Dcs.DCSTypes#Vec2 Vec2 The Vec2 coordinates where to spawn the group. +-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. +-- @return Wrapper.Group#GROUP that was spawned. -- @return #nil Nothing was spawned. -function SPAWN:SpawnFromVec2( Vec2, OuterRadius, InnerRadius, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, Vec2, OuterRadius, InnerRadius, SpawnIndex } ) +function SPAWN:SpawnFromVec2( Vec2, SpawnIndex ) + self:F( { self.SpawnTemplatePrefix, Vec2, SpawnIndex } ) local PointVec2 = POINT_VEC2:NewFromVec2( Vec2 ) - return self:SpawnFromVec3( PointVec2:GetVec3(), OuterRadius, InnerRadius, SpawnIndex ) + return self:SpawnFromVec3( PointVec2:GetVec3(), SpawnIndex ) end ---- Will spawn a group from a hosting unit. This function is mostly advisable to be used if you want to simulate spawning from air units, like helicopters, which are dropping infantry into a defined Landing Zone. +--- Will spawn a group from a hosting unit. This method is mostly advisable to be used if you want to simulate spawning from air units, like helicopters, which are dropping infantry into a defined Landing Zone. -- Note that each point in the route assigned to the spawning group is reset to the point of the spawn. -- You can use the returned group to further define the route to be followed. -- @param #SPAWN self --- @param Unit#UNIT HostUnit The air or ground unit dropping or unloading the group. --- @param #number OuterRadius (Optional) The outer radius in meters where the new group will be spawned. --- @param #number InnerRadius (Optional) The inner radius in meters where the new group will NOT be spawned. --- @param #number SpawnIndex (Optional) The index which group to spawn within the given zone. --- @return Group#GROUP that was spawned. +-- @param Wrapper.Unit#UNIT HostUnit The air or ground unit dropping or unloading the group. +-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. +-- @return Wrapper.Group#GROUP that was spawned. -- @return #nil Nothing was spawned. -function SPAWN:SpawnFromUnit( HostUnit, OuterRadius, InnerRadius, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, HostUnit, OuterRadius, InnerRadius, SpawnIndex } ) +function SPAWN:SpawnFromUnit( HostUnit, SpawnIndex ) + self:F( { self.SpawnTemplatePrefix, HostUnit, SpawnIndex } ) if HostUnit and HostUnit:IsAlive() then -- and HostUnit:getUnit(1):inAir() == false then - return self:SpawnFromVec3( HostUnit:GetVec3(), OuterRadius, InnerRadius, SpawnIndex ) + return self:SpawnFromVec3( HostUnit:GetVec3(), SpawnIndex ) end return nil end ---- Will spawn a group from a hosting static. This function is mostly advisable to be used if you want to simulate spawning from buldings and structures (static buildings). +--- Will spawn a group from a hosting static. This method is mostly advisable to be used if you want to simulate spawning from buldings and structures (static buildings). -- You can use the returned group to further define the route to be followed. -- @param #SPAWN self --- @param Static#STATIC HostStatic The static dropping or unloading the group. --- @param #number OuterRadius (Optional) The outer radius in meters where the new group will be spawned. --- @param #number InnerRadius (Optional) The inner radius in meters where the new group will NOT be spawned. --- @param #number SpawnIndex (Optional) The index which group to spawn within the given zone. --- @return Group#GROUP that was spawned. +-- @param Wrapper.Static#STATIC HostStatic The static dropping or unloading the group. +-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. +-- @return Wrapper.Group#GROUP that was spawned. -- @return #nil Nothing was spawned. -function SPAWN:SpawnFromStatic( HostStatic, OuterRadius, InnerRadius, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, HostStatic, OuterRadius, InnerRadius, SpawnIndex } ) +function SPAWN:SpawnFromStatic( HostStatic, SpawnIndex ) + self:F( { self.SpawnTemplatePrefix, HostStatic, SpawnIndex } ) if HostStatic and HostStatic:IsAlive() then - return self:SpawnFromVec3( HostStatic:GetVec3(), OuterRadius, InnerRadius, SpawnIndex ) + return self:SpawnFromVec3( HostStatic:GetVec3(), SpawnIndex ) end return nil end ---- Will spawn a Group within a given @{Zone#ZONE}. --- Once the group is spawned within the zone, it will continue on its route. --- The first waypoint (where the group is spawned) is replaced with the zone coordinates. +--- Will spawn a Group within a given @{Zone}. +-- The @{Zone} can be of any type derived from @{Core.Zone#ZONE_BASE}. +-- Once the @{Group} is spawned within the zone, the @{Group} will continue on its route. +-- The **first waypoint** (where the group is spawned) is replaced with the zone location coordinates. -- @param #SPAWN self --- @param Zone#ZONE Zone The zone where the group is to be spawned. --- @param #number ZoneRandomize (Optional) Set to true if you want to randomize the starting point in the zone. --- @param #number SpawnIndex (Optional) The index which group to spawn within the given zone. --- @return Group#GROUP that was spawned. +-- @param Core.Zone#ZONE Zone The zone where the group is to be spawned. +-- @param #boolean RandomizeGroup (optional) Randomization of the @{Group} position in the zone. +-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. +-- @return Wrapper.Group#GROUP that was spawned. -- @return #nil when nothing was spawned. -function SPAWN:SpawnInZone( Zone, ZoneRandomize, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, Zone, ZoneRandomize, SpawnIndex } ) +function SPAWN:SpawnInZone( Zone, RandomizeGroup, SpawnIndex ) + self:F( { self.SpawnTemplatePrefix, Zone, RandomizeGroup, SpawnIndex } ) if Zone then - if ZoneRandomize then - return self:SpawnFromVec2( Zone:GetVec2(), Zone:GetRadius(), 0, SpawnIndex ) + if RandomizeGroup then + return self:SpawnFromVec2( Zone:GetRandomVec2(), SpawnIndex ) else - return self:SpawnFromVec2( Zone:GetVec2(), 0, 0, SpawnIndex ) + return self:SpawnFromVec2( Zone:GetVec2(), SpawnIndex ) end end return nil end - - - ---- Will spawn a plane group in uncontrolled mode... +--- (AIR) Will spawn a plane group in uncontrolled mode... -- This will be similar to the uncontrolled flag setting in the ME. +-- @param #SPAWN self -- @return #SPAWN self -function SPAWN:UnControlled() +function SPAWN:InitUnControlled() self:F( { self.SpawnTemplatePrefix } ) self.SpawnUnControlled = true @@ -742,12 +918,12 @@ function SPAWN:SpawnGroupName( SpawnIndex ) end ---- Will find the first alive GROUP it has spawned, and return the alive GROUP object and the first Index where the first alive GROUP object has been found. +--- Will find the first alive @{Group} it has spawned, and return the alive @{Group} object and the first Index where the first alive @{Group} object has been found. -- @param #SPAWN self --- @return Group#GROUP, #number The GROUP object found, the new Index where the group was found. +-- @return Wrapper.Group#GROUP, #number The @{Group} object found, the new Index where the group was found. -- @return #nil, #nil When no group is found, #nil is returned. -- @usage --- -- Find the first alive GROUP object of the SpawnPlanes SPAWN object GROUP collection that it has spawned during the mission. +-- -- Find the first alive @{Group} object of the SpawnPlanes SPAWN object @{Group} collection that it has spawned during the mission. -- local GroupPlane, Index = SpawnPlanes:GetFirstAliveGroup() -- while GroupPlane ~= nil do -- -- Do actions with the GroupPlane object. @@ -767,13 +943,13 @@ function SPAWN:GetFirstAliveGroup() end ---- Will find the next alive GROUP object from a given Index, and return a reference to the alive GROUP object and the next Index where the alive GROUP has been found. +--- Will find the next alive @{Group} object from a given Index, and return a reference to the alive @{Group} object and the next Index where the alive @{Group} has been found. -- @param #SPAWN self --- @param #number SpawnIndexStart A Index holding the start position to search from. This function can also be used to find the first alive GROUP object from the given Index. --- @return Group#GROUP, #number The next alive GROUP object found, the next Index where the next alive GROUP object was found. --- @return #nil, #nil When no alive GROUP object is found from the start Index position, #nil is returned. +-- @param #number SpawnIndexStart A Index holding the start position to search from. This method can also be used to find the first alive @{Group} object from the given Index. +-- @return Wrapper.Group#GROUP, #number The next alive @{Group} object found, the next Index where the next alive @{Group} object was found. +-- @return #nil, #nil When no alive @{Group} object is found from the start Index position, #nil is returned. -- @usage --- -- Find the first alive GROUP object of the SpawnPlanes SPAWN object GROUP collection that it has spawned during the mission. +-- -- Find the first alive @{Group} object of the SpawnPlanes SPAWN object @{Group} collection that it has spawned during the mission. -- local GroupPlane, Index = SpawnPlanes:GetFirstAliveGroup() -- while GroupPlane ~= nil do -- -- Do actions with the GroupPlane object. @@ -793,12 +969,12 @@ function SPAWN:GetNextAliveGroup( SpawnIndexStart ) return nil, nil end ---- Will find the last alive GROUP object, and will return a reference to the last live GROUP object and the last Index where the last alive GROUP object has been found. +--- Will find the last alive @{Group} object, and will return a reference to the last live @{Group} object and the last Index where the last alive @{Group} object has been found. -- @param #SPAWN self --- @return Group#GROUP, #number The last alive GROUP object found, the last Index where the last alive GROUP object was found. --- @return #nil, #nil When no alive GROUP object is found, #nil is returned. +-- @return Wrapper.Group#GROUP, #number The last alive @{Group} object found, the last Index where the last alive @{Group} object was found. +-- @return #nil, #nil When no alive @{Group} object is found, #nil is returned. -- @usage --- -- Find the last alive GROUP object of the SpawnPlanes SPAWN object GROUP collection that it has spawned during the mission. +-- -- Find the last alive @{Group} object of the SpawnPlanes SPAWN object @{Group} collection that it has spawned during the mission. -- local GroupPlane, Index = SpawnPlanes:GetLastAliveGroup() -- if GroupPlane then -- GroupPlane can be nil!!! -- -- Do actions with the GroupPlane object. @@ -826,7 +1002,7 @@ end -- If no index is given, it will return the first group in the list. -- @param #SPAWN self -- @param #number SpawnIndex The index of the group to return. --- @return Group#GROUP self +-- @return Wrapper.Group#GROUP self function SPAWN:GetGroupFromIndex( SpawnIndex ) self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnIndex } ) @@ -846,7 +1022,7 @@ end -- The method will search for a #-mark, and will return the index behind the #-mark of the DCSUnit. -- It will return nil of no prefix was found. -- @param #SPAWN self --- @param DCSUnit#Unit DCSUnit The @{DCSUnit} to be searched. +-- @param Dcs.DCSWrapper.Unit#Unit DCSUnit The @{DCSUnit} to be searched. -- @return #string The prefix -- @return #nil Nothing found function SPAWN:_GetGroupIndexFromDCSUnit( DCSUnit ) @@ -868,7 +1044,7 @@ end -- The method will search for a #-mark, and will return the text before the #-mark. -- It will return nil of no prefix was found. -- @param #SPAWN self --- @param DCSUnit#UNIT DCSUnit The @{DCSUnit} to be searched. +-- @param Dcs.DCSWrapper.Unit#UNIT DCSUnit The @{DCSUnit} to be searched. -- @return #string The prefix -- @return #nil Nothing found function SPAWN:_GetPrefixFromDCSUnit( DCSUnit ) @@ -888,8 +1064,8 @@ end --- Return the group within the SpawnGroups collection with input a DCSUnit. -- @param #SPAWN self --- @param DCSUnit#Unit DCSUnit The @{DCSUnit} to be searched. --- @return Group#GROUP The Group +-- @param Dcs.DCSWrapper.Unit#Unit DCSUnit The @{DCSUnit} to be searched. +-- @return Wrapper.Group#GROUP The Group -- @return #nil Nothing found function SPAWN:_GetGroupFromDCSUnit( DCSUnit ) self:F3( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, DCSUnit } ) @@ -1038,8 +1214,6 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) for UnitID = 1, #SpawnTemplate.units do SpawnTemplate.units[UnitID].name = string.format( SpawnTemplate.name .. '-%02d', UnitID ) SpawnTemplate.units[UnitID].unitId = nil - SpawnTemplate.units[UnitID].x = SpawnTemplate.route.points[1].x - SpawnTemplate.units[UnitID].y = SpawnTemplate.route.points[1].y end self:T3( { "Template:", SpawnTemplate } ) @@ -1076,6 +1250,8 @@ function SPAWN:_RandomizeRoute( SpawnIndex ) end end + self:_RandomizeZones( SpawnIndex ) + return self end @@ -1106,6 +1282,57 @@ function SPAWN:_RandomizeTemplate( SpawnIndex ) return self end +--- Private method that randomizes the @{Zone}s where the Group will be spawned. +-- @param #SPAWN self +-- @param #number SpawnIndex +-- @return #SPAWN self +function SPAWN:_RandomizeZones( SpawnIndex ) + self:F( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeZones } ) + + if self.SpawnRandomizeZones then + local SpawnZone = nil -- Core.Zone#ZONE_BASE + while not SpawnZone do + self:T( { SpawnZoneTableCount = #self.SpawnZoneTable, self.SpawnZoneTable } ) + local ZoneID = math.random( #self.SpawnZoneTable ) + self:T( ZoneID ) + SpawnZone = self.SpawnZoneTable[ ZoneID ]:GetZoneMaybe() + end + + self:T( "Preparing Spawn in Zone", SpawnZone:GetName() ) + + local SpawnVec2 = SpawnZone:GetRandomVec2() + + self:T( { SpawnVec2 = SpawnVec2 } ) + + local SpawnTemplate = self.SpawnGroups[SpawnIndex].SpawnTemplate + + self:T( { Route = SpawnTemplate.route } ) + + for UnitID = 1, #SpawnTemplate.units do + local UnitTemplate = SpawnTemplate.units[UnitID] + self:T( 'Before Translation SpawnTemplate.units['..UnitID..'].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. UnitTemplate.y ) + local SX = UnitTemplate.x + local SY = UnitTemplate.y + local BX = SpawnTemplate.route.points[1].x + local BY = SpawnTemplate.route.points[1].y + local TX = SpawnVec2.x + ( SX - BX ) + local TY = SpawnVec2.y + ( SY - BY ) + UnitTemplate.x = TX + UnitTemplate.y = TY + -- TODO: Manage altitude based on landheight... + --SpawnTemplate.units[UnitID].alt = SpawnVec2: + self:T( 'After Translation SpawnTemplate.units['..UnitID..'].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. UnitTemplate.y ) + end + SpawnTemplate.x = SpawnVec2.x + SpawnTemplate.y = SpawnVec2.y + SpawnTemplate.route.points[1].x = SpawnVec2.x + SpawnTemplate.route.points[1].y = SpawnVec2.y + end + + return self + +end + function SPAWN:_TranslateRotate( SpawnIndex, SpawnRootX, SpawnRootY, SpawnX, SpawnY, SpawnAngle ) self:F( { self.SpawnTemplatePrefix, SpawnIndex, SpawnRootX, SpawnRootY, SpawnX, SpawnY, SpawnAngle } ) @@ -1149,7 +1376,7 @@ function SPAWN:_TranslateRotate( SpawnIndex, SpawnRootX, SpawnRootY, SpawnX, Spa return self end ---- Get the next index of the groups to be spawned. This function is complicated, as it is used at several spaces. +--- Get the next index of the groups to be spawned. This method is complicated, as it is used at several spaces. function SPAWN:_GetSpawnIndex( SpawnIndex ) self:F2( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnMaxGroups, self.SpawnMaxUnitsAlive, self.AliveUnits, #self.SpawnTemplate.units } ) @@ -1177,7 +1404,7 @@ end -- TODO Need to delete this... _DATABASE does this now ... --- @param #SPAWN self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event function SPAWN:_OnBirth( Event ) if timer.getTime0() < timer.getAbsTime() then @@ -1197,7 +1424,7 @@ end -- @todo Need to delete this... _DATABASE does this now ... --- @param #SPAWN self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event function SPAWN:_OnDeadOrCrash( Event ) self:F( self.SpawnTemplatePrefix, Event ) @@ -1284,32 +1511,64 @@ function SPAWN:_Scheduler() return true end +--- Schedules the CleanUp of Groups +-- @param #SPAWN self +-- @return #boolean True = Continue Scheduler function SPAWN:_SpawnCleanUpScheduler() self:F( { "CleanUp Scheduler:", self.SpawnTemplatePrefix } ) - local SpawnCursor - local SpawnGroup, SpawnCursor = self:GetFirstAliveGroup( SpawnCursor ) - - self:T( { "CleanUp Scheduler:", SpawnGroup } ) + local SpawnGroup, SpawnCursor = self:GetFirstAliveGroup() + self:T( { "CleanUp Scheduler:", SpawnGroup, SpawnCursor } ) while SpawnGroup do - - if SpawnGroup:AllOnGround() and SpawnGroup:GetMaxVelocity() < 1 then - if not self.SpawnCleanUpTimeStamps[SpawnGroup:GetName()] then - self.SpawnCleanUpTimeStamps[SpawnGroup:GetName()] = timer.getTime() - else - if self.SpawnCleanUpTimeStamps[SpawnGroup:GetName()] + self.SpawnCleanUpInterval < timer.getTime() then - self:T( { "CleanUp Scheduler:", "Cleaning:", SpawnGroup } ) - SpawnGroup:Destroy() - end - end - else - self.SpawnCleanUpTimeStamps[SpawnGroup:GetName()] = nil - end + + local SpawnUnits = SpawnGroup:GetUnits() + + for UnitID, UnitData in pairs( SpawnUnits ) do + + local SpawnUnit = UnitData -- Wrapper.Unit#UNIT + local SpawnUnitName = SpawnUnit:GetName() + + + self.SpawnCleanUpTimeStamps[SpawnUnitName] = self.SpawnCleanUpTimeStamps[SpawnUnitName] or {} + local Stamp = self.SpawnCleanUpTimeStamps[SpawnUnitName] + self:T( { SpawnUnitName, Stamp } ) + + if Stamp.Vec2 then + if SpawnUnit:InAir() == false and SpawnUnit:GetVelocityKMH() < 1 then + local NewVec2 = SpawnUnit:GetVec2() + if Stamp.Vec2.x == NewVec2.x and Stamp.Vec2.y == NewVec2.y then + -- If the plane is not moving, and is on the ground, assign it with a timestamp... + if Stamp.Time + self.SpawnCleanUpInterval < timer.getTime() then + self:T( { "CleanUp Scheduler:", "ReSpawning:", SpawnGroup:GetName() } ) + self:ReSpawn( SpawnCursor ) + Stamp.Vec2 = nil + Stamp.Time = nil + end + else + Stamp.Time = timer.getTime() + Stamp.Vec2 = SpawnUnit:GetVec2() + end + else + Stamp.Vec2 = nil + Stamp.Time = nil + end + else + if SpawnUnit:InAir() == false then + Stamp.Vec2 = SpawnUnit:GetVec2() + if SpawnUnit:GetVelocityKMH() < 1 then + Stamp.Time = timer.getTime() + end + else + Stamp.Time = nil + Stamp.Vec2 = nil + end + end + end SpawnGroup, SpawnCursor = self:GetNextAliveGroup( SpawnCursor ) - self:T( { "CleanUp Scheduler:", SpawnGroup } ) + self:T( { "CleanUp Scheduler:", SpawnGroup, SpawnCursor } ) end diff --git a/Moose Development/Moose/Moose.lua b/Moose Development/Moose/Moose.lua index ba20dc1d0..32b309b3b 100644 --- a/Moose Development/Moose/Moose.lua +++ b/Moose Development/Moose/Moose.lua @@ -1,71 +1,74 @@ --- The main include file for the MOOSE system. -Include.File( "Routines" ) -Include.File( "Utils" ) -Include.File( "Base" ) -Include.File( "Object" ) -Include.File( "Identifiable" ) -Include.File( "Positionable" ) -Include.File( "Controllable" ) -Include.File( "Scheduler" ) -Include.File( "Event" ) -Include.File( "Menu" ) -Include.File( "Group" ) -Include.File( "Unit" ) -Include.File( "Zone" ) -Include.File( "Client" ) -Include.File( "Static" ) -Include.File( "Airbase" ) -Include.File( "Database" ) -Include.File( "Set" ) -Include.File( "Point" ) -Include.File( "Moose" ) -Include.File( "Scoring" ) -Include.File( "Cargo" ) -Include.File( "Message" ) -Include.File( "Stage" ) -Include.File( "Task" ) -Include.File( "GoHomeTask" ) -Include.File( "DestroyBaseTask" ) -Include.File( "DestroyGroupsTask" ) -Include.File( "DestroyRadarsTask" ) -Include.File( "DestroyUnitTypesTask" ) -Include.File( "PickupTask" ) -Include.File( "DeployTask" ) -Include.File( "NoTask" ) -Include.File( "RouteTask" ) -Include.File( "Mission" ) -Include.File( "CleanUp" ) -Include.File( "Spawn" ) -Include.File( "Movement" ) -Include.File( "Sead" ) -Include.File( "Escort" ) -Include.File( "MissileTrainer" ) -Include.File( "PatrolZone" ) -Include.File( "AIBalancer" ) -Include.File( "AirbasePolice" ) +--- Core Routines +Include.File( "Utilities/Routines" ) +Include.File( "Utilities/Utils" ) -Include.File( "Detection" ) -Include.File( "DetectionManager" ) +--- Core Classes +Include.File( "Core/Base" ) +Include.File( "Core/Scheduler" ) +Include.File( "Core/ScheduleDispatcher") +Include.File( "Core/Event" ) +Include.File( "Core/Menu" ) +Include.File( "Core/Zone" ) +Include.File( "Core/Database" ) +Include.File( "Core/Set" ) +Include.File( "Core/Point" ) +Include.File( "Core/Message" ) +Include.File( "Core/Fsm" ) -Include.File( "StateMachine" ) +--- Wrapper Classes +Include.File( "Wrapper/Object" ) +Include.File( "Wrapper/Identifiable" ) +Include.File( "Wrapper/Positionable" ) +Include.File( "Wrapper/Controllable" ) +Include.File( "Wrapper/Group" ) +Include.File( "Wrapper/Unit" ) +Include.File( "Wrapper/Client" ) +Include.File( "Wrapper/Static" ) +Include.File( "Wrapper/Airbase" ) -Include.File( "Process" ) -Include.File( "Process_Assign" ) -Include.File( "Process_Route" ) -Include.File( "Process_Smoke" ) -Include.File( "Process_Destroy" ) -Include.File( "Process_JTAC" ) +--- Functional Classes +Include.File( "Functional/Scoring" ) +Include.File( "Functional/CleanUp" ) +Include.File( "Functional/Spawn" ) +Include.File( "Functional/Movement" ) +Include.File( "Functional/Sead" ) +Include.File( "Functional/Escort" ) +Include.File( "Functional/MissileTrainer" ) +Include.File( "Functional/AirbasePolice" ) +Include.File( "Functional/Detection" ) + +--- AI Classes +Include.File( "AI/AI_Balancer" ) +Include.File( "AI/AI_Patrol" ) +Include.File( "AI/AI_Cargo" ) + +--- Actions +Include.File( "Actions/Act_Assign" ) +Include.File( "Actions/Act_Route" ) +Include.File( "Actions/Act_Account" ) +Include.File( "Actions/Act_Assist" ) + +--- Task Handling Classes +Include.File( "Tasking/CommandCenter" ) +Include.File( "Tasking/Mission" ) +Include.File( "Tasking/Task" ) +Include.File( "Tasking/DetectionManager" ) +Include.File( "Tasking/Task_SEAD" ) +Include.File( "Tasking/Task_A2G" ) -Include.File( "Task" ) -Include.File( "Task_SEAD" ) -Include.File( "Task_A2G" ) -- The order of the declarations is important here. Don't touch it. --- Declare the event dispatcher based on the EVENT class -_EVENTDISPATCHER = EVENT:New() -- Event#EVENT +_EVENTDISPATCHER = EVENT:New() -- Core.Event#EVENT + +--- Declare the timer dispatcher based on the SCHEDULEDISPATCHER class +_SCHEDULEDISPATCHER = SCHEDULEDISPATCHER:New() -- Core.Timer#SCHEDULEDISPATCHER --- Declare the main database object, which is used internally by the MOOSE classes. _DATABASE = DATABASE:New() -- Database#DATABASE + + diff --git a/Moose Development/Moose/Moose_Test_CARGO_Pickup.lua b/Moose Development/Moose/Moose_Test_CARGO_Pickup.lua deleted file mode 100644 index 4d5165081..000000000 --- a/Moose Development/Moose/Moose_Test_CARGO_Pickup.lua +++ /dev/null @@ -1,8 +0,0 @@ - -local Mission = MISSION:New( "Pickup Cargo", "High", "Test for Cargo Pickup", coalition.side.RED ) - -local CargoEngineer = UNIT:FindByName( "Engineer" ) -local InfantryCargo = CARGO_UNIT:New( Mission, CargoEngineer, "Engineer", "Engineer Sven", "81", 2000, 300 ) - -local CargoCarrier = UNIT:FindByName( "CargoCarrier" ) -InfantryCargo:OnBoard( CargoCarrier ) \ No newline at end of file diff --git a/Moose Development/Moose/PatrolZone.lua b/Moose Development/Moose/PatrolZone.lua deleted file mode 100644 index ffa4a3a6d..000000000 --- a/Moose Development/Moose/PatrolZone.lua +++ /dev/null @@ -1,265 +0,0 @@ ---- This module contains the PATROLZONE class. --- --- === --- --- 1) @{Patrol#PATROLZONE} class, extends @{Base#BASE} --- =================================================== --- The @{Patrol#PATROLZONE} class implements the core functions to patrol a @{Zone}. --- --- 1.1) PATROLZONE constructor: --- ---------------------------- --- @{PatrolZone#PATROLZONE.New}(): Creates a new PATROLZONE object. --- --- 1.2) Modify the PATROLZONE parameters: --- -------------------------------------- --- The following methods are available to modify the parameters of a PATROLZONE object: --- --- * @{PatrolZone#PATROLZONE.SetGroup}(): Set the AI Patrol Group. --- * @{PatrolZone#PATROLZONE.SetSpeed}(): Set the patrol speed of the AI, for the next patrol. --- * @{PatrolZone#PATROLZONE.SetAltitude}(): Set altitude of the AI, for the next patrol. --- --- 1.3) Manage the out of fuel in the PATROLZONE: --- ---------------------------------------------- --- When the PatrolGroup is out of fuel, it is required that a new PatrolGroup is started, before the old PatrolGroup can return to the home base. --- Therefore, with a parameter and a calculation of the distance to the home base, the fuel treshold is calculated. --- When the fuel treshold is reached, the PatrolGroup will continue for a given time its patrol task in orbit, while a new PatrolGroup is targetted to the PATROLZONE. --- Once the time is finished, the old PatrolGroup will return to the base. --- Use the method @{PatrolZone#PATROLZONE.ManageFuel}() to have this proces in place. --- --- === --- --- @module PatrolZone --- @author FlightControl - - ---- PATROLZONE class --- @type PATROLZONE --- @field Group#GROUP PatrolGroup The @{Group} patrolling. --- @field Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed. --- @field DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. --- @field DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. --- @field DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Group} in km/h. --- @field DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Group} in km/h. --- @extends Base#BASE -PATROLZONE = { - ClassName = "PATROLZONE", -} - ---- Creates a new PATROLZONE object, taking a @{Group} object as a parameter. The GROUP needs to be alive. --- @param #PATROLZONE self --- @param Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed. --- @param DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. --- @param DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. --- @param DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Group} in km/h. --- @param DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Group} in km/h. --- @return #PATROLZONE self --- @usage --- -- Define a new PATROLZONE Object. This PatrolArea will patrol a group within PatrolZone between 3000 and 6000 meters, with a variying speed between 600 and 900 km/h. --- PatrolZone = ZONE:New( 'PatrolZone' ) --- PatrolGroup = GROUP:FindByName( "Patrol Group" ) --- PatrolArea = PATROLZONE:New( PatrolGroup, PatrolZone, 3000, 6000, 600, 900 ) -function PATROLZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed ) - - -- Inherits from BASE - local self = BASE:Inherit( self, BASE:New() ) - - self.PatrolZone = PatrolZone - self.PatrolFloorAltitude = PatrolFloorAltitude - self.PatrolCeilingAltitude = PatrolCeilingAltitude - self.PatrolMinSpeed = PatrolMinSpeed - self.PatrolMaxSpeed = PatrolMaxSpeed - - return self -end - ---- Set the @{Group} to act as the Patroller. --- @param #PATROLZONE self --- @param Group#GROUP PatrolGroup The @{Group} patrolling. --- @return #PATROLZONE self -function PATROLZONE:SetGroup( PatrolGroup ) - - self.PatrolGroup = PatrolGroup - self.PatrolGroupTemplateName = PatrolGroup:GetName() - self:NewPatrolRoute() - - if not self.PatrolOutOfFuelMonitor then - self.PatrolOutOfFuelMonitor = SCHEDULER:New( nil, _MonitorOutOfFuelScheduled, { self }, 1, 120, 0 ) - self.SpawnPatrolGroup = SPAWN:New( self.PatrolGroupTemplateName ) - end - - return self -end - ---- Sets (modifies) the minimum and maximum speed of the patrol. --- @param #PATROLZONE self --- @param DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Group} in km/h. --- @param DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Group} in km/h. --- @return #PATROLZONE self -function PATROLZONE:SetSpeed( PatrolMinSpeed, PatrolMaxSpeed ) - self:F2( { PatrolMinSpeed, PatrolMaxSpeed } ) - - self.PatrolMinSpeed = PatrolMinSpeed - self.PatrolMaxSpeed = PatrolMaxSpeed -end - ---- Sets the floor and ceiling altitude of the patrol. --- @param #PATROLZONE self --- @param DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. --- @param DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. --- @return #PATROLZONE self -function PATROLZONE:SetAltitude( PatrolFloorAltitude, PatrolCeilingAltitude ) - self:F2( { PatrolFloorAltitude, PatrolCeilingAltitude } ) - - self.PatrolFloorAltitude = PatrolFloorAltitude - self.PatrolCeilingAltitude = PatrolCeilingAltitude -end - - - ---- @param Group#GROUP PatrolGroup -function _NewPatrolRoute( PatrolGroup ) - - PatrolGroup:T( "NewPatrolRoute" ) - local PatrolZone = PatrolGroup:GetState( PatrolGroup, "PatrolZone" ) -- PatrolZone#PATROLZONE - PatrolZone:NewPatrolRoute() -end - ---- Defines a new patrol route using the @{PatrolZone} parameters and settings. --- @param #PATROLZONE self --- @return #PATROLZONE self -function PATROLZONE:NewPatrolRoute() - - self:F2() - - local PatrolRoute = {} - - if self.PatrolGroup:IsAlive() then - --- Determine if the PatrolGroup is within the PatrolZone. - -- If not, make a waypoint within the to that the PatrolGroup will fly at maximum speed to that point. - --- --- Calculate the current route point. --- local CurrentVec2 = self.PatrolGroup:GetVec2() --- local CurrentAltitude = self.PatrolGroup:GetUnit(1):GetAltitude() --- local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) --- local CurrentRoutePoint = CurrentPointVec3:RoutePointAir( --- POINT_VEC3.RoutePointAltType.BARO, --- POINT_VEC3.RoutePointType.TurningPoint, --- POINT_VEC3.RoutePointAction.TurningPoint, --- ToPatrolZoneSpeed, --- true --- ) --- --- PatrolRoute[#PatrolRoute+1] = CurrentRoutePoint - - self:T2( PatrolRoute ) - - if self.PatrolGroup:IsNotInZone( self.PatrolZone ) then - --- Find a random 2D point in PatrolZone. - local ToPatrolZoneVec2 = self.PatrolZone:GetRandomVec2() - self:T2( ToPatrolZoneVec2 ) - - --- Define Speed and Altitude. - local ToPatrolZoneAltitude = math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ) - local ToPatrolZoneSpeed = self.PatrolMaxSpeed - self:T2( ToPatrolZoneSpeed ) - - --- Obtain a 3D @{Point} from the 2D point + altitude. - local ToPatrolZonePointVec3 = POINT_VEC3:New( ToPatrolZoneVec2.x, ToPatrolZoneAltitude, ToPatrolZoneVec2.y ) - - --- Create a route point of type air. - local ToPatrolZoneRoutePoint = ToPatrolZonePointVec3:RoutePointAir( - POINT_VEC3.RoutePointAltType.BARO, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - ToPatrolZoneSpeed, - true - ) - - PatrolRoute[#PatrolRoute+1] = ToPatrolZoneRoutePoint - - end - - --- Define a random point in the @{Zone}. The AI will fly to that point within the zone. - - --- Find a random 2D point in PatrolZone. - local ToTargetVec2 = self.PatrolZone:GetRandomVec2() - self:T2( ToTargetVec2 ) - - --- Define Speed and Altitude. - local ToTargetAltitude = math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ) - local ToTargetSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed ) - self:T2( { self.PatrolMinSpeed, self.PatrolMaxSpeed, ToTargetSpeed } ) - - --- Obtain a 3D @{Point} from the 2D point + altitude. - local ToTargetPointVec3 = POINT_VEC3:New( ToTargetVec2.x, ToTargetAltitude, ToTargetVec2.y ) - - --- Create a route point of type air. - local ToTargetRoutePoint = ToTargetPointVec3:RoutePointAir( - POINT_VEC3.RoutePointAltType.BARO, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - ToTargetSpeed, - true - ) - - --ToTargetPointVec3:SmokeRed() - - PatrolRoute[#PatrolRoute+1] = ToTargetRoutePoint - - --- Now we're going to do something special, we're going to call a function from a waypoint action at the PatrolGroup... - self.PatrolGroup:WayPointInitialize( PatrolRoute ) - - --- Do a trick, link the NewPatrolRoute function of the PATROLGROUP object to the PatrolGroup in a temporary variable ... - self.PatrolGroup:SetState( self.PatrolGroup, "PatrolZone", self ) - self.PatrolGroup:WayPointFunction( #PatrolRoute, 1, "_NewPatrolRoute" ) - - --- NOW ROUTE THE GROUP! - self.PatrolGroup:WayPointExecute( 1, 2 ) - end - -end - ---- When the PatrolGroup is out of fuel, it is required that a new PatrolGroup is started, before the old PatrolGroup can return to the home base. --- Therefore, with a parameter and a calculation of the distance to the home base, the fuel treshold is calculated. --- When the fuel treshold is reached, the PatrolGroup will continue for a given time its patrol task in orbit, while a new PatrolGroup is targetted to the PATROLZONE. --- Once the time is finished, the old PatrolGroup will return to the base. --- @param #PATROLZONE self --- @param #number PatrolFuelTresholdPercentage The treshold in percentage (between 0 and 1) when the PatrolGroup is considered to get out of fuel. --- @param #number PatrolOutOfFuelOrbitTime The amount of seconds the out of fuel PatrolGroup will orbit before returning to the base. --- @return #PATROLZONE self -function PATROLZONE:ManageFuel( PatrolFuelTresholdPercentage, PatrolOutOfFuelOrbitTime ) - - self.PatrolManageFuel = true - self.PatrolFuelTresholdPercentage = PatrolFuelTresholdPercentage - self.PatrolOutOfFuelOrbitTime = PatrolOutOfFuelOrbitTime - - if self.PatrolGroup then - self.PatrolOutOfFuelMonitor = SCHEDULER:New( self, self._MonitorOutOfFuelScheduled, {}, 1, 120, 0 ) - self.SpawnPatrolGroup = SPAWN:New( self.PatrolGroupTemplateName ) - end - return self -end - ---- @param #PATROLZONE self -function _MonitorOutOfFuelScheduled( self ) - self:F2( "_MonitorOutOfFuelScheduled" ) - - if self.PatrolGroup and self.PatrolGroup:IsAlive() then - - local Fuel = self.PatrolGroup:GetUnit(1):GetFuel() - if Fuel < self.PatrolFuelTresholdPercentage then - local OldPatrolGroup = self.PatrolGroup - local PatrolGroupTemplate = self.PatrolGroup:GetTemplate() - - local OrbitTask = OldPatrolGroup:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed ) - local TimedOrbitTask = OldPatrolGroup:TaskControlled( OrbitTask, OldPatrolGroup:TaskCondition(nil,nil,nil,nil,self.PatrolOutOfFuelOrbitTime,nil ) ) - OldPatrolGroup:SetTask( TimedOrbitTask, 10 ) - - local NewPatrolGroup = self.SpawnPatrolGroup:Spawn() - self.PatrolGroup = NewPatrolGroup - self:NewPatrolRoute() - end - else - self.PatrolOutOfFuelMonitor:Stop() - end -end \ No newline at end of file diff --git a/Moose Development/Moose/Positionable.lua b/Moose Development/Moose/Positionable.lua deleted file mode 100644 index 87c0629ac..000000000 --- a/Moose Development/Moose/Positionable.lua +++ /dev/null @@ -1,280 +0,0 @@ ---- This module contains the POSITIONABLE class. --- --- 1) @{Positionable#POSITIONABLE} class, extends @{Identifiable#IDENTIFIABLE} --- =========================================================== --- The @{Positionable#POSITIONABLE} class is a wrapper class to handle the POSITIONABLE objects: --- --- * Support all DCS APIs. --- * Enhance with POSITIONABLE specific APIs not in the DCS API set. --- * Manage the "state" of the POSITIONABLE. --- --- 1.1) POSITIONABLE constructor: --- ------------------------------ --- The POSITIONABLE class provides the following functions to construct a POSITIONABLE instance: --- --- * @{Positionable#POSITIONABLE.New}(): Create a POSITIONABLE instance. --- --- 1.2) POSITIONABLE methods: --- -------------------------- --- The following methods can be used to identify an measurable object: --- --- * @{Positionable#POSITIONABLE.GetID}(): Returns the ID of the measurable object. --- * @{Positionable#POSITIONABLE.GetName}(): Returns the name of the measurable object. --- --- === --- --- @module Positionable --- @author FlightControl - ---- The POSITIONABLE class --- @type POSITIONABLE --- @extends Identifiable#IDENTIFIABLE --- @field #string PositionableName The name of the measurable. -POSITIONABLE = { - ClassName = "POSITIONABLE", - PositionableName = "", -} - ---- A DCSPositionable --- @type DCSPositionable --- @field id_ The ID of the controllable in DCS - ---- Create a new POSITIONABLE from a DCSPositionable --- @param #POSITIONABLE self --- @param DCSPositionable#Positionable PositionableName The POSITIONABLE name --- @return #POSITIONABLE self -function POSITIONABLE:New( PositionableName ) - local self = BASE:Inherit( self, IDENTIFIABLE:New( PositionableName ) ) - - return self -end - ---- Returns the @{DCSTypes#Position3} position vectors indicating the point and direction vectors in 3D of the POSITIONABLE within the mission. --- @param Positionable#POSITIONABLE self --- @return DCSTypes#Position The 3D position vectors of the POSITIONABLE. --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:GetPositionVec3() - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local PositionablePosition = DCSPositionable:getPosition() - self:T3( PositionablePosition ) - return PositionablePosition - end - - return nil -end - ---- Returns the @{DCSTypes#Vec2} vector indicating the point in 2D of the POSITIONABLE within the mission. --- @param Positionable#POSITIONABLE self --- @return DCSTypes#Vec2 The 2D point vector of the POSITIONABLE. --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:GetVec2() - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local PositionableVec3 = DCSPositionable:getPosition().p - - local PositionableVec2 = {} - PositionableVec2.x = PositionableVec3.x - PositionableVec2.y = PositionableVec3.z - - self:T2( PositionableVec2 ) - return PositionableVec2 - end - - return nil -end - ---- Returns a POINT_VEC2 object indicating the point in 2D of the POSITIONABLE within the mission. --- @param Positionable#POSITIONABLE self --- @return Point#POINT_VEC2 The 2D point vector of the POSITIONABLE. --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:GetPointVec2() - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local PositionableVec3 = DCSPositionable:getPosition().p - - local PositionablePointVec2 = POINT_VEC2:NewFromVec3( PositionableVec3 ) - - self:T2( PositionablePointVec2 ) - return PositionablePointVec2 - end - - return nil -end - - ---- Returns a random @{DCSTypes#Vec3} vector within a range, indicating the point in 3D of the POSITIONABLE within the mission. --- @param Positionable#POSITIONABLE self --- @return DCSTypes#Vec3 The 3D point vector of the POSITIONABLE. --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:GetRandomVec3( Radius ) - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local PositionablePointVec3 = DCSPositionable:getPosition().p - local PositionableRandomVec3 = {} - local angle = math.random() * math.pi*2; - PositionableRandomVec3.x = PositionablePointVec3.x + math.cos( angle ) * math.random() * Radius; - PositionableRandomVec3.y = PositionablePointVec3.y - PositionableRandomVec3.z = PositionablePointVec3.z + math.sin( angle ) * math.random() * Radius; - - self:T3( PositionableRandomVec3 ) - return PositionableRandomVec3 - end - - return nil -end - ---- Returns the @{DCSTypes#Vec3} vector indicating the 3D vector of the POSITIONABLE within the mission. --- @param Positionable#POSITIONABLE self --- @return DCSTypes#Vec3 The 3D point vector of the POSITIONABLE. --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:GetVec3() - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local PositionableVec3 = DCSPositionable:getPosition().p - self:T3( PositionableVec3 ) - return PositionableVec3 - end - - return nil -end - ---- Returns the altitude of the POSITIONABLE. --- @param Positionable#POSITIONABLE self --- @return DCSTypes#Distance The altitude of the POSITIONABLE. --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:GetAltitude() - self:F2() - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local PositionablePointVec3 = DCSPositionable:getPoint() --DCSTypes#Vec3 - return PositionablePointVec3.y - end - - return nil -end - ---- Returns if the Positionable is located above a runway. --- @param Positionable#POSITIONABLE self --- @return #boolean true if Positionable is above a runway. --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:IsAboveRunway() - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - - local Vec2 = self:GetVec2() - local SurfaceType = land.getSurfaceType( Vec2 ) - local IsAboveRunway = SurfaceType == land.SurfaceType.RUNWAY - - self:T2( IsAboveRunway ) - return IsAboveRunway - end - - return nil -end - - - ---- Returns the POSITIONABLE heading in degrees. --- @param Positionable#POSITIONABLE self --- @return #number The POSTIONABLE heading -function POSITIONABLE:GetHeading() - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - - local PositionablePosition = DCSPositionable:getPosition() - if PositionablePosition then - local PositionableHeading = math.atan2( PositionablePosition.x.z, PositionablePosition.x.x ) - if PositionableHeading < 0 then - PositionableHeading = PositionableHeading + 2 * math.pi - end - PositionableHeading = PositionableHeading * 180 / math.pi - self:T2( PositionableHeading ) - return PositionableHeading - end - end - - return nil -end - - ---- Returns true if the POSITIONABLE is in the air. --- @param Positionable#POSITIONABLE self --- @return #boolean true if in the air. --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:InAir() - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local PositionableInAir = DCSPositionable:inAir() - self:T3( PositionableInAir ) - return PositionableInAir - end - - return nil -end - ---- Returns the POSITIONABLE velocity vector. --- @param Positionable#POSITIONABLE self --- @return DCSTypes#Vec3 The velocity vector --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:GetVelocity() - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local PositionableVelocityVec3 = DCSPositionable:getVelocity() - self:T3( PositionableVelocityVec3 ) - return PositionableVelocityVec3 - end - - return nil -end - ---- Returns the POSITIONABLE velocity in km/h. --- @param Positionable#POSITIONABLE self --- @return #number The velocity in km/h --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:GetVelocityKMH() - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local VelocityVec3 = self:GetVelocity() - local Velocity = ( VelocityVec3.x ^ 2 + VelocityVec3.y ^ 2 + VelocityVec3.z ^ 2 ) ^ 0.5 -- in meters / sec - local Velocity = Velocity * 3.6 -- now it is in km/h. - self:T3( Velocity ) - return Velocity - end - - return nil -end - - - - diff --git a/Moose Development/Moose/Process.lua b/Moose Development/Moose/Process.lua deleted file mode 100644 index 6c728c301..000000000 --- a/Moose Development/Moose/Process.lua +++ /dev/null @@ -1,90 +0,0 @@ ---- @module Process - ---- The PROCESS class --- @type PROCESS --- @field Scheduler#SCHEDULER ProcessScheduler --- @field Unit#UNIT ProcessUnit --- @field Group#GROUP ProcessGroup --- @field Menu#MENU_GROUP MissionMenu --- @field Task#TASK_BASE Task --- @field StateMachine#STATEMACHINE_TASK Fsm --- @field #string ProcessName --- @extends Base#BASE -PROCESS = { - ClassName = "TASK", - ProcessScheduler = nil, - NextEvent = nil, - Scores = {}, -} - ---- Instantiates a new TASK Base. Should never be used. Interface Class. --- @param #PROCESS self --- @param #string ProcessName --- @param Task#TASK_BASE Task --- @param Unit#UNIT ProcessUnit --- @return #PROCESS self -function PROCESS:New( ProcessName, Task, ProcessUnit ) - local self = BASE:Inherit( self, BASE:New() ) - self:F() - - self.ProcessUnit = ProcessUnit - self.ProcessGroup = ProcessUnit:GetGroup() - self.MissionMenu = Task.Mission:GetMissionMenu( self.ProcessGroup ) - self.Task = Task - self.ProcessName = ProcessName - - self.ProcessScheduler = SCHEDULER:New() - - return self -end - ---- @param #PROCESS self -function PROCESS:NextEvent( NextEvent, ... ) - self:F(self.ProcessName) - self.ProcessScheduler:Schedule( self.Fsm, NextEvent, arg, 1 ) -- This schedules the next event, but only if scheduling is activated. -end - ---- @param #PROCESS self -function PROCESS:StopEvents() - self:F( { "Stop Process ", self.ProcessName } ) - self.ProcessScheduler:Stop() -end - ---- Adds a score for the PROCESS to be achieved. --- @param #PROCESS self --- @param #string ProcessStatus is the status of the PROCESS when the score needs to be given. --- @param #string ScoreText is a text describing the score that is given according the status. --- @param #number Score is a number providing the score of the status. --- @return #PROCESS self -function PROCESS:AddScore( ProcessStatus, ScoreText, Score ) - self:F2( { ProcessStatus, ScoreText, Score } ) - - self.Scores[ProcessStatus] = self.Scores[ProcessStatus] or {} - self.Scores[ProcessStatus].ScoreText = ScoreText - self.Scores[ProcessStatus].Score = Score - return self -end - ---- StateMachine callback function for a PROCESS --- @param #PROCESS self --- @param StateMachine#STATEMACHINE_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To -function PROCESS:OnStateChange( Fsm, Event, From, To ) - self:E( { self.ProcessName, Event, From, To, self.ProcessUnit.UnitName } ) - - if self:IsTrace() then - MESSAGE:New( "Process " .. self.ProcessName .. " : " .. Event .. " changed to state " .. To, 15 ):ToAll() - end - - if self.Scores[To] then - - local Scoring = self.Task:GetScoring() - if Scoring then - Scoring:_AddMissionTaskScore( self.Task.Mission, self.ProcessUnit, self.Scores[To].ScoreText, self.Scores[To].Score ) - end - end -end - - diff --git a/Moose Development/Moose/Process_Assign.lua b/Moose Development/Moose/Process_Assign.lua deleted file mode 100644 index de51b8499..000000000 --- a/Moose Development/Moose/Process_Assign.lua +++ /dev/null @@ -1,185 +0,0 @@ ---- This module contains the PROCESS_ASSIGN classes. --- --- === --- --- 1) @{Task_Assign#TASK_ASSIGN_ACCEPT} class, extends @{Task#TASK_BASE} --- ===================================================================== --- The @{Task_Assign#TASK_ASSIGN_ACCEPT} class accepts by default a task for a player. No player intervention is allowed to reject the task. --- --- 2) @{Task_Assign#TASK_ASSIGN_MENU_ACCEPT} class, extends @{Task#TASK_BASE} --- ========================================================================== --- The @{Task_Assign#TASK_ASSIGN_MENU_ACCEPT} class accepts a task when the player accepts the task through an added menu option. --- This assignment type is useful to conditionally allow the player to choose whether or not he would accept the task. --- The assignment type also allows to reject the task. --- --- --- --- --- --- --- @module Task_Assign --- - - -do -- PROCESS_ASSIGN_ACCEPT - - --- PROCESS_ASSIGN_ACCEPT class - -- @type PROCESS_ASSIGN_ACCEPT - -- @field Task#TASK_BASE Task - -- @field Unit#UNIT ProcessUnit - -- @field Zone#ZONE_BASE TargetZone - -- @extends Task2#TASK2 - PROCESS_ASSIGN_ACCEPT = { - ClassName = "PROCESS_ASSIGN_ACCEPT", - } - - - --- Creates a new task assignment state machine. The process will accept the task by default, no player intervention accepted. - -- @param #PROCESS_ASSIGN_ACCEPT self - -- @param Task#TASK Task - -- @param Unit#UNIT Unit - -- @return #PROCESS_ASSIGN_ACCEPT self - function PROCESS_ASSIGN_ACCEPT:New( Task, ProcessUnit, TaskBriefing ) - - -- Inherits from BASE - local self = BASE:Inherit( self, PROCESS:New( "ASSIGN_ACCEPT", Task, ProcessUnit ) ) -- #PROCESS_ASSIGN_ACCEPT - - self.TaskBriefing = TaskBriefing - - self.Fsm = STATEMACHINE_PROCESS:New( self, { - initial = 'UnAssigned', - events = { - { name = 'Start', from = 'UnAssigned', to = 'Assigned' }, - { name = 'Fail', from = 'UnAssigned', to = 'Failed' }, - }, - callbacks = { - onAssign = self.OnAssign, - }, - endstates = { - 'Assigned', 'Failed' - }, - } ) - - return self - end - - --- StateMachine callback function for a TASK2 - -- @param #PROCESS_ASSIGN_ACCEPT self - -- @param StateMachine#STATEMACHINE_PROCESS Fsm - -- @param #string Event - -- @param #string From - -- @param #string To - function PROCESS_ASSIGN_ACCEPT:OnAssigned( Fsm, Event, From, To ) - self:E( { Event, From, To, self.ProcessUnit.UnitName} ) - - end - -end - - -do -- PROCESS_ASSIGN_MENU_ACCEPT - - --- PROCESS_ASSIGN_MENU_ACCEPT class - -- @type PROCESS_ASSIGN_MENU_ACCEPT - -- @field Task#TASK_BASE Task - -- @field Unit#UNIT ProcessUnit - -- @field Zone#ZONE_BASE TargetZone - -- @extends Task2#TASK2 - PROCESS_ASSIGN_MENU_ACCEPT = { - ClassName = "PROCESS_ASSIGN_MENU_ACCEPT", - } - - - --- Creates a new task assignment state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator. - -- @param #PROCESS_ASSIGN_MENU_ACCEPT self - -- @param Task#TASK Task - -- @param Unit#UNIT Unit - -- @return #PROCESS_ASSIGN_MENU_ACCEPT self - function PROCESS_ASSIGN_MENU_ACCEPT:New( Task, ProcessUnit, TaskBriefing ) - - -- Inherits from BASE - local self = BASE:Inherit( self, PROCESS:New( "ASSIGN_MENU_ACCEPT", Task, ProcessUnit ) ) -- #PROCESS_ASSIGN_MENU_ACCEPT - - self.TaskBriefing = TaskBriefing - - self.Fsm = STATEMACHINE_PROCESS:New( self, { - initial = 'UnAssigned', - events = { - { name = 'Start', from = 'UnAssigned', to = 'AwaitAccept' }, - { name = 'Assign', from = 'AwaitAccept', to = 'Assigned' }, - { name = 'Reject', from = 'AwaitAccept', to = 'Rejected' }, - { name = 'Fail', from = 'AwaitAccept', to = 'Rejected' }, - }, - callbacks = { - onStart = self.OnStart, - onAssign = self.OnAssign, - onReject = self.OnReject, - }, - endstates = { - 'Assigned', 'Rejected' - }, - } ) - - return self - end - - --- StateMachine callback function for a TASK2 - -- @param #PROCESS_ASSIGN_MENU_ACCEPT self - -- @param StateMachine#STATEMACHINE_TASK Fsm - -- @param #string Event - -- @param #string From - -- @param #string To - function PROCESS_ASSIGN_MENU_ACCEPT:OnStart( Fsm, Event, From, To ) - self:E( { Event, From, To, self.ProcessUnit.UnitName} ) - - MESSAGE:New( self.TaskBriefing .. "\nAccess the radio menu to accept the task. You have 30 seconds or the assignment will be cancelled.", 30, "Assignment" ):ToGroup( self.ProcessUnit:GetGroup() ) - self.MenuText = self.Task.TaskName - - local ProcessGroup = self.ProcessUnit:GetGroup() - self.Menu = MENU_GROUP:New( ProcessGroup, "Task " .. self.MenuText .. " acceptance" ) - self.MenuAcceptTask = MENU_GROUP_COMMAND:New( ProcessGroup, "Accept task " .. self.MenuText, self.Menu, self.MenuAssign, self ) - self.MenuRejectTask = MENU_GROUP_COMMAND:New( ProcessGroup, "Reject task " .. self.MenuText, self.Menu, self.MenuReject, self ) - end - - --- Menu function. - -- @param #PROCESS_ASSIGN_MENU_ACCEPT self - function PROCESS_ASSIGN_MENU_ACCEPT:MenuAssign() - self:E( ) - - self:NextEvent( self.Fsm.Assign ) - end - - --- Menu function. - -- @param #PROCESS_ASSIGN_MENU_ACCEPT self - function PROCESS_ASSIGN_MENU_ACCEPT:MenuReject() - self:E( ) - - self:NextEvent( self.Fsm.Reject ) - end - - --- StateMachine callback function for a TASK2 - -- @param #PROCESS_ASSIGN_MENU_ACCEPT self - -- @param StateMachine#STATEMACHINE_PROCESS Fsm - -- @param #string Event - -- @param #string From - -- @param #string To - function PROCESS_ASSIGN_MENU_ACCEPT:OnAssign( Fsm, Event, From, To ) - self:E( { Event, From, To, self.ProcessUnit.UnitName} ) - - self.Menu:Remove() - end - - --- StateMachine callback function for a TASK2 - -- @param #PROCESS_ASSIGN_MENU_ACCEPT self - -- @param StateMachine#STATEMACHINE_PROCESS Fsm - -- @param #string Event - -- @param #string From - -- @param #string To - function PROCESS_ASSIGN_MENU_ACCEPT:OnReject( Fsm, Event, From, To ) - self:E( { Event, From, To, self.ProcessUnit.UnitName} ) - - self.Menu:Remove() - self.Task:UnAssignFromUnit( self.ProcessUnit ) - self.ProcessUnit:Destroy() - end -end diff --git a/Moose Development/Moose/Process_Destroy.lua b/Moose Development/Moose/Process_Destroy.lua deleted file mode 100644 index 3a8c83113..000000000 --- a/Moose Development/Moose/Process_Destroy.lua +++ /dev/null @@ -1,180 +0,0 @@ ---- @module Process_Destroy - ---- PROCESS_DESTROY class --- @type PROCESS_DESTROY --- @field Unit#UNIT ProcessUnit --- @field Set#SET_UNIT TargetSetUnit --- @extends Process#PROCESS -PROCESS_DESTROY = { - ClassName = "PROCESS_DESTROY", - Fsm = {}, - TargetSetUnit = nil, -} - - ---- Creates a new DESTROY process. --- @param #PROCESS_DESTROY self --- @param Task#TASK Task --- @param Unit#UNIT ProcessUnit --- @param Set#SET_UNIT TargetSetUnit --- @return #PROCESS_DESTROY self -function PROCESS_DESTROY:New( Task, ProcessName, ProcessUnit, TargetSetUnit ) - - -- Inherits from BASE - local self = BASE:Inherit( self, PROCESS:New( ProcessName, Task, ProcessUnit ) ) -- #PROCESS_DESTROY - - self.TargetSetUnit = TargetSetUnit - - self.DisplayInterval = 30 - self.DisplayCount = 30 - self.DisplayMessage = true - self.DisplayTime = 10 -- 10 seconds is the default - self.DisplayCategory = "HQ" -- Targets is the default display category - - self.Fsm = STATEMACHINE_PROCESS:New( self, { - initial = 'Assigned', - events = { - { name = 'Start', from = 'Assigned', to = 'Waiting' }, - { name = 'Start', from = 'Waiting', to = 'Waiting' }, - { name = 'HitTarget', from = 'Waiting', to = 'Destroy' }, - { name = 'MoreTargets', from = 'Destroy', to = 'Waiting' }, - { name = 'Destroyed', from = 'Destroy', to = 'Success' }, - { name = 'Fail', from = 'Assigned', to = 'Failed' }, - { name = 'Fail', from = 'Waiting', to = 'Failed' }, - { name = 'Fail', from = 'Destroy', to = 'Failed' }, - }, - callbacks = { - onStart = self.OnStart, - onWaiting = self.OnWaiting, - onHitTarget = self.OnHitTarget, - onMoreTargets = self.OnMoreTargets, - onDestroyed = self.OnDestroyed, - onKilled = self.OnKilled, - }, - endstates = { 'Success', 'Failed' } - } ) - - - _EVENTDISPATCHER:OnDead( self.EventDead, self ) - - return self -end - ---- Process Events - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_DESTROY self --- @param StateMachine#STATEMACHINE_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To -function PROCESS_DESTROY:OnStart( Fsm, Event, From, To ) - - self:NextEvent( Fsm.Start ) -end - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_DESTROY self --- @param StateMachine#STATEMACHINE_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To -function PROCESS_DESTROY:OnWaiting( Fsm, Event, From, To ) - - local TaskGroup = self.ProcessUnit:GetGroup() - if self.DisplayCount >= self.DisplayInterval then - MESSAGE:New( "Your group with assigned " .. self.Task:GetName() .. " task has " .. self.TargetSetUnit:GetUnitTypesText() .. " targets left to be destroyed.", 5, "HQ" ):ToGroup( TaskGroup ) - self.DisplayCount = 1 - else - self.DisplayCount = self.DisplayCount + 1 - end - - return true -- Process always the event. - -end - - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_DESTROY self --- @param StateMachine#STATEMACHINE_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To --- @param Event#EVENTDATA Event -function PROCESS_DESTROY:OnHitTarget( Fsm, Event, From, To, Event ) - - - self.TargetSetUnit:Flush() - - if self.TargetSetUnit:FindUnit( Event.IniUnitName ) then - self.TargetSetUnit:RemoveUnitsByName( Event.IniUnitName ) - local TaskGroup = self.ProcessUnit:GetGroup() - MESSAGE:New( "You hit a target. Your group with assigned " .. self.Task:GetName() .. " task has " .. self.TargetSetUnit:Count() .. " targets ( " .. self.TargetSetUnit:GetUnitTypesText() .. " ) left to be destroyed.", 15, "HQ" ):ToGroup( TaskGroup ) - end - - - if self.TargetSetUnit:Count() > 0 then - self:NextEvent( Fsm.MoreTargets ) - else - self:NextEvent( Fsm.Destroyed ) - end -end - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_DESTROY self --- @param StateMachine#STATEMACHINE_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To -function PROCESS_DESTROY:OnMoreTargets( Fsm, Event, From, To ) - - -end - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_DESTROY self --- @param StateMachine#STATEMACHINE_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To --- @param Event#EVENTDATA DCSEvent -function PROCESS_DESTROY:OnKilled( Fsm, Event, From, To ) - - self:NextEvent( Fsm.Restart ) - -end - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_DESTROY self --- @param StateMachine#STATEMACHINE_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To -function PROCESS_DESTROY:OnRestart( Fsm, Event, From, To ) - - self:NextEvent( Fsm.Menu ) - -end - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_DESTROY self --- @param StateMachine#STATEMACHINE_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To -function PROCESS_DESTROY:OnDestroyed( Fsm, Event, From, To ) - -end - ---- DCS Events - ---- @param #PROCESS_DESTROY self --- @param Event#EVENTDATA Event -function PROCESS_DESTROY:EventDead( Event ) - - if Event.IniDCSUnit then - self:NextEvent( self.Fsm.HitTarget, Event ) - end -end - - diff --git a/Moose Development/Moose/Process_Route.lua b/Moose Development/Moose/Process_Route.lua deleted file mode 100644 index fd240a700..000000000 --- a/Moose Development/Moose/Process_Route.lua +++ /dev/null @@ -1,86 +0,0 @@ ---- @module Task_Route - ---- PROCESS_ROUTE class --- @type PROCESS_ROUTE --- @field Task#TASK TASK --- @field Unit#UNIT ProcessUnit --- @field Zone#ZONE_BASE TargetZone --- @extends Task2#TASK2 -PROCESS_ROUTE = { - ClassName = "PROCESS_ROUTE", -} - - ---- Creates a new routing state machine. The task will route a CLIENT to a ZONE until the CLIENT is within that ZONE. --- @param #PROCESS_ROUTE self --- @param Task#TASK Task --- @param Unit#UNIT Unit --- @return #PROCESS_ROUTE self -function PROCESS_ROUTE:New( Task, ProcessUnit, TargetZone ) - - -- Inherits from BASE - local self = BASE:Inherit( self, PROCESS:New( "ROUTE", Task, ProcessUnit ) ) -- #PROCESS_ROUTE - - self.TargetZone = TargetZone - self.DisplayInterval = 30 - self.DisplayCount = 30 - self.DisplayMessage = true - self.DisplayTime = 10 -- 10 seconds is the default - self.DisplayCategory = "HQ" -- Route is the default display category - - self.Fsm = STATEMACHINE_PROCESS:New( self, { - initial = 'UnArrived', - events = { - { name = 'Start', from = 'UnArrived', to = 'UnArrived' }, - { name = 'Fail', from = 'UnArrived', to = 'Failed' }, - }, - callbacks = { - onleaveUnArrived = self.OnLeaveUnArrived, - onFail = self.OnFail, - }, - endstates = { - 'Arrived', 'Failed' - }, - } ) - - return self -end - ---- Task Events - ---- StateMachine callback function for a TASK2 --- @param #PROCESS_ROUTE self --- @param StateMachine#STATEMACHINE_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To -function PROCESS_ROUTE:OnLeaveUnArrived( Fsm, Event, From, To ) - - if self.ProcessUnit:IsAlive() then - local IsInZone = self.ProcessUnit:IsInZone( self.TargetZone ) - - if self.DisplayCount >= self.DisplayInterval then - if not IsInZone then - local ZoneVec2 = self.TargetZone:GetVec2() - local ZonePointVec2 = POINT_VEC2:New( ZoneVec2.x, ZoneVec2.y ) - local TaskUnitVec2 = self.ProcessUnit:GetVec2() - local TaskUnitPointVec2 = POINT_VEC2:New( TaskUnitVec2.x, TaskUnitVec2.y ) - local RouteText = self.ProcessUnit:GetCallsign() .. ": Route to " .. TaskUnitPointVec2:GetBRText( ZonePointVec2 ) .. " km to target." - MESSAGE:New( RouteText, self.DisplayTime, self.DisplayCategory ):ToGroup( self.ProcessUnit:GetGroup() ) - end - self.DisplayCount = 1 - else - self.DisplayCount = self.DisplayCount + 1 - end - - --if not IsInZone then - self:NextEvent( Fsm.Start ) - --end - - return IsInZone -- if false, then the event will not be executed... - end - - return false - -end - diff --git a/Moose Development/Moose/Process_Smoke.lua b/Moose Development/Moose/Process_Smoke.lua deleted file mode 100644 index b21b990df..000000000 --- a/Moose Development/Moose/Process_Smoke.lua +++ /dev/null @@ -1,108 +0,0 @@ ---- @module Process_Smoke - -do -- PROCESS_SMOKE_TARGETS - - --- PROCESS_SMOKE_TARGETS class - -- @type PROCESS_SMOKE_TARGETS - -- @field Task#TASK_BASE Task - -- @field Unit#UNIT ProcessUnit - -- @field Set#SET_UNIT TargetSetUnit - -- @field Zone#ZONE_BASE TargetZone - -- @extends Task2#TASK2 - PROCESS_SMOKE_TARGETS = { - ClassName = "PROCESS_SMOKE_TARGETS", - } - - - --- Creates a new task assignment state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator. - -- @param #PROCESS_SMOKE_TARGETS self - -- @param Task#TASK Task - -- @param Unit#UNIT Unit - -- @return #PROCESS_SMOKE_TARGETS self - function PROCESS_SMOKE_TARGETS:New( Task, ProcessUnit, TargetSetUnit, TargetZone ) - - -- Inherits from BASE - local self = BASE:Inherit( self, PROCESS:New( "ASSIGN_MENU_ACCEPT", Task, ProcessUnit ) ) -- #PROCESS_SMOKE_TARGETS - - self.TargetSetUnit = TargetSetUnit - self.TargetZone = TargetZone - - self.Fsm = STATEMACHINE_PROCESS:New( self, { - initial = 'None', - events = { - { name = 'Start', from = 'None', to = 'AwaitSmoke' }, - { name = 'Next', from = 'AwaitSmoke', to = 'Smoking' }, - { name = 'Next', from = 'Smoking', to = 'AwaitSmoke' }, - { name = 'Fail', from = 'Smoking', to = 'Failed' }, - { name = 'Fail', from = 'AwaitSmoke', to = 'Failed' }, - { name = 'Fail', from = 'None', to = 'Failed' }, - }, - callbacks = { - onStart = self.OnStart, - onNext = self.OnNext, - onSmoking = self.OnSmoking, - }, - endstates = { - }, - } ) - - return self - end - - --- StateMachine callback function for a TASK2 - -- @param #PROCESS_SMOKE_TARGETS self - -- @param StateMachine#STATEMACHINE_TASK Fsm - -- @param #string Event - -- @param #string From - -- @param #string To - function PROCESS_SMOKE_TARGETS:OnStart( Fsm, Event, From, To ) - self:E( { Event, From, To, self.ProcessUnit.UnitName} ) - - self:E("Set smoke menu") - - local ProcessGroup = self.ProcessUnit:GetGroup() - local MissionMenu = self.Task.Mission:GetMissionMenu( ProcessGroup ) - - local function MenuSmoke( MenuParam ) - self:E( MenuParam ) - local self = MenuParam.self - local SmokeColor = MenuParam.SmokeColor - self.SmokeColor = SmokeColor - self:NextEvent( self.Fsm.Next ) - end - - self.Menu = MENU_GROUP:New( ProcessGroup, "Target acquisition", MissionMenu ) - self.MenuSmokeBlue = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop blue smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Blue } ) - self.MenuSmokeGreen = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop green smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Green } ) - self.MenuSmokeOrange = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop Orange smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Orange } ) - self.MenuSmokeRed = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop Red smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Red } ) - self.MenuSmokeWhite = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop White smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.White } ) - end - - --- StateMachine callback function for a TASK2 - -- @param #PROCESS_SMOKE_TARGETS self - -- @param StateMachine#STATEMACHINE_PROCESS Fsm - -- @param #string Event - -- @param #string From - -- @param #string To - function PROCESS_SMOKE_TARGETS:OnSmoking( Fsm, Event, From, To ) - self:E( { Event, From, To, self.ProcessUnit.UnitName} ) - - self.TargetSetUnit:ForEachUnit( - --- @param Unit#UNIT SmokeUnit - function( SmokeUnit ) - if math.random( 1, ( 100 * self.TargetSetUnit:Count() ) / 4 ) <= 100 then - SCHEDULER:New( self, - function() - if SmokeUnit:IsAlive() then - SmokeUnit:Smoke( self.SmokeColor, 150 ) - end - end, {}, math.random( 10, 60 ) - ) - end - end - ) - - end - -end \ No newline at end of file diff --git a/Moose Development/Moose/Scheduler.lua b/Moose Development/Moose/Scheduler.lua deleted file mode 100644 index 834a5d987..000000000 --- a/Moose Development/Moose/Scheduler.lua +++ /dev/null @@ -1,202 +0,0 @@ ---- This module contains the SCHEDULER class. --- --- 1) @{Scheduler#SCHEDULER} class, extends @{Base#BASE} --- ===================================================== --- The @{Scheduler#SCHEDULER} class models time events calling given event handling functions. --- --- 1.1) SCHEDULER constructor --- -------------------------- --- The SCHEDULER class is quite easy to use: --- --- * @{Scheduler#SCHEDULER.New}: Setup a new scheduler and start it with the specified parameters. --- --- 1.2) SCHEDULER timer stop and start --- ----------------------------------- --- The SCHEDULER can be stopped and restarted with the following methods: --- --- * @{Scheduler#SCHEDULER.Start}: (Re-)Start the scheduler. --- * @{Scheduler#SCHEDULER.Stop}: Stop the scheduler. --- --- 1.3) Reschedule new time event --- ------------------------------ --- With @{Scheduler#SCHEDULER.Schedule} a new time event can be scheduled. --- --- === --- --- ### Contributions: --- --- * Mechanist : Concept & Testing --- --- ### Authors: --- --- * FlightControl : Design & Programming --- --- === --- --- @module Scheduler - - ---- The SCHEDULER class --- @type SCHEDULER --- @field #number ScheduleID the ID of the scheduler. --- @extends Base#BASE -SCHEDULER = { - ClassName = "SCHEDULER", -} - ---- SCHEDULER constructor. --- @param #SCHEDULER self --- @param #table TimeEventObject Specified for which Moose object the timer is setup. If a value of nil is provided, a scheduler will be setup without an object reference. --- @param #function TimeEventFunction The event function to be called when a timer event occurs. The event function needs to accept the parameters specified in TimeEventFunctionArguments. --- @param #table TimeEventFunctionArguments Optional arguments that can be given as part of scheduler. The arguments need to be given as a table { param1, param 2, ... }. --- @param #number StartSeconds Specifies the amount of seconds that will be waited before the scheduling is started, and the event function is called. --- @param #number RepeatSecondsInterval Specifies the interval in seconds when the scheduler will call the event function. --- @param #number RandomizationFactor Specifies a randomization factor between 0 and 1 to randomize the RepeatSecondsInterval. --- @param #number StopSeconds Specifies the amount of seconds when the scheduler will be stopped. --- @return #SCHEDULER self -function SCHEDULER:New( TimeEventObject, TimeEventFunction, TimeEventFunctionArguments, StartSeconds, RepeatSecondsInterval, RandomizationFactor, StopSeconds ) - local self = BASE:Inherit( self, BASE:New() ) - self:F2( { TimeEventObject, TimeEventFunction, TimeEventFunctionArguments, StartSeconds, RepeatSecondsInterval, RandomizationFactor, StopSeconds } ) - - - self:Schedule( TimeEventObject, TimeEventFunction, TimeEventFunctionArguments, StartSeconds, RepeatSecondsInterval, RandomizationFactor, StopSeconds ) - - return self -end - ---- Schedule a new time event. Note that the schedule will only take place if the scheduler is *started*. Even for a single schedule event, the scheduler needs to be started also. --- @param #SCHEDULER self --- @param #table TimeEventObject Specified for which Moose object the timer is setup. If a value of nil is provided, a scheduler will be setup without an object reference. --- @param #function TimeEventFunction The event function to be called when a timer event occurs. The event function needs to accept the parameters specified in TimeEventFunctionArguments. --- @param #table TimeEventFunctionArguments Optional arguments that can be given as part of scheduler. The arguments need to be given as a table { param1, param 2, ... }. --- @param #number StartSeconds Specifies the amount of seconds that will be waited before the scheduling is started, and the event function is called. --- @param #number RepeatSecondsInterval Specifies the interval in seconds when the scheduler will call the event function. --- @param #number RandomizationFactor Specifies a randomization factor between 0 and 1 to randomize the RepeatSecondsInterval. --- @param #number StopSeconds Specifies the amount of seconds when the scheduler will be stopped. --- @return #SCHEDULER self -function SCHEDULER:Schedule( TimeEventObject, TimeEventFunction, TimeEventFunctionArguments, StartSeconds, RepeatSecondsInterval, RandomizationFactor, StopSeconds ) - self:F2( { TimeEventFunctionArguments, StartSeconds, RepeatSecondsInterval, RandomizationFactor, StopSeconds } ) - - self.TimeEventObject = TimeEventObject - self.TimeEventFunction = TimeEventFunction - self.TimeEventFunctionArguments = TimeEventFunctionArguments - self.StartSeconds = StartSeconds - self.Repeat = false - self.RepeatSecondsInterval = RepeatSecondsInterval or 0 - self.RandomizationFactor = RandomizationFactor or 0 - self.StopSeconds = StopSeconds - - self.StartTime = timer.getTime() - - self:Start() - - return self -end - ---- (Re-)Starts the scheduler. --- @param #SCHEDULER self --- @return #SCHEDULER self -function SCHEDULER:Start() - self:F2( self.TimeEventObject ) - - if self.RepeatSecondsInterval ~= 0 then - self.Repeat = true - end - - if self.StartSeconds then - if self.ScheduleID then - timer.removeFunction( self.ScheduleID ) - end - self.ScheduleID = timer.scheduleFunction( self._Scheduler, self, timer.getTime() + self.StartSeconds + .01 ) - end - - return self -end - ---- Stops the scheduler. --- @param #SCHEDULER self --- @return #SCHEDULER self -function SCHEDULER:Stop() - self:F2( self.TimeEventObject ) - - self.Repeat = false - if self.ScheduleID then - self:E( "Stop Schedule" ) - timer.removeFunction( self.ScheduleID ) - end - self.ScheduleID = nil - - return self -end - --- Private Functions - ---- @param #SCHEDULER self -function SCHEDULER:_Scheduler() - self:F2( self.TimeEventFunctionArguments ) - - local ErrorHandler = function( errmsg ) - - env.info( "Error in SCHEDULER function:" .. errmsg ) - if debug ~= nil then - env.info( debug.traceback() ) - end - - return errmsg - end - - local StartTime = self.StartTime - local StopSeconds = self.StopSeconds - local Repeat = self.Repeat - local RandomizationFactor = self.RandomizationFactor - local RepeatSecondsInterval = self.RepeatSecondsInterval - local ScheduleID = self.ScheduleID - - local Status, Result - if self.TimeEventObject then - Status, Result = xpcall( function() return self.TimeEventFunction( self.TimeEventObject, unpack( self.TimeEventFunctionArguments ) ) end, ErrorHandler ) - else - Status, Result = xpcall( function() return self.TimeEventFunction( unpack( self.TimeEventFunctionArguments ) ) end, ErrorHandler ) - end - - self:T( { "Timer Event2 .. " .. self.ScheduleID, Status, Result, StartTime, RepeatSecondsInterval, RandomizationFactor, StopSeconds } ) - - if Status and ( ( Result == nil ) or ( Result and Result ~= false ) ) then - if Repeat and ( not StopSeconds or ( StopSeconds and timer.getTime() <= StartTime + StopSeconds ) ) then - local ScheduleTime = - timer.getTime() + - self.RepeatSecondsInterval + - math.random( - - ( RandomizationFactor * RepeatSecondsInterval / 2 ), - ( RandomizationFactor * RepeatSecondsInterval / 2 ) - ) + - 0.01 - self:T( { self.TimeEventFunctionArguments, "Repeat:", timer.getTime(), ScheduleTime } ) - return ScheduleTime -- returns the next time the function needs to be called. - else - timer.removeFunction( ScheduleID ) - self.ScheduleID = nil - end - else - timer.removeFunction( ScheduleID ) - self.ScheduleID = nil - end - - return nil -end - - - - - - - - - - - - - - - - diff --git a/Moose Development/Moose/StateMachine.lua b/Moose Development/Moose/StateMachine.lua deleted file mode 100644 index c597cadff..000000000 --- a/Moose Development/Moose/StateMachine.lua +++ /dev/null @@ -1,290 +0,0 @@ ---- This module contains the STATEMACHINE class. --- This development is based on a state machine implementation made by Conroy Kyle. --- The state machine can be found here: https://github.com/kyleconroy/lua-state-machine --- --- I've taken the development and enhanced it to make the state machine hierarchical... --- It is a fantastic development, this module. --- --- === --- --- 1) @{Workflow#STATEMACHINE} class, extends @{Base#BASE} --- ============================================== --- --- 1.1) Add or remove objects from the STATEMACHINE --- -------------------------------------------- --- @module StateMachine --- @author FlightControl - - ---- STATEMACHINE class --- @type STATEMACHINE -STATEMACHINE = { - ClassName = "STATEMACHINE", -} - ---- Creates a new STATEMACHINE object. --- @param #STATEMACHINE self --- @return #STATEMACHINE -function STATEMACHINE:New( options ) - - -- Inherits from BASE - local self = BASE:Inherit( self, BASE:New() ) - - - --local self = routines.utils.deepCopy( self ) -- Create a new self instance - - assert(options.events) - - --local MT = {} - --setmetatable( self, MT ) - --self.__index = self - - self.options = options - self.current = options.initial or 'none' - self.events = {} - self.subs = {} - self.endstates = {} - - for _, event in ipairs(options.events or {}) do - local name = event.name - self[name] = self[name] or self:_create_transition(name) - self.events[name] = self.events[name] or { map = {} } - self:_add_to_map(self.events[name].map, event) - end - - for name, callback in pairs(options.callbacks or {}) do - self[name] = callback - end - - for name, sub in pairs( options.subs or {} ) do - self:_submap( self.subs, sub, name ) - end - - for name, endstate in pairs( options.endstates or {} ) do - self.endstates[endstate] = endstate - end - - return self -end - -function STATEMACHINE:LoadCallBacks( CallBackTable ) - - for name, callback in pairs( CallBackTable or {} ) do - self[name] = callback - end - -end - -function STATEMACHINE:_submap( subs, sub, name ) - self:E( { sub = sub, name = name } ) - subs[sub.onstateparent] = subs[sub.onstateparent] or {} - subs[sub.onstateparent][sub.oneventparent] = subs[sub.onstateparent][sub.oneventparent] or {} - local Index = #subs[sub.onstateparent][sub.oneventparent] + 1 - subs[sub.onstateparent][sub.oneventparent][Index] = {} - subs[sub.onstateparent][sub.oneventparent][Index].fsm = sub.fsm - subs[sub.onstateparent][sub.oneventparent][Index].event = sub.event - subs[sub.onstateparent][sub.oneventparent][Index].returnevents = sub.returnevents -- these events need to be given to find the correct continue event ... if none given, the processing will stop. - subs[sub.onstateparent][sub.oneventparent][Index].name = name - subs[sub.onstateparent][sub.oneventparent][Index].fsmparent = self -end - - -function STATEMACHINE:_call_handler(handler, params) - if handler then - return handler(unpack(params)) - end -end - -function STATEMACHINE:_create_transition(name) - self:E( { name = name } ) - return function(self, ...) - local can, to = self:can(name) - self:T( { name, can, to } ) - - if can then - local from = self.current - local params = { self, name, from, to, ... } - - if self:_call_handler(self["onbefore" .. name], params) == false - or self:_call_handler(self["onleave" .. from], params) == false then - return false - end - - self.current = to - - local execute = true - - local subtable = self:_gosub( to, name ) - for _, sub in pairs( subtable ) do - self:F( "calling sub: " .. sub.event ) - sub.fsm.fsmparent = self - sub.fsm.returnevents = sub.returnevents - sub.fsm[sub.event]( sub.fsm ) - execute = true - end - - local fsmparent, event = self:_isendstate( to ) - if fsmparent and event then - self:F( { "end state: ", fsmparent, event } ) - self:_call_handler(self["onenter" .. to] or self["on" .. to], params) - self:_call_handler(self["onafter" .. name] or self["on" .. name], params) - self:_call_handler(self["onstatechange"], params) - fsmparent[event]( fsmparent ) - execute = false - end - - if execute then - self:F( { "execute: " .. to, name } ) - self:_call_handler(self["onenter" .. to] or self["on" .. to], params) - self:_call_handler(self["onafter" .. name] or self["on" .. name], params) - self:_call_handler(self["onstatechange"], params) - end - - return true - end - - return false - end -end - -function STATEMACHINE:_gosub( parentstate, parentevent ) - local fsmtable = {} - if self.subs[parentstate] and self.subs[parentstate][parentevent] then - return self.subs[parentstate][parentevent] - else - return {} - end -end - -function STATEMACHINE:_isendstate( state ) - local fsmparent = self.fsmparent - if fsmparent and self.endstates[state] then - self:E( { state = state, endstates = self.endstates, endstate = self.endstates[state] } ) - local returnevent = nil - local fromstate = fsmparent.current - self:E( fromstate ) - self:E( self.returnevents ) - for _, eventname in pairs( self.returnevents ) do - local event = fsmparent.events[eventname] - self:E( event ) - local to = event and event.map[fromstate] or event.map['*'] - if to and to == state then - return fsmparent, eventname - else - self:E( { "could not find parent event name for state", fromstate, to } ) - end - end - end - - return nil -end - -function STATEMACHINE:_add_to_map(map, event) - if type(event.from) == 'string' then - map[event.from] = event.to - else - for _, from in ipairs(event.from) do - map[from] = event.to - end - end -end - -function STATEMACHINE:is(state) - return self.current == state -end - -function STATEMACHINE:can(e) - local event = self.events[e] - local to = event and event.map[self.current] or event.map['*'] - return to ~= nil, to -end - -function STATEMACHINE:cannot(e) - return not self:can(e) -end - -function STATEMACHINE:todot(filename) - local dotfile = io.open(filename,'w') - dotfile:write('digraph {\n') - local transition = function(event,from,to) - dotfile:write(string.format('%s -> %s [label=%s];\n',from,to,event)) - end - for _, event in pairs(self.options.events) do - if type(event.from) == 'table' then - for _, from in ipairs(event.from) do - transition(event.name,from,event.to) - end - else - transition(event.name,event.from,event.to) - end - end - dotfile:write('}\n') - dotfile:close() -end - ---- STATEMACHINE_PROCESS class --- @type STATEMACHINE_PROCESS --- @field Process#PROCESS Process --- @extends StateMachine#STATEMACHINE -STATEMACHINE_PROCESS = { - ClassName = "STATEMACHINE_PROCESS", -} - ---- Creates a new STATEMACHINE_PROCESS object. --- @param #STATEMACHINE_PROCESS self --- @return #STATEMACHINE_PROCESS -function STATEMACHINE_PROCESS:New( Process, options ) - - local FsmProcess = routines.utils.deepCopy( self ) -- Create a new self instance - local Parent = STATEMACHINE:New(options) - - setmetatable( FsmProcess, Parent ) - FsmProcess.__index = FsmProcess - - FsmProcess["onstatechange"] = Process.OnStateChange - FsmProcess.Process = Process - - return FsmProcess -end - -function STATEMACHINE_PROCESS:_call_handler( handler, params ) - if handler then - return handler( self.Process, unpack( params ) ) - end -end - ---- STATEMACHINE_TASK class --- @type STATEMACHINE_TASK --- @field Task#TASK_BASE Task --- @extends StateMachine#STATEMACHINE -STATEMACHINE_TASK = { - ClassName = "STATEMACHINE_TASK", -} - ---- Creates a new STATEMACHINE_TASK object. --- @param #STATEMACHINE_TASK self --- @return #STATEMACHINE_TASK -function STATEMACHINE_TASK:New( Task, TaskUnit, options ) - - local FsmTask = routines.utils.deepCopy( self ) -- Create a new self instance - local Parent = STATEMACHINE:New(options) - - setmetatable( FsmTask, Parent ) - FsmTask.__index = FsmTask - - FsmTask["onstatechange"] = Task.OnStateChange - FsmTask["onAssigned"] = Task.OnAssigned - FsmTask["onSuccess"] = Task.OnSuccess - FsmTask["onFailed"] = Task.OnFailed - - FsmTask.Task = Task - FsmTask.TaskUnit = TaskUnit - - return FsmTask -end - -function STATEMACHINE_TASK:_call_handler( handler, params ) - if handler then - return handler( self.Task, self.TaskUnit, unpack( params ) ) - end -end diff --git a/Moose Development/Moose/Task.lua b/Moose Development/Moose/Task.lua deleted file mode 100644 index aa4b3521d..000000000 --- a/Moose Development/Moose/Task.lua +++ /dev/null @@ -1,864 +0,0 @@ ---- This module contains the TASK_BASE class. --- --- 1) @{#TASK_BASE} class, extends @{Base#BASE} --- ============================================ --- 1.1) The @{#TASK_BASE} class implements the methods for task orchestration within MOOSE. --- ---------------------------------------------------------------------------------------- --- The class provides a couple of methods to: --- --- * @{#TASK_BASE.AssignToGroup}():Assign a task to a group (of players). --- * @{#TASK_BASE.AddProcess}():Add a @{Process} to a task. --- * @{#TASK_BASE.RemoveProcesses}():Remove a running @{Process} from a running task. --- * @{#TASK_BASE.AddStateMachine}():Add a @{StateMachine} to a task. --- * @{#TASK_BASE.RemoveStateMachines}():Remove @{StateMachine}s from a task. --- * @{#TASK_BASE.HasStateMachine}():Enquire if the task has a @{StateMachine} --- * @{#TASK_BASE.AssignToUnit}(): Assign a task to a unit. (Needs to be implemented in the derived classes from @{#TASK_BASE}. --- * @{#TASK_BASE.UnAssignFromUnit}(): Unassign the task from a unit. --- --- 1.2) Set and enquire task status (beyond the task state machine processing). --- ---------------------------------------------------------------------------- --- A task needs to implement as a minimum the following task states: --- --- * **Success**: Expresses the successful execution and finalization of the task. --- * **Failed**: Expresses the failure of a task. --- * **Planned**: Expresses that the task is created, but not yet in execution and is not assigned yet. --- * **Assigned**: Expresses that the task is assigned to a Group of players, and that the task is in execution mode. --- --- A task may also implement the following task states: --- --- * **Rejected**: Expresses that the task is rejected by a player, who was requested to accept the task. --- * **Cancelled**: Expresses that the task is cancelled by HQ or through a logical situation where a cancellation of the task is required. --- --- A task can implement more statusses than the ones outlined above. Please consult the documentation of the specific tasks to understand the different status modelled. --- --- The status of tasks can be set by the methods **State** followed by the task status. An example is `StateAssigned()`. --- The status of tasks can be enquired by the methods **IsState** followed by the task status name. An example is `if IsStateAssigned() then`. --- --- 1.3) Add scoring when reaching a certain task status: --- ----------------------------------------------------- --- Upon reaching a certain task status in a task, additional scoring can be given. If the Mission has a scoring system attached, the scores will be added to the mission scoring. --- Use the method @{#TASK_BASE.AddScore}() to add scores when a status is reached. --- --- 1.4) Task briefing: --- ------------------- --- A task briefing can be given that is shown to the player when he is assigned to the task. --- --- === --- --- ### Authors: FlightControl - Design and Programming --- --- @module Task - ---- The TASK_BASE class --- @type TASK_BASE --- @field Scheduler#SCHEDULER TaskScheduler --- @field Mission#MISSION Mission --- @field StateMachine#STATEMACHINE Fsm --- @field Set#SET_GROUP SetGroup The Set of Groups assigned to the Task --- @extends Base#BASE -TASK_BASE = { - ClassName = "TASK_BASE", - TaskScheduler = nil, - Processes = {}, - Players = nil, - Scores = {}, - Menu = {}, - SetGroup = nil, -} - - ---- Instantiates a new TASK_BASE. Should never be used. Interface Class. --- @param #TASK_BASE self --- @param Mission#MISSION The mission wherein the Task is registered. --- @param Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. --- @param #string TaskName The name of the Task --- @param #string TaskType The type of the Task --- @param #string TaskCategory The category of the Task (A2G, A2A, Transport, ... ) --- @return #TASK_BASE self -function TASK_BASE:New( Mission, SetGroup, TaskName, TaskType, TaskCategory ) - - local self = BASE:Inherit( self, BASE:New() ) - self:E( "New TASK " .. TaskName ) - - self.Processes = {} - self.Fsm = {} - - self.Mission = Mission - self.SetGroup = SetGroup - - self:SetCategory( TaskCategory ) - self:SetType( TaskType ) - self:SetName( TaskName ) - self:SetID( Mission:GetNextTaskID( self ) ) -- The Mission orchestrates the task sequences .. - - self.TaskBriefing = "You are assigned to the task: " .. self.TaskName .. "." - - return self -end - ---- Cleans all references of a TASK_BASE. --- @param #TASK_BASE self --- @return #nil -function TASK_BASE:CleanUp() - - _EVENTDISPATCHER:OnPlayerLeaveRemove( self ) - _EVENTDISPATCHER:OnDeadRemove( self ) - _EVENTDISPATCHER:OnCrashRemove( self ) - _EVENTDISPATCHER:OnPilotDeadRemove( self ) - - return nil -end - - ---- Assign the @{Task}to a @{Group}. --- @param #TASK_BASE self --- @param Group#GROUP TaskGroup -function TASK_BASE:AssignToGroup( TaskGroup ) - self:F2( TaskGroup:GetName() ) - - local TaskGroupName = TaskGroup:GetName() - - TaskGroup:SetState( TaskGroup, "Assigned", self ) - - self:RemoveMenuForGroup( TaskGroup ) - self:SetAssignedMenuForGroup( TaskGroup ) - - local TaskUnits = TaskGroup:GetUnits() - for UnitID, UnitData in pairs( TaskUnits ) do - local TaskUnit = UnitData -- Unit#UNIT - local PlayerName = TaskUnit:GetPlayerName() - if PlayerName ~= nil or PlayerName ~= "" then - self:AssignToUnit( TaskUnit ) - end - end -end - ---- Send the briefng message of the @{Task} to the assigned @{Group}s. --- @param #TASK_BASE self -function TASK_BASE:SendBriefingToAssignedGroups() - self:F2() - - for TaskGroupName, TaskGroup in pairs( self.SetGroup:GetSet() ) do - - if self:IsAssignedToGroup( TaskGroup ) then - TaskGroup:Message( self.TaskBriefing, 60 ) - end - end -end - - ---- Assign the @{Task} from the @{Group}s. --- @param #TASK_BASE self -function TASK_BASE:UnAssignFromGroups() - self:F2() - - for TaskGroupName, TaskGroup in pairs( self.SetGroup:GetSet() ) do - - TaskGroup:SetState( TaskGroup, "Assigned", nil ) - local TaskUnits = TaskGroup:GetUnits() - for UnitID, UnitData in pairs( TaskUnits ) do - local TaskUnit = UnitData -- Unit#UNIT - local PlayerName = TaskUnit:GetPlayerName() - if PlayerName ~= nil or PlayerName ~= "" then - self:UnAssignFromUnit( TaskUnit ) - end - end - end -end - ---- Returns if the @{Task} is assigned to the Group. --- @param #TASK_BASE self --- @param Group#GROUP TaskGroup --- @return #boolean -function TASK_BASE:IsAssignedToGroup( TaskGroup ) - - local TaskGroupName = TaskGroup:GetName() - - if self:IsStateAssigned() then - if TaskGroup:GetState( TaskGroup, "Assigned" ) == self then - return true - end - end - - return false -end - ---- Assign the @{Task}to an alive @{Unit}. --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @return #TASK_BASE self -function TASK_BASE:AssignToUnit( TaskUnit ) - self:F( TaskUnit:GetName() ) - - return nil -end - ---- UnAssign the @{Task} from an alive @{Unit}. --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @return #TASK_BASE self -function TASK_BASE:UnAssignFromUnit( TaskUnitName ) - self:F( TaskUnitName ) - - if self:HasStateMachine( TaskUnitName ) == true then - self:RemoveStateMachines( TaskUnitName ) - self:RemoveProcesses( TaskUnitName ) - end - - return self -end - ---- Set the menu options of the @{Task} to all the groups in the SetGroup. --- @param #TASK_BASE self --- @return #TASK_BASE self -function TASK_BASE:SetPlannedMenu() - - local MenuText = self:GetPlannedMenuText() - for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - if not self:IsAssignedToGroup( TaskGroup ) then - self:SetPlannedMenuForGroup( TaskGroup, MenuText ) - end - end -end - ---- Set the menu options of the @{Task} to all the groups in the SetGroup. --- @param #TASK_BASE self --- @return #TASK_BASE self -function TASK_BASE:SetAssignedMenu() - - for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - if self:IsAssignedToGroup( TaskGroup ) then - self:SetAssignedMenuForGroup( TaskGroup ) - end - end -end - ---- Remove the menu options of the @{Task} to all the groups in the SetGroup. --- @param #TASK_BASE self --- @return #TASK_BASE self -function TASK_BASE:RemoveMenu() - - for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - self:RemoveMenuForGroup( TaskGroup ) - end -end - ---- Set the planned menu option of the @{Task}. --- @param #TASK_BASE self --- @param Group#GROUP TaskGroup --- @param #string MenuText The menu text. --- @return #TASK_BASE self -function TASK_BASE:SetPlannedMenuForGroup( TaskGroup, MenuText ) - self:E( TaskGroup:GetName() ) - - local TaskMission = self.Mission:GetName() - local TaskCategory = self:GetCategory() - local TaskType = self:GetType() - - local Mission = self.Mission - - Mission.MenuMission = Mission.MenuMission or {} - local MenuMission = Mission.MenuMission - - Mission.MenuCategory = Mission.MenuCategory or {} - local MenuCategory = Mission.MenuCategory - - Mission.MenuType = Mission.MenuType or {} - local MenuType = Mission.MenuType - - self.Menu = self.Menu or {} - local Menu = self.Menu - - local TaskGroupName = TaskGroup:GetName() - MenuMission[TaskGroupName] = MenuMission[TaskGroupName] or MENU_GROUP:New( TaskGroup, TaskMission, nil ) - - MenuCategory[TaskGroupName] = MenuCategory[TaskGroupName] or {} - MenuCategory[TaskGroupName][TaskCategory] = MenuCategory[TaskGroupName][TaskCategory] or MENU_GROUP:New( TaskGroup, TaskCategory, MenuMission[TaskGroupName] ) - - MenuType[TaskGroupName] = MenuType[TaskGroupName] or {} - MenuType[TaskGroupName][TaskType] = MenuType[TaskGroupName][TaskType] or MENU_GROUP:New( TaskGroup, TaskType, MenuCategory[TaskGroupName][TaskCategory] ) - - if Menu[TaskGroupName] then - Menu[TaskGroupName]:Remove() - end - Menu[TaskGroupName] = MENU_GROUP_COMMAND:New( TaskGroup, MenuText, MenuType[TaskGroupName][TaskType], self.MenuAssignToGroup, { self = self, TaskGroup = TaskGroup } ) - - return self -end - ---- Set the assigned menu options of the @{Task}. --- @param #TASK_BASE self --- @param Group#GROUP TaskGroup --- @return #TASK_BASE self -function TASK_BASE:SetAssignedMenuForGroup( TaskGroup ) - self:E( TaskGroup:GetName() ) - - local TaskMission = self.Mission:GetName() - - local Mission = self.Mission - - Mission.MenuMission = Mission.MenuMission or {} - local MenuMission = Mission.MenuMission - - self.MenuStatus = self.MenuStatus or {} - local MenuStatus = self.MenuStatus - - - self.MenuAbort = self.MenuAbort or {} - local MenuAbort = self.MenuAbort - - local TaskGroupName = TaskGroup:GetName() - MenuMission[TaskGroupName] = MenuMission[TaskGroupName] or MENU_GROUP:New( TaskGroup, TaskMission, nil ) - MenuStatus[TaskGroupName] = MENU_GROUP_COMMAND:New( TaskGroup, "Task Status", MenuMission[TaskGroupName], self.MenuTaskStatus, { self = self, TaskGroup = TaskGroup } ) - MenuAbort[TaskGroupName] = MENU_GROUP_COMMAND:New( TaskGroup, "Abort Task", MenuMission[TaskGroupName], self.MenuTaskAbort, { self = self, TaskGroup = TaskGroup } ) - - return self -end - ---- Remove the menu option of the @{Task} for a @{Group}. --- @param #TASK_BASE self --- @param Group#GROUP TaskGroup --- @return #TASK_BASE self -function TASK_BASE:RemoveMenuForGroup( TaskGroup ) - - local TaskGroupName = TaskGroup:GetName() - - local Mission = self.Mission - local MenuMission = Mission.MenuMission - local MenuCategory = Mission.MenuCategory - local MenuType = Mission.MenuType - local MenuStatus = self.MenuStatus - local MenuAbort = self.MenuAbort - local Menu = self.Menu - - Menu = Menu or {} - if Menu[TaskGroupName] then - Menu[TaskGroupName]:Remove() - Menu[TaskGroupName] = nil - end - - MenuType = MenuType or {} - if MenuType[TaskGroupName] then - for _, Menu in pairs( MenuType[TaskGroupName] ) do - Menu:Remove() - end - MenuType[TaskGroupName] = nil - end - - MenuCategory = MenuCategory or {} - if MenuCategory[TaskGroupName] then - for _, Menu in pairs( MenuCategory[TaskGroupName] ) do - Menu:Remove() - end - MenuCategory[TaskGroupName] = nil - end - - MenuStatus = MenuStatus or {} - if MenuStatus[TaskGroupName] then - MenuStatus[TaskGroupName]:Remove() - MenuStatus[TaskGroupName] = nil - end - - MenuAbort = MenuAbort or {} - if MenuAbort[TaskGroupName] then - MenuAbort[TaskGroupName]:Remove() - MenuAbort[TaskGroupName] = nil - end - -end - -function TASK_BASE.MenuAssignToGroup( MenuParam ) - - local self = MenuParam.self - local TaskGroup = MenuParam.TaskGroup - - self:AssignToGroup( TaskGroup ) -end - -function TASK_BASE.MenuTaskStatus( MenuParam ) - - local self = MenuParam.self - local TaskGroup = MenuParam.TaskGroup - - --self:AssignToGroup( TaskGroup ) -end - -function TASK_BASE.MenuTaskAbort( MenuParam ) - - local self = MenuParam.self - local TaskGroup = MenuParam.TaskGroup - - --self:AssignToGroup( TaskGroup ) -end - - - ---- Returns the @{Task} name. --- @param #TASK_BASE self --- @return #string TaskName -function TASK_BASE:GetTaskName() - return self.TaskName -end - - ---- Add Process to @{Task} with key @{Unit}. --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @return #TASK_BASE self -function TASK_BASE:AddProcess( TaskUnit, Process ) - local TaskUnitName = TaskUnit:GetName() - self.Processes = self.Processes or {} - self.Processes[TaskUnitName] = self.Processes[TaskUnitName] or {} - self.Processes[TaskUnitName][#self.Processes[TaskUnitName]+1] = Process - return Process -end - - ---- Remove Processes from @{Task} with key @{Unit} --- @param #TASK_BASE self --- @param #string TaskUnitName --- @return #TASK_BASE self -function TASK_BASE:RemoveProcesses( TaskUnitName ) - - for ProcessID, ProcessData in pairs( self.Processes[TaskUnitName] ) do - local Process = ProcessData -- Process#PROCESS - Process:StopEvents() - Process = nil - self.Processes[TaskUnitName][ProcessID] = nil - self:E( self.Processes[TaskUnitName][ProcessID] ) - end - self.Processes[TaskUnitName] = nil -end - ---- Fail processes from @{Task} with key @{Unit} --- @param #TASK_BASE self --- @param #string TaskUnitName --- @return #TASK_BASE self -function TASK_BASE:FailProcesses( TaskUnitName ) - - for ProcessID, ProcessData in pairs( self.Processes[TaskUnitName] ) do - local Process = ProcessData -- Process#PROCESS - Process.Fsm:Fail() - end -end - ---- Add a FiniteStateMachine to @{Task} with key @{Unit} --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @return #TASK_BASE self -function TASK_BASE:AddStateMachine( TaskUnit, Fsm ) - local TaskUnitName = TaskUnit:GetName() - self.Fsm[TaskUnitName] = self.Fsm[TaskUnitName] or {} - self.Fsm[TaskUnitName][#self.Fsm[TaskUnitName]+1] = Fsm - return Fsm -end - ---- Remove FiniteStateMachines from @{Task} with key @{Unit} --- @param #TASK_BASE self --- @param #string TaskUnitName --- @return #TASK_BASE self -function TASK_BASE:RemoveStateMachines( TaskUnitName ) - - for _, Fsm in pairs( self.Fsm[TaskUnitName] ) do - Fsm = nil - self.Fsm[TaskUnitName][_] = nil - self:E( self.Fsm[TaskUnitName][_] ) - end - self.Fsm[TaskUnitName] = nil -end - ---- Checks if there is a FiniteStateMachine assigned to @{Unit} for @{Task} --- @param #TASK_BASE self --- @param #string TaskUnitName --- @return #TASK_BASE self -function TASK_BASE:HasStateMachine( TaskUnitName ) - - self:F( { TaskUnitName, self.Fsm[TaskUnitName] ~= nil } ) - return ( self.Fsm[TaskUnitName] ~= nil ) -end - - - - - ---- Register a potential new assignment for a new spawned @{Unit}. --- Tasks only get assigned if there are players in it. --- @param #TASK_BASE self --- @param Event#EVENTDATA Event --- @return #TASK_BASE self -function TASK_BASE:_EventAssignUnit( Event ) - if Event.IniUnit then - self:F( Event ) - local TaskUnit = Event.IniUnit - if TaskUnit:IsAlive() then - local TaskPlayerName = TaskUnit:GetPlayerName() - if TaskPlayerName ~= nil then - if not self:HasStateMachine( TaskUnit ) then - -- Check if the task was assigned to the group, if it was assigned to the group, assign to the unit just spawned and initiate the processes. - local TaskGroup = TaskUnit:GetGroup() - if self:IsAssignedToGroup( TaskGroup ) then - self:AssignToUnit( TaskUnit ) - end - end - end - end - end - return nil -end - ---- Catches the "player leave unit" event for a @{Unit} .... --- When a player is an air unit, and leaves the unit: --- --- * and he is not at an airbase runway on the ground, he will fail its task. --- * and he is on an airbase and on the ground, the process for him will just continue to work, he can switch airplanes, and take-off again. --- This is important to model the change from plane types for a player during mission assignment. --- @param #TASK_BASE self --- @param Event#EVENTDATA Event --- @return #TASK_BASE self -function TASK_BASE:_EventPlayerLeaveUnit( Event ) - self:F( Event ) - if Event.IniUnit then - local TaskUnit = Event.IniUnit - local TaskUnitName = Event.IniUnitName - - -- Check if for this unit in the task there is a process ongoing. - if self:HasStateMachine( TaskUnitName ) then - if TaskUnit:IsAir() then - if TaskUnit:IsAboveRunway() then - -- do nothing - else - self:E( "IsNotAboveRunway" ) - -- Player left airplane during an assigned task and was not at an airbase. - self:FailProcesses( TaskUnitName ) - self:UnAssignFromUnit( TaskUnitName ) - end - end - end - - end - return nil -end - ---- UnAssigns a @{Unit} that is left by a player, crashed, dead, .... --- There are only assignments if there are players in it. --- @param #TASK_BASE self --- @param Event#EVENTDATA Event --- @return #TASK_BASE self -function TASK_BASE:_EventDead( Event ) - self:F( Event ) - if Event.IniUnit then - local TaskUnit = Event.IniUnit - local TaskUnitName = Event.IniUnitName - - -- Check if for this unit in the task there is a process ongoing. - if self:HasStateMachine( TaskUnitName ) then - self:FailProcesses( TaskUnitName ) - self:UnAssignFromUnit( TaskUnitName ) - end - - local TaskGroup = Event.IniUnit:GetGroup() - TaskGroup:SetState( TaskGroup, "Assigned", nil ) - end - return nil -end - ---- Gets the Scoring of the task --- @param #TASK_BASE self --- @return Scoring#SCORING Scoring -function TASK_BASE:GetScoring() - return self.Mission:GetScoring() -end - - ---- Gets the Task Index, which is a combination of the Task category, the Task type, the Task name. --- @param #TASK_BASE self --- @return #string The Task ID -function TASK_BASE:GetTaskIndex() - - local TaskCategory = self:GetCategory() - local TaskType = self:GetType() - local TaskName = self:GetName() - - return TaskCategory .. "." ..TaskType .. "." .. TaskName -end - ---- Sets the Name of the Task --- @param #TASK_BASE self --- @param #string TaskName -function TASK_BASE:SetName( TaskName ) - self.TaskName = TaskName -end - ---- Gets the Name of the Task --- @param #TASK_BASE self --- @return #string The Task Name -function TASK_BASE:GetName() - return self.TaskName -end - ---- Sets the Type of the Task --- @param #TASK_BASE self --- @param #string TaskType -function TASK_BASE:SetType( TaskType ) - self.TaskType = TaskType -end - ---- Gets the Type of the Task --- @param #TASK_BASE self --- @return #string TaskType -function TASK_BASE:GetType() - return self.TaskType -end - ---- Sets the Category of the Task --- @param #TASK_BASE self --- @param #string TaskCategory -function TASK_BASE:SetCategory( TaskCategory ) - self.TaskCategory = TaskCategory -end - ---- Gets the Category of the Task --- @param #TASK_BASE self --- @return #string TaskCategory -function TASK_BASE:GetCategory() - return self.TaskCategory -end - ---- Sets the ID of the Task --- @param #TASK_BASE self --- @param #string TaskID -function TASK_BASE:SetID( TaskID ) - self.TaskID = TaskID -end - ---- Gets the ID of the Task --- @param #TASK_BASE self --- @return #string TaskID -function TASK_BASE:GetID() - return self.TaskID -end - - ---- Sets a @{Task} to status **Success**. --- @param #TASK_BASE self -function TASK_BASE:StateSuccess() - self:SetState( self, "State", "Success" ) - return self -end - ---- Is the @{Task} status **Success**. --- @param #TASK_BASE self -function TASK_BASE:IsStateSuccess() - return self:GetStateString() == "Success" -end - ---- Sets a @{Task} to status **Failed**. --- @param #TASK_BASE self -function TASK_BASE:StateFailed() - self:SetState( self, "State", "Failed" ) - return self -end - ---- Is the @{Task} status **Failed**. --- @param #TASK_BASE self -function TASK_BASE:IsStateFailed() - return self:GetStateString() == "Failed" -end - ---- Sets a @{Task} to status **Planned**. --- @param #TASK_BASE self -function TASK_BASE:StatePlanned() - self:SetState( self, "State", "Planned" ) - return self -end - ---- Is the @{Task} status **Planned**. --- @param #TASK_BASE self -function TASK_BASE:IsStatePlanned() - return self:GetStateString() == "Planned" -end - ---- Sets a @{Task} to status **Assigned**. --- @param #TASK_BASE self -function TASK_BASE:StateAssigned() - self:SetState( self, "State", "Assigned" ) - return self -end - ---- Is the @{Task} status **Assigned**. --- @param #TASK_BASE self -function TASK_BASE:IsStateAssigned() - return self:GetStateString() == "Assigned" -end - ---- Sets a @{Task} to status **Hold**. --- @param #TASK_BASE self -function TASK_BASE:StateHold() - self:SetState( self, "State", "Hold" ) - return self -end - ---- Is the @{Task} status **Hold**. --- @param #TASK_BASE self -function TASK_BASE:IsStateHold() - return self:GetStateString() == "Hold" -end - ---- Sets a @{Task} to status **Replanned**. --- @param #TASK_BASE self -function TASK_BASE:StateReplanned() - self:SetState( self, "State", "Replanned" ) - return self -end - ---- Is the @{Task} status **Replanned**. --- @param #TASK_BASE self -function TASK_BASE:IsStateReplanned() - return self:GetStateString() == "Replanned" -end - ---- Gets the @{Task} status. --- @param #TASK_BASE self -function TASK_BASE:GetStateString() - return self:GetState( self, "State" ) -end - ---- Sets a @{Task} briefing. --- @param #TASK_BASE self --- @param #string TaskBriefing --- @return #TASK_BASE self -function TASK_BASE:SetBriefing( TaskBriefing ) - self.TaskBriefing = TaskBriefing - return self -end - - - ---- Adds a score for the TASK to be achieved. --- @param #TASK_BASE self --- @param #string TaskStatus is the status of the TASK when the score needs to be given. --- @param #string ScoreText is a text describing the score that is given according the status. --- @param #number Score is a number providing the score of the status. --- @return #TASK_BASE self -function TASK_BASE:AddScore( TaskStatus, ScoreText, Score ) - self:F2( { TaskStatus, ScoreText, Score } ) - - self.Scores[TaskStatus] = self.Scores[TaskStatus] or {} - self.Scores[TaskStatus].ScoreText = ScoreText - self.Scores[TaskStatus].Score = Score - return self -end - ---- StateMachine callback function for a TASK --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @param StateMachine#STATEMACHINE_TASK Fsm --- @param #string Event --- @param #string From --- @param #string To --- @param Event#EVENTDATA Event -function TASK_BASE:OnAssigned( TaskUnit, Fsm, Event, From, To ) - - self:E("Assigned") - - local TaskGroup = TaskUnit:GetGroup() - - TaskGroup:Message( self.TaskBriefing, 20 ) - - self:RemoveMenuForGroup( TaskGroup ) - self:SetAssignedMenuForGroup( TaskGroup ) - -end - - ---- StateMachine callback function for a TASK --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @param StateMachine#STATEMACHINE_TASK Fsm --- @param #string Event --- @param #string From --- @param #string To --- @param Event#EVENTDATA Event -function TASK_BASE:OnSuccess( TaskUnit, Fsm, Event, From, To ) - - self:E("Success") - - self:UnAssignFromGroups() - - local TaskGroup = TaskUnit:GetGroup() - self.Mission:SetPlannedMenu() - - self:StateSuccess() - - -- The task has become successful, the event catchers can be cleaned. - self:CleanUp() - -end - ---- StateMachine callback function for a TASK --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @param StateMachine#STATEMACHINE_TASK Fsm --- @param #string Event --- @param #string From --- @param #string To --- @param Event#EVENTDATA Event -function TASK_BASE:OnFailed( TaskUnit, Fsm, Event, From, To ) - - self:E( { "Failed for unit ", TaskUnit:GetName(), TaskUnit:GetPlayerName() } ) - - -- A task cannot be "failed", so a task will always be there waiting for players to join. - -- When the player leaves its unit, we will need to check whether he was on the ground or not at an airbase. - -- When the player crashes, we will need to check whether in the group there are other players still active. It not, we reset the task from Assigned to Planned, otherwise, we just leave as Assigned. - - self:UnAssignFromGroups() - self:StatePlanned() - -end - ---- StateMachine callback function for a TASK --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @param StateMachine#STATEMACHINE_TASK Fsm --- @param #string Event --- @param #string From --- @param #string To --- @param Event#EVENTDATA Event -function TASK_BASE:OnStateChange( TaskUnit, Fsm, Event, From, To ) - - if self:IsTrace() then - MESSAGE:New( "Task " .. self.TaskName .. " : " .. Event .. " changed to state " .. To, 15 ):ToAll() - end - - self:E( { Event, From, To } ) - self:SetState( self, "State", To ) - - if self.Scores[To] then - local Scoring = self:GetScoring() - if Scoring then - Scoring:_AddMissionScore( self.Mission, self.Scores[To].ScoreText, self.Scores[To].Score ) - end - end - -end - - ---- @param #TASK_BASE self -function TASK_BASE:_Schedule() - self:F2() - - self.TaskScheduler = SCHEDULER:New( self, _Scheduler, {}, 15, 15 ) - return self -end - - ---- @param #TASK_BASE self -function TASK_BASE._Scheduler() - self:F2() - - return true -end - - - - diff --git a/Moose Development/Moose/Task_A2G.lua b/Moose Development/Moose/Task_A2G.lua deleted file mode 100644 index e30cc205b..000000000 --- a/Moose Development/Moose/Task_A2G.lua +++ /dev/null @@ -1,150 +0,0 @@ ---- This module contains the TASK_A2G classes. --- --- 1) @{#TASK_A2G} class, extends @{Task#TASK_BASE} --- ================================================= --- The @{#TASK_A2G} class defines a CAS or BAI task of a @{Set} of Target Units, --- located at a Target Zone, based on the tasking capabilities defined in @{Task#TASK_BASE}. --- The TASK_A2G is implemented using a @{Statemachine#STATEMACHINE_TASK}, and has the following statuses: --- --- * **None**: Start of the process --- * **Planned**: The SEAD task is planned. Upon Planned, the sub-process @{Process_Assign#PROCESS_ASSIGN_ACCEPT} is started to accept the task. --- * **Assigned**: The SEAD task is assigned to a @{Group#GROUP}. Upon Assigned, the sub-process @{Process_Route#PROCESS_ROUTE} is started to route the active Units in the Group to the attack zone. --- * **Success**: The SEAD task is successfully completed. Upon Success, the sub-process @{Process_SEAD#PROCESS_SEAD} is started to follow-up successful SEADing of the targets assigned in the task. --- * **Failed**: The SEAD task has failed. This will happen if the player exists the task early, without communicating a possible cancellation to HQ. --- --- === --- --- ### Authors: FlightControl - Design and Programming --- --- @module Task_A2G - - -do -- TASK_A2G - - --- The TASK_A2G class - -- @type TASK_A2G - -- @extends Task#TASK_BASE - TASK_A2G = { - ClassName = "TASK_A2G", - } - - --- Instantiates a new TASK_A2G. - -- @param #TASK_A2G self - -- @param Mission#MISSION Mission - -- @param Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. - -- @param #string TaskName The name of the Task. - -- @param #string TaskType BAI or CAS - -- @param Set#SET_UNIT UnitSetTargets - -- @param Zone#ZONE_BASE TargetZone - -- @return #TASK_A2G self - function TASK_A2G:New( Mission, SetGroup, TaskName, TaskType, TargetSetUnit, TargetZone, FACUnit ) - local self = BASE:Inherit( self, TASK_BASE:New( Mission, SetGroup, TaskName, TaskType, "A2G" ) ) - self:F() - - self.TargetSetUnit = TargetSetUnit - self.TargetZone = TargetZone - self.FACUnit = FACUnit - - _EVENTDISPATCHER:OnPlayerLeaveUnit( self._EventPlayerLeaveUnit, self ) - _EVENTDISPATCHER:OnDead( self._EventDead, self ) - _EVENTDISPATCHER:OnCrash( self._EventDead, self ) - _EVENTDISPATCHER:OnPilotDead( self._EventDead, self ) - - return self - end - - --- Removes a TASK_A2G. - -- @param #TASK_A2G self - -- @return #nil - function TASK_A2G:CleanUp() - - self:GetParent( self ):CleanUp() - - return nil - end - - - --- Assign the @{Task} to a @{Unit}. - -- @param #TASK_A2G self - -- @param Unit#UNIT TaskUnit - -- @return #TASK_A2G self - function TASK_A2G:AssignToUnit( TaskUnit ) - self:F( TaskUnit:GetName() ) - - local ProcessAssign = self:AddProcess( TaskUnit, PROCESS_ASSIGN_ACCEPT:New( self, TaskUnit, self.TaskBriefing ) ) - local ProcessRoute = self:AddProcess( TaskUnit, PROCESS_ROUTE:New( self, TaskUnit, self.TargetZone ) ) - local ProcessDestroy = self:AddProcess( TaskUnit, PROCESS_DESTROY:New( self, self.TaskType, TaskUnit, self.TargetSetUnit ) ) - local ProcessSmoke = self:AddProcess( TaskUnit, PROCESS_SMOKE_TARGETS:New( self, TaskUnit, self.TargetSetUnit, self.TargetZone ) ) - local ProcessJTAC = self:AddProcess( TaskUnit, PROCESS_JTAC:New( self, TaskUnit, self.TargetSetUnit, self.FACUnit ) ) - - local Process = self:AddStateMachine( TaskUnit, STATEMACHINE_TASK:New( self, TaskUnit, { - initial = 'None', - events = { - { name = 'Next', from = 'None', to = 'Planned' }, - { name = 'Next', from = 'Planned', to = 'Assigned' }, - { name = 'Reject', from = 'Planned', to = 'Rejected' }, - { name = 'Next', from = 'Assigned', to = 'Success' }, - { name = 'Fail', from = 'Assigned', to = 'Failed' }, - { name = 'Fail', from = 'Arrived', to = 'Failed' } - }, - callbacks = { - onNext = self.OnNext, - onRemove = self.OnRemove, - }, - subs = { - Assign = { onstateparent = 'Planned', oneventparent = 'Next', fsm = ProcessAssign.Fsm, event = 'Start', returnevents = { 'Next', 'Reject' } }, - Route = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessRoute.Fsm, event = 'Start' }, - Destroy = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessDestroy.Fsm, event = 'Start', returnevents = { 'Next' } }, - Smoke = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessSmoke.Fsm, event = 'Start', }, - JTAC = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessJTAC.Fsm, event = 'Start', }, - } - } ) ) - - ProcessRoute:AddScore( "Failed", "failed to destroy a ground unit", -100 ) - ProcessDestroy:AddScore( "Destroy", "destroyed a ground unit", 25 ) - ProcessDestroy:AddScore( "Failed", "failed to destroy a ground unit", -100 ) - - Process:Next() - - return self - end - - --- StateMachine callback function for a TASK - -- @param #TASK_A2G self - -- @param StateMachine#STATEMACHINE_TASK Fsm - -- @param #string Event - -- @param #string From - -- @param #string To - -- @param Event#EVENTDATA Event - function TASK_A2G:OnNext( Fsm, Event, From, To, Event ) - - self:SetState( self, "State", To ) - - end - - --- @param #TASK_A2G self - function TASK_A2G:GetPlannedMenuText() - return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.TargetSetUnit:GetUnitTypesText() .. " )" - end - - - --- @param #TASK_A2G self - function TASK_A2G:_Schedule() - self:F2() - - self.TaskScheduler = SCHEDULER:New( self, _Scheduler, {}, 15, 15 ) - return self - end - - - --- @param #TASK_A2G self - function TASK_A2G._Scheduler() - self:F2() - - return true - end - -end - - - diff --git a/Moose Development/Moose/Task_SEAD.lua b/Moose Development/Moose/Task_SEAD.lua deleted file mode 100644 index 9cba080f9..000000000 --- a/Moose Development/Moose/Task_SEAD.lua +++ /dev/null @@ -1,146 +0,0 @@ ---- This module contains the TASK_SEAD classes. --- --- 1) @{#TASK_SEAD} class, extends @{Task#TASK_BASE} --- ================================================= --- The @{#TASK_SEAD} class defines a SEAD task for a @{Set} of Target Units, located at a Target Zone, --- based on the tasking capabilities defined in @{Task#TASK_BASE}. --- The TASK_SEAD is implemented using a @{Statemachine#STATEMACHINE_TASK}, and has the following statuses: --- --- * **None**: Start of the process --- * **Planned**: The SEAD task is planned. Upon Planned, the sub-process @{Process_Assign#PROCESS_ASSIGN_ACCEPT} is started to accept the task. --- * **Assigned**: The SEAD task is assigned to a @{Group#GROUP}. Upon Assigned, the sub-process @{Process_Route#PROCESS_ROUTE} is started to route the active Units in the Group to the attack zone. --- * **Success**: The SEAD task is successfully completed. Upon Success, the sub-process @{Process_SEAD#PROCESS_SEAD} is started to follow-up successful SEADing of the targets assigned in the task. --- * **Failed**: The SEAD task has failed. This will happen if the player exists the task early, without communicating a possible cancellation to HQ. --- --- === --- --- ### Authors: FlightControl - Design and Programming --- --- @module Task_SEAD - - -do -- TASK_SEAD - - --- The TASK_SEAD class - -- @type TASK_SEAD - -- @field Set#SET_UNIT TargetSetUnit - -- @extends Task#TASK_BASE - TASK_SEAD = { - ClassName = "TASK_SEAD", - } - - --- Instantiates a new TASK_SEAD. - -- @param #TASK_SEAD self - -- @param Mission#MISSION Mission - -- @param Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. - -- @param #string TaskName The name of the Task. - -- @param Set#SET_UNIT UnitSetTargets - -- @param Zone#ZONE_BASE TargetZone - -- @return #TASK_SEAD self - function TASK_SEAD:New( Mission, SetGroup, TaskName, TargetSetUnit, TargetZone ) - local self = BASE:Inherit( self, TASK_BASE:New( Mission, SetGroup, TaskName, "SEAD", "A2G" ) ) - self:F() - - self.TargetSetUnit = TargetSetUnit - self.TargetZone = TargetZone - - _EVENTDISPATCHER:OnPlayerLeaveUnit( self._EventPlayerLeaveUnit, self ) - _EVENTDISPATCHER:OnDead( self._EventDead, self ) - _EVENTDISPATCHER:OnCrash( self._EventDead, self ) - _EVENTDISPATCHER:OnPilotDead( self._EventDead, self ) - - return self - end - - --- Removes a TASK_SEAD. - -- @param #TASK_SEAD self - -- @return #nil - function TASK_SEAD:CleanUp() - - self:GetParent(self):CleanUp() - - return nil - end - - - - --- Assign the @{Task} to a @{Unit}. - -- @param #TASK_SEAD self - -- @param Unit#UNIT TaskUnit - -- @return #TASK_SEAD self - function TASK_SEAD:AssignToUnit( TaskUnit ) - self:F( TaskUnit:GetName() ) - - local ProcessAssign = self:AddProcess( TaskUnit, PROCESS_ASSIGN_ACCEPT:New( self, TaskUnit, self.TaskBriefing ) ) - local ProcessRoute = self:AddProcess( TaskUnit, PROCESS_ROUTE:New( self, TaskUnit, self.TargetZone ) ) - local ProcessSEAD = self:AddProcess( TaskUnit, PROCESS_DESTROY:New( self, "SEAD", TaskUnit, self.TargetSetUnit ) ) - local ProcessSmoke = self:AddProcess( TaskUnit, PROCESS_SMOKE_TARGETS:New( self, TaskUnit, self.TargetSetUnit, self.TargetZone ) ) - - local Process = self:AddStateMachine( TaskUnit, STATEMACHINE_TASK:New( self, TaskUnit, { - initial = 'None', - events = { - { name = 'Next', from = 'None', to = 'Planned' }, - { name = 'Next', from = 'Planned', to = 'Assigned' }, - { name = 'Reject', from = 'Planned', to = 'Rejected' }, - { name = 'Next', from = 'Assigned', to = 'Success' }, - { name = 'Fail', from = 'Assigned', to = 'Failed' }, - { name = 'Fail', from = 'Arrived', to = 'Failed' } - }, - callbacks = { - onNext = self.OnNext, - onRemove = self.OnRemove, - }, - subs = { - Assign = { onstateparent = 'Planned', oneventparent = 'Next', fsm = ProcessAssign.Fsm, event = 'Start', returnevents = { 'Next', 'Reject' } }, - Route = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessRoute.Fsm, event = 'Start' }, - Sead = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessSEAD.Fsm, event = 'Start', returnevents = { 'Next' } }, - Smoke = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessSmoke.Fsm, event = 'Start', } - } - } ) ) - - ProcessRoute:AddScore( "Failed", "failed to destroy a radar", -100 ) - ProcessSEAD:AddScore( "Destroy", "destroyed a radar", 25 ) - ProcessSEAD:AddScore( "Failed", "failed to destroy a radar", -100 ) - self:AddScore( "Success", "Destroyed all target radars", 250 ) - - Process:Next() - - return self - end - - --- StateMachine callback function for a TASK - -- @param #TASK_SEAD self - -- @param StateMachine#STATEMACHINE_TASK Fsm - -- @param #string Event - -- @param #string From - -- @param #string To - -- @param Event#EVENTDATA Event - function TASK_SEAD:OnNext( Fsm, Event, From, To ) - - self:SetState( self, "State", To ) - - end - - - --- @param #TASK_SEAD self - function TASK_SEAD:GetPlannedMenuText() - return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.TargetSetUnit:GetUnitTypesText() .. " )" - end - - --- @param #TASK_SEAD self - function TASK_SEAD:_Schedule() - self:F2() - - self.TaskScheduler = SCHEDULER:New( self, _Scheduler, {}, 15, 15 ) - return self - end - - - --- @param #TASK_SEAD self - function TASK_SEAD._Scheduler() - self:F2() - - return true - end - -end diff --git a/Moose Development/Moose/Tasking/CommandCenter.lua b/Moose Development/Moose/Tasking/CommandCenter.lua new file mode 100644 index 000000000..34a5ffad3 --- /dev/null +++ b/Moose Development/Moose/Tasking/CommandCenter.lua @@ -0,0 +1,268 @@ +--- A COMMANDCENTER is the owner of multiple missions within MOOSE. +-- A COMMANDCENTER governs multiple missions, the tasking and the reporting. +-- @module CommandCenter + + + +--- The REPORT class +-- @type REPORT +-- @extends Core.Base#BASE +REPORT = { + ClassName = "REPORT", +} + +--- Create a new REPORT. +-- @param #REPORT self +-- @param #string Title +-- @return #REPORT +function REPORT:New( Title ) + + local self = BASE:Inherit( self, BASE:New() ) + + self.Report = {} + self.Report[#self.Report+1] = Title + + return self +end + +--- Add a new line to a REPORT. +-- @param #REPORT self +-- @param #string Text +-- @return #REPORT +function REPORT:Add( Text ) + self.Report[#self.Report+1] = Text + return self.Report[#self.Report+1] +end + +function REPORT:Text() + return table.concat( self.Report, "\n" ) +end + +--- The COMMANDCENTER class +-- @type COMMANDCENTER +-- @field Wrapper.Group#GROUP HQ +-- @field Dcs.DCSCoalitionWrapper.Object#coalition CommandCenterCoalition +-- @list Missions +-- @extends Core.Base#BASE +COMMANDCENTER = { + ClassName = "COMMANDCENTER", + CommandCenterName = "", + CommandCenterCoalition = nil, + CommandCenterPositionable = nil, + Name = "", +} +--- The constructor takes an IDENTIFIABLE as the HQ command center. +-- @param #COMMANDCENTER self +-- @param Wrapper.Positionable#POSITIONABLE CommandCenterPositionable +-- @param #string CommandCenterName +-- @return #COMMANDCENTER +function COMMANDCENTER:New( CommandCenterPositionable, CommandCenterName ) + + local self = BASE:Inherit( self, BASE:New() ) + + self.CommandCenterPositionable = CommandCenterPositionable + self.CommandCenterName = CommandCenterName or CommandCenterPositionable:GetName() + self.CommandCenterCoalition = CommandCenterPositionable:GetCoalition() + + self.Missions = setmetatable( {}, { __mode = "v" } ) + + self:EventOnBirth( + --- @param #COMMANDCENTER self + --- @param Core.Event#EVENTDATA EventData + function( self, EventData ) + self:E( { EventData } ) + local EventGroup = GROUP:Find( EventData.IniDCSGroup ) + if EventGroup and self:HasGroup( EventGroup ) then + local MenuReporting = MENU_GROUP:New( EventGroup, "Reporting", self.CommandCenterMenu ) + local MenuMissionsSummary = MENU_GROUP_COMMAND:New( EventGroup, "Missions Summary Report", MenuReporting, self.ReportSummary, self, EventGroup ) + local MenuMissionsDetails = MENU_GROUP_COMMAND:New( EventGroup, "Missions Details Report", MenuReporting, self.ReportDetails, self, EventGroup ) + self:ReportSummary( EventGroup ) + end + local PlayerUnit = EventData.IniUnit + for MissionID, Mission in pairs( self:GetMissions() ) do + local Mission = Mission -- Tasking.Mission#MISSION + Mission:JoinUnit( PlayerUnit ) + Mission:ReportDetails() + end + + end + ) + + -- When a player enters a client or a unit, the CommandCenter will check for each Mission and each Task in the Mission if the player has things to do. + -- For these elements, it will= + -- - Set the correct menu. + -- - Assign the PlayerUnit to the Task if required. + -- - Send a message to the other players in the group that this player has joined. + self:EventOnPlayerEnterUnit( + --- @param #COMMANDCENTER self + -- @param Core.Event#EVENTDATA EventData + function( self, EventData ) + local PlayerUnit = EventData.IniUnit + for MissionID, Mission in pairs( self:GetMissions() ) do + local Mission = Mission -- Tasking.Mission#MISSION + Mission:JoinUnit( PlayerUnit ) + Mission:ReportDetails() + end + end + ) + + -- Handle when a player leaves a slot and goes back to spectators ... + -- The PlayerUnit will be UnAssigned from the Task. + -- When there is no Unit left running the Task, the Task goes into Abort... + self:EventOnPlayerLeaveUnit( + --- @param #TASK self + -- @param Core.Event#EVENTDATA EventData + function( self, EventData ) + local PlayerUnit = EventData.IniUnit + for MissionID, Mission in pairs( self:GetMissions() ) do + Mission:AbortUnit( PlayerUnit ) + end + end + ) + + -- Handle when a player leaves a slot and goes back to spectators ... + -- The PlayerUnit will be UnAssigned from the Task. + -- When there is no Unit left running the Task, the Task goes into Abort... + self:EventOnCrash( + --- @param #TASK self + -- @param Core.Event#EVENTDATA EventData + function( self, EventData ) + local PlayerUnit = EventData.IniUnit + for MissionID, Mission in pairs( self:GetMissions() ) do + Mission:CrashUnit( PlayerUnit ) + end + end + ) + + return self +end + +--- Gets the name of the HQ command center. +-- @param #COMMANDCENTER self +-- @return #string +function COMMANDCENTER:GetName() + + return self.HQName +end + +--- Gets the POSITIONABLE of the HQ command center. +-- @param #COMMANDCENTER self +-- @return Wrapper.Positionable#POSITIONABLE +function COMMANDCENTER:GetPositionable() + return self.CommandCenterPositionable +end + +--- Get the Missions governed by the HQ command center. +-- @param #COMMANDCENTER self +-- @return #list +function COMMANDCENTER:GetMissions() + + return self.Missions +end + +--- Add a MISSION to be governed by the HQ command center. +-- @param #COMMANDCENTER self +-- @param Tasking.Mission#MISSION Mission +-- @return Tasking.Mission#MISSION +function COMMANDCENTER:AddMission( Mission ) + + self.Missions[Mission] = Mission + + return Mission +end + +--- Removes a MISSION to be governed by the HQ command center. +-- The given Mission is not nilified. +-- @param #COMMANDCENTER self +-- @param Tasking.Mission#MISSION Mission +-- @return Tasking.Mission#MISSION +function COMMANDCENTER:RemoveMission( Mission ) + + self.Missions[Mission] = nil + + return Mission +end + +--- Sets the menu structure of the Missions governed by the HQ command center. +-- @param #COMMANDCENTER self +function COMMANDCENTER:SetMenu() + self:F() + + self.CommandCenterMenu = self.CommandCenterMenu or MENU_COALITION:New( self.CommandCenterCoalition, "HQ" ) + + for MissionID, Mission in pairs( self:GetMissions() ) do + local Mission = Mission -- Tasking.Mission#MISSION + Mission:SetMenu() + end +end + + +--- Checks of the COMMANDCENTER has a GROUP. +-- @param #COMMANDCENTER self +-- @param Wrapper.Group#GROUP +-- @return #boolean +function COMMANDCENTER:HasGroup( MissionGroup ) + + local Has = false + + for MissionID, Mission in pairs( self.Missions ) do + local Mission = Mission -- Tasking.Mission#MISSION + if Mission:HasGroup( MissionGroup ) then + Has = true + break + end + end + + return Has +end + +--- Send a CC message to a GROUP. +-- @param #COMMANDCENTER self +function COMMANDCENTER:MessageToGroup( Message, TaskGroup ) + + self:GetPositionable():MessageToGroup( Message , 20, TaskGroup ) + +end + +--- Send a CC message to the coalition of the CC. +-- @param #COMMANDCENTER self +function COMMANDCENTER:MessageToCoalition( Message ) + + local CCCoalition = self:GetPositionable():GetCoalition() + self:GetPositionable():MessageToBlue( Message , 20, CCCoalition ) + +end + +--- Report the status of all MISSIONs to a GROUP. +-- Each Mission is listed, with an indication how many Tasks are still to be completed. +-- @param #COMMANDCENTER self +function COMMANDCENTER:ReportSummary( ReportGroup ) + self:E( ReportGroup ) + + local Report = REPORT:New() + + for MissionID, Mission in pairs( self.Missions ) do + local Mission = Mission -- Tasking.Mission#MISSION + Report:Add( " - " .. Mission:ReportOverview() ) + end + + self:GetPositionable():MessageToGroup( Report:Text(), 30, ReportGroup ) + +end + +--- Report the status of a Task to a Group. +-- Report the details of a Mission, listing the Mission, and all the Task details. +-- @param #COMMANDCENTER self +function COMMANDCENTER:ReportDetails( ReportGroup, Task ) + self:E( ReportGroup ) + + local Report = REPORT:New() + + for MissionID, Mission in pairs( self.Missions ) do + local Mission = Mission -- Tasking.Mission#MISSION + Report:Add( " - " .. Mission:ReportDetails() ) + end + + self:GetPositionable():MessageToGroup( Report:Text(), 30, ReportGroup ) +end + diff --git a/Moose Development/Moose/DetectionManager.lua b/Moose Development/Moose/Tasking/DetectionManager.lua similarity index 80% rename from Moose Development/Moose/DetectionManager.lua rename to Moose Development/Moose/Tasking/DetectionManager.lua index 17aad91d8..d40836b6c 100644 --- a/Moose Development/Moose/DetectionManager.lua +++ b/Moose Development/Moose/Tasking/DetectionManager.lua @@ -1,38 +1,38 @@ ---- This module contains the DETECTION_MANAGER class and derived classes. +-- This module contains the DETECTION_MANAGER class and derived classes. -- -- === -- --- 1) @{DetectionManager#DETECTION_MANAGER} class, extends @{Base#BASE} +-- 1) @{Tasking.DetectionManager#DETECTION_MANAGER} class, extends @{Core.Base#BASE} -- ==================================================================== --- The @{DetectionManager#DETECTION_MANAGER} class defines the core functions to report detected objects to groups. +-- The @{Tasking.DetectionManager#DETECTION_MANAGER} class defines the core functions to report detected objects to groups. -- Reportings can be done in several manners, and it is up to the derived classes if DETECTION_MANAGER to model the reporting behaviour. -- -- 1.1) DETECTION_MANAGER constructor: -- ----------------------------------- --- * @{DetectionManager#DETECTION_MANAGER.New}(): Create a new DETECTION_MANAGER instance. +-- * @{Tasking.DetectionManager#DETECTION_MANAGER.New}(): Create a new DETECTION_MANAGER instance. -- -- 1.2) DETECTION_MANAGER reporting: -- --------------------------------- --- Derived DETECTION_MANAGER classes will reports detected units using the method @{DetectionManager#DETECTION_MANAGER.ReportDetected}(). This method implements polymorphic behaviour. +-- Derived DETECTION_MANAGER classes will reports detected units using the method @{Tasking.DetectionManager#DETECTION_MANAGER.ReportDetected}(). This method implements polymorphic behaviour. -- --- The time interval in seconds of the reporting can be changed using the methods @{DetectionManager#DETECTION_MANAGER.SetReportInterval}(). --- To control how long a reporting message is displayed, use @{DetectionManager#DETECTION_MANAGER.SetReportDisplayTime}(). --- Derived classes need to implement the method @{DetectionManager#DETECTION_MANAGER.GetReportDisplayTime}() to use the correct display time for displayed messages during a report. +-- The time interval in seconds of the reporting can be changed using the methods @{Tasking.DetectionManager#DETECTION_MANAGER.SetReportInterval}(). +-- To control how long a reporting message is displayed, use @{Tasking.DetectionManager#DETECTION_MANAGER.SetReportDisplayTime}(). +-- Derived classes need to implement the method @{Tasking.DetectionManager#DETECTION_MANAGER.GetReportDisplayTime}() to use the correct display time for displayed messages during a report. -- --- Reporting can be started and stopped using the methods @{DetectionManager#DETECTION_MANAGER.StartReporting}() and @{DetectionManager#DETECTION_MANAGER.StopReporting}() respectively. --- If an ad-hoc report is requested, use the method @{DetectionManager#DETECTION_MANAGER#ReportNow}(). +-- Reporting can be started and stopped using the methods @{Tasking.DetectionManager#DETECTION_MANAGER.StartReporting}() and @{Tasking.DetectionManager#DETECTION_MANAGER.StopReporting}() respectively. +-- If an ad-hoc report is requested, use the method @{Tasking.DetectionManager#DETECTION_MANAGER#ReportNow}(). -- -- The default reporting interval is every 60 seconds. The reporting messages are displayed 15 seconds. -- -- === -- --- 2) @{DetectionManager#DETECTION_REPORTING} class, extends @{DetectionManager#DETECTION_MANAGER} +-- 2) @{Tasking.DetectionManager#DETECTION_REPORTING} class, extends @{Tasking.DetectionManager#DETECTION_MANAGER} -- ========================================================================================= --- The @{DetectionManager#DETECTION_REPORTING} class implements detected units reporting. Reporting can be controlled using the reporting methods available in the @{DetectionManager#DETECTION_MANAGER} class. +-- The @{Tasking.DetectionManager#DETECTION_REPORTING} class implements detected units reporting. Reporting can be controlled using the reporting methods available in the @{Tasking.DetectionManager#DETECTION_MANAGER} class. -- -- 2.1) DETECTION_REPORTING constructor: -- ------------------------------- --- The @{DetectionManager#DETECTION_REPORTING.New}() method creates a new DETECTION_REPORTING instance. +-- The @{Tasking.DetectionManager#DETECTION_REPORTING.New}() method creates a new DETECTION_REPORTING instance. -- -- === -- @@ -64,7 +64,7 @@ do -- DETECTION MANAGER --- DETECTION_MANAGER class. -- @type DETECTION_MANAGER -- @field Set#SET_GROUP SetGroup The groups to which the FAC will report to. - -- @field Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects. + -- @field Functional.Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects. -- @extends Base#BASE DETECTION_MANAGER = { ClassName = "DETECTION_MANAGER", @@ -75,12 +75,12 @@ do -- DETECTION MANAGER --- FAC constructor. -- @param #DETECTION_MANAGER self -- @param Set#SET_GROUP SetGroup - -- @param Detection#DETECTION_BASE Detection + -- @param Functional.Detection#DETECTION_BASE Detection -- @return #DETECTION_MANAGER self function DETECTION_MANAGER:New( SetGroup, Detection ) -- Inherits from BASE - local self = BASE:Inherit( self, BASE:New() ) -- Detection#DETECTION_MANAGER + local self = BASE:Inherit( self, BASE:New() ) -- Functional.Detection#DETECTION_MANAGER self.SetGroup = SetGroup self.Detection = Detection @@ -125,7 +125,7 @@ do -- DETECTION MANAGER --- Reports the detected items to the @{Set#SET_GROUP}. -- @param #DETECTION_MANAGER self - -- @param Detection#DETECTION_BASE Detection + -- @param Functional.Detection#DETECTION_BASE Detection -- @return #DETECTION_MANAGER self function DETECTION_MANAGER:ReportDetected( Detection ) self:F2() @@ -148,7 +148,7 @@ do -- DETECTION MANAGER return self end - --- Report the detected @{Unit#UNIT}s detected within the @{Detection#DETECTION_BASE} object to the @{Set#SET_GROUP}s. + --- Report the detected @{Wrapper.Unit#UNIT}s detected within the @{Functional.Detection#DETECTION_BASE} object to the @{Set#SET_GROUP}s. -- @param #DETECTION_MANAGER self function DETECTION_MANAGER:_FacScheduler( SchedulerName ) self:F2( { SchedulerName } ) @@ -156,7 +156,7 @@ do -- DETECTION MANAGER return self:ProcessDetected( self.Detection ) -- self.SetGroup:ForEachGroup( --- --- @param Group#GROUP Group +-- --- @param Wrapper.Group#GROUP Group -- function( Group ) -- if Group:IsAlive() then -- return self:ProcessDetected( self.Detection ) @@ -175,7 +175,7 @@ do -- DETECTION_REPORTING --- DETECTION_REPORTING class. -- @type DETECTION_REPORTING -- @field Set#SET_GROUP SetGroup The groups to which the FAC will report to. - -- @field Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects. + -- @field Functional.Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects. -- @extends #DETECTION_MANAGER DETECTION_REPORTING = { ClassName = "DETECTION_REPORTING", @@ -185,7 +185,7 @@ do -- DETECTION_REPORTING --- DETECTION_REPORTING constructor. -- @param #DETECTION_REPORTING self -- @param Set#SET_GROUP SetGroup - -- @param Detection#DETECTION_AREAS Detection + -- @param Functional.Detection#DETECTION_AREAS Detection -- @return #DETECTION_REPORTING self function DETECTION_REPORTING:New( SetGroup, Detection ) @@ -198,7 +198,7 @@ do -- DETECTION_REPORTING --- Creates a string of the detected items in a @{Detection}. -- @param #DETECTION_MANAGER self - -- @param Set#SET_UNIT DetectedSet The detected Set created by the @{Detection#DETECTION_BASE} object. + -- @param Set#SET_UNIT DetectedSet The detected Set created by the @{Functional.Detection#DETECTION_BASE} object. -- @return #DETECTION_MANAGER self function DETECTION_REPORTING:GetDetectedItemsText( DetectedSet ) self:F2() @@ -207,7 +207,7 @@ do -- DETECTION_REPORTING local UnitTypes = {} for DetectedUnitID, DetectedUnitData in pairs( DetectedSet:GetSet() ) do - local DetectedUnit = DetectedUnitData -- Unit#UNIT + local DetectedUnit = DetectedUnitData -- Wrapper.Unit#UNIT if DetectedUnit:IsAlive() then local UnitType = DetectedUnit:GetTypeName() @@ -230,8 +230,8 @@ do -- DETECTION_REPORTING --- Reports the detected items to the @{Set#SET_GROUP}. -- @param #DETECTION_REPORTING self - -- @param Group#GROUP Group The @{Group} object to where the report needs to go. - -- @param Detection#DETECTION_AREAS Detection The detection created by the @{Detection#DETECTION_BASE} object. + -- @param Wrapper.Group#GROUP Group The @{Group} object to where the report needs to go. + -- @param Functional.Detection#DETECTION_AREAS Detection The detection created by the @{Functional.Detection#DETECTION_BASE} object. -- @return #boolean Return true if you want the reporting to continue... false will cancel the reporting loop. function DETECTION_REPORTING:ProcessDetected( Group, Detection ) self:F2( Group ) @@ -239,7 +239,7 @@ do -- DETECTION_REPORTING self:E( Group ) local DetectedMsg = {} for DetectedAreaID, DetectedAreaData in pairs( Detection:GetDetectedAreas() ) do - local DetectedArea = DetectedAreaData -- Detection#DETECTION_AREAS.DetectedArea + local DetectedArea = DetectedAreaData -- Functional.Detection#DETECTION_AREAS.DetectedArea DetectedMsg[#DetectedMsg+1] = " - Group #" .. DetectedAreaID .. ": " .. self:GetDetectedItemsText( DetectedArea.Set ) end local FACGroup = Detection:GetDetectionGroups() @@ -255,10 +255,10 @@ do -- DETECTION_DISPATCHER --- DETECTION_DISPATCHER class. -- @type DETECTION_DISPATCHER -- @field Set#SET_GROUP SetGroup The groups to which the FAC will report to. - -- @field Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects. - -- @field Mission#MISSION Mission - -- @field Group#GROUP CommandCenter - -- @extends DetectionManager#DETECTION_MANAGER + -- @field Functional.Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects. + -- @field Tasking.Mission#MISSION Mission + -- @field Wrapper.Group#GROUP CommandCenter + -- @extends Tasking.DetectionManager#DETECTION_MANAGER DETECTION_DISPATCHER = { ClassName = "DETECTION_DISPATCHER", Mission = nil, @@ -270,7 +270,7 @@ do -- DETECTION_DISPATCHER --- DETECTION_DISPATCHER constructor. -- @param #DETECTION_DISPATCHER self -- @param Set#SET_GROUP SetGroup - -- @param Detection#DETECTION_BASE Detection + -- @param Functional.Detection#DETECTION_BASE Detection -- @return #DETECTION_DISPATCHER self function DETECTION_DISPATCHER:New( Mission, CommandCenter, SetGroup, Detection ) @@ -288,7 +288,7 @@ do -- DETECTION_DISPATCHER --- Creates a SEAD task when there are targets for it. -- @param #DETECTION_DISPATCHER self - -- @param Detection#DETECTION_AREAS.DetectedArea DetectedArea + -- @param Functional.Detection#DETECTION_AREAS.DetectedArea DetectedArea -- @return Set#SET_UNIT TargetSetUnit: The target set of units. -- @return #nil If there are no targets to be set. function DETECTION_DISPATCHER:EvaluateSEAD( DetectedArea ) @@ -316,8 +316,8 @@ do -- DETECTION_DISPATCHER --- Creates a CAS task when there are targets for it. -- @param #DETECTION_DISPATCHER self - -- @param Detection#DETECTION_AREAS.DetectedArea DetectedArea - -- @return Task#TASK_BASE + -- @param Functional.Detection#DETECTION_AREAS.DetectedArea DetectedArea + -- @return Tasking.Task#TASK function DETECTION_DISPATCHER:EvaluateCAS( DetectedArea ) self:F( { DetectedArea.AreaID } ) @@ -344,8 +344,8 @@ do -- DETECTION_DISPATCHER --- Creates a BAI task when there are targets for it. -- @param #DETECTION_DISPATCHER self - -- @param Detection#DETECTION_AREAS.DetectedArea DetectedArea - -- @return Task#TASK_BASE + -- @param Functional.Detection#DETECTION_AREAS.DetectedArea DetectedArea + -- @return Tasking.Task#TASK function DETECTION_DISPATCHER:EvaluateBAI( DetectedArea, FriendlyCoalition ) self:F( { DetectedArea.AreaID } ) @@ -373,14 +373,15 @@ do -- DETECTION_DISPATCHER --- Evaluates the removal of the Task from the Mission. -- Can only occur when the DetectedArea is Changed AND the state of the Task is "Planned". -- @param #DETECTION_DISPATCHER self - -- @param Mission#MISSION Mission - -- @param Task#TASK_BASE Task - -- @param Detection#DETECTION_AREAS.DetectedArea DetectedArea - -- @return Task#TASK_BASE + -- @param Tasking.Mission#MISSION Mission + -- @param Tasking.Task#TASK Task + -- @param Functional.Detection#DETECTION_AREAS.DetectedArea DetectedArea + -- @return Tasking.Task#TASK function DETECTION_DISPATCHER:EvaluateRemoveTask( Mission, Task, DetectedArea ) if Task then if Task:IsStatePlanned() and DetectedArea.Changed == true then + self:E( "Removing Tasking: " .. Task:GetTaskName() ) Mission:RemoveTaskMenu( Task ) Task = Mission:RemoveTask( Task ) end @@ -392,7 +393,7 @@ do -- DETECTION_DISPATCHER --- Assigns tasks in relation to the detected items to the @{Set#SET_GROUP}. -- @param #DETECTION_DISPATCHER self - -- @param Detection#DETECTION_AREAS Detection The detection created by the @{Detection#DETECTION_AREAS} object. + -- @param Functional.Detection#DETECTION_AREAS Detection The detection created by the @{Functional.Detection#DETECTION_AREAS} object. -- @return #boolean Return true if you want the task assigning to continue... false will cancel the loop. function DETECTION_DISPATCHER:ProcessDetected( Detection ) self:F2() @@ -406,7 +407,7 @@ do -- DETECTION_DISPATCHER --- First we need to the detected targets. for DetectedAreaID, DetectedAreaData in ipairs( Detection:GetDetectedAreas() ) do - local DetectedArea = DetectedAreaData -- Detection#DETECTION_AREAS.DetectedArea + local DetectedArea = DetectedAreaData -- Functional.Detection#DETECTION_AREAS.DetectedArea local DetectedSet = DetectedArea.Set local DetectedZone = DetectedArea.Zone self:E( { "Targets in DetectedArea", DetectedArea.AreaID, DetectedSet:Count(), tostring( DetectedArea ) } ) @@ -420,11 +421,12 @@ do -- DETECTION_DISPATCHER if not SEADTask then local TargetSetUnit = self:EvaluateSEAD( DetectedArea ) -- Returns a SetUnit if there are targets to be SEADed... if TargetSetUnit then - SEADTask = Mission:AddTask( TASK_SEAD:New( Mission, self.SetGroup, "SEAD." .. AreaID, TargetSetUnit , DetectedZone ) ):StatePlanned() + SEADTask = Mission:AddTask( TASK_SEAD:New( Mission, self.SetGroup, "SEAD." .. AreaID, TargetSetUnit , DetectedZone ) ) end end if SEADTask and SEADTask:IsStatePlanned() then - SEADTask:SetPlannedMenu() + self:E( "Planned" ) + --SEADTask:SetPlannedMenu() TaskMsg[#TaskMsg+1] = " - " .. SEADTask:GetStateString() .. " SEAD " .. AreaID .. " - " .. SEADTask.TargetSetUnit:GetUnitTypesText() end @@ -434,11 +436,11 @@ do -- DETECTION_DISPATCHER if not CASTask then local TargetSetUnit = self:EvaluateCAS( DetectedArea ) -- Returns a SetUnit if there are targets to be SEADed... if TargetSetUnit then - CASTask = Mission:AddTask( TASK_A2G:New( Mission, self.SetGroup, "CAS." .. AreaID, "CAS", TargetSetUnit , DetectedZone, DetectedArea.NearestFAC ) ):StatePlanned() + CASTask = Mission:AddTask( TASK_A2G:New( Mission, self.SetGroup, "CAS." .. AreaID, "CAS", TargetSetUnit , DetectedZone, DetectedArea.NearestFAC ) ) end end if CASTask and CASTask:IsStatePlanned() then - CASTask:SetPlannedMenu() + --CASTask:SetPlannedMenu() TaskMsg[#TaskMsg+1] = " - " .. CASTask:GetStateString() .. " CAS " .. AreaID .. " - " .. CASTask.TargetSetUnit:GetUnitTypesText() end @@ -448,11 +450,11 @@ do -- DETECTION_DISPATCHER if not BAITask then local TargetSetUnit = self:EvaluateBAI( DetectedArea, self.CommandCenter:GetCoalition() ) -- Returns a SetUnit if there are targets to be SEADed... if TargetSetUnit then - BAITask = Mission:AddTask( TASK_A2G:New( Mission, self.SetGroup, "BAI." .. AreaID, "BAI", TargetSetUnit , DetectedZone, DetectedArea.NearestFAC ) ):StatePlanned() + BAITask = Mission:AddTask( TASK_A2G:New( Mission, self.SetGroup, "BAI." .. AreaID, "BAI", TargetSetUnit , DetectedZone, DetectedArea.NearestFAC ) ) end end if BAITask and BAITask:IsStatePlanned() then - BAITask:SetPlannedMenu() + --BAITask:SetPlannedMenu() TaskMsg[#TaskMsg+1] = " - " .. BAITask:GetStateString() .. " BAI " .. AreaID .. " - " .. BAITask.TargetSetUnit:GetUnitTypesText() end @@ -483,6 +485,9 @@ do -- DETECTION_DISPATCHER end + -- TODO set menus using the HQ coordinator + Mission:SetMenu() + if #AreaMsg > 0 then for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do if not TaskGroup:GetState( TaskGroup, "Assigned" ) then diff --git a/Moose Development/Moose/Mission.lua b/Moose Development/Moose/Tasking/Mission.lua similarity index 74% rename from Moose Development/Moose/Mission.lua rename to Moose Development/Moose/Tasking/Mission.lua index a5d59800e..c0ae13e21 100644 --- a/Moose Development/Moose/Mission.lua +++ b/Moose Development/Moose/Tasking/Mission.lua @@ -4,16 +4,15 @@ --- The MISSION class -- @type MISSION --- @extends Base#BASE -- @field #MISSION.Clients _Clients --- @field Menu#MENU_COALITION MissionMenu +-- @field Core.Menu#MENU_COALITION MissionMenu -- @field #string MissionBriefing +-- @extends Core.Fsm#FSM MISSION = { ClassName = "MISSION", Name = "", MissionStatus = "PENDING", _Clients = {}, - Tasks = {}, TaskMenus = {}, TaskCategoryMenus = {}, TaskTypeMenus = {}, @@ -31,36 +30,66 @@ MISSION = { _GoalTasks = {} } ---- @type MISSION.Clients --- @list - -function MISSION:Meta() - - local self = BASE:Inherit( self, BASE:New() ) - - return self -end - --- This is the main MISSION declaration method. Each Mission is like the master or a Mission orchestration between, Clients, Tasks, Stages etc. -- @param #MISSION self +-- @param Tasking.CommandCenter#COMMANDCENTER CommandCenter -- @param #string MissionName is the name of the mission. This name will be used to reference the status of each mission by the players. -- @param #string MissionPriority is a string indicating the "priority" of the Mission. f.e. "Primary", "Secondary" or "First", "Second". It is free format and up to the Mission designer to choose. There are no rules behind this field. -- @param #string MissionBriefing is a string indicating the mission briefing to be shown when a player joins a @{CLIENT}. --- @param DCSCoalitionObject#coalition MissionCoalition is a string indicating the coalition or party to which this mission belongs to. It is free format and can be chosen freely by the mission designer. Note that this field is not to be confused with the coalition concept of the ME. Examples of a Mission Coalition could be "NATO", "CCCP", "Intruders", "Terrorists"... +-- @param Dcs.DCSCoalitionWrapper.Object#coalition MissionCoalition is a string indicating the coalition or party to which this mission belongs to. It is free format and can be chosen freely by the mission designer. Note that this field is not to be confused with the coalition concept of the ME. Examples of a Mission Coalition could be "NATO", "CCCP", "Intruders", "Terrorists"... -- @return #MISSION self -function MISSION:New( MissionName, MissionPriority, MissionBriefing, MissionCoalition ) +function MISSION:New( CommandCenter, MissionName, MissionPriority, MissionBriefing, MissionCoalition ) - self = MISSION:Meta() + local self = BASE:Inherit( self, FSM:New() ) -- Core.Fsm#FSM + + self:SetStartState( "Idle" ) + + self:AddTransition( "Idle", "Start", "Ongoing" ) + self:AddTransition( "Ongoing", "Stop", "Idle" ) + self:AddTransition( "Ongoing", "Complete", "Completed" ) + self:AddTransition( "*", "Fail", "Failed" ) + self:T( { MissionName, MissionPriority, MissionBriefing, MissionCoalition } ) + self.CommandCenter = CommandCenter + CommandCenter:AddMission( self ) + self.Name = MissionName self.MissionPriority = MissionPriority self.MissionBriefing = MissionBriefing self.MissionCoalition = MissionCoalition + + self.Tasks = {} return self end +--- FSM function for a MISSION +-- @param #MISSION self +-- @param #string Event +-- @param #string From +-- @param #string To +function MISSION:onbeforeComplete( Event, From, To ) + + for TaskID, Task in pairs( self:GetTasks() ) do + local Task = Task -- Tasking.Task#TASK + if not Task:IsStateSuccess() and not Task:IsStateFailed() and not Task:IsStateAborted() and not Task:IsStateCancelled() then + return false -- Mission cannot be completed. Other Tasks are still active. + end + end + return true -- Allow Mission completion. +end + +--- FSM function for a MISSION +-- @param #MISSION self +-- @param #string Event +-- @param #string From +-- @param #string To +function MISSION:onenterCompleted( Event, From, To ) + + self:GetCommandCenter():MessageToCoalition( "Mission " .. self:GetName() .. " has been completed! Good job guys!" ) +end + --- Gets the mission name. -- @param #MISSION self -- @return #MISSION self @@ -68,6 +97,70 @@ function MISSION:GetName() return self.Name end +--- Add a Unit to join the Mission. +-- For each Task within the Mission, the Unit is joined with the Task. +-- If the Unit was not part of a Task in the Mission, false is returned. +-- If the Unit is part of a Task in the Mission, true is returned. +-- @param #MISSION self +-- @param Wrapper.Unit#UNIT PlayerUnit The CLIENT or UNIT of the Player joining the Mission. +-- @return #boolean true if Unit is part of a Task in the Mission. +function MISSION:JoinUnit( PlayerUnit ) + self:F( { PlayerUnit = PlayerUnit } ) + + local PlayerUnitAdded = false + + for TaskID, Task in pairs( self:GetTasks() ) do + local Task = Task -- Tasking.Task#TASK + if Task:JoinUnit( PlayerUnit ) then + PlayerUnitAdded = true + end + end + + return PlayerUnitAdded +end + +--- Aborts a PlayerUnit from the Mission. +-- For each Task within the Mission, the PlayerUnit is removed from Task where it is assigned. +-- If the Unit was not part of a Task in the Mission, false is returned. +-- If the Unit is part of a Task in the Mission, true is returned. +-- @param #MISSION self +-- @param Wrapper.Unit#UNIT PlayerUnit The CLIENT or UNIT of the Player joining the Mission. +-- @return #boolean true if Unit is part of a Task in the Mission. +function MISSION:AbortUnit( PlayerUnit ) + self:F( { PlayerUnit = PlayerUnit } ) + + local PlayerUnitRemoved = false + + for TaskID, Task in pairs( self:GetTasks() ) do + if Task:AbortUnit( PlayerUnit ) then + PlayerUnitRemoved = true + end + end + + return PlayerUnitRemoved +end + +--- Handles a crash of a PlayerUnit from the Mission. +-- For each Task within the Mission, the PlayerUnit is removed from Task where it is assigned. +-- If the Unit was not part of a Task in the Mission, false is returned. +-- If the Unit is part of a Task in the Mission, true is returned. +-- @param #MISSION self +-- @param Wrapper.Unit#UNIT PlayerUnit The CLIENT or UNIT of the Player crashing. +-- @return #boolean true if Unit is part of a Task in the Mission. +function MISSION:CrashUnit( PlayerUnit ) + self:F( { PlayerUnit = PlayerUnit } ) + + local PlayerUnitRemoved = false + + for TaskID, Task in pairs( self:GetTasks() ) do + if Task:CrashUnit( PlayerUnit ) then + PlayerUnitRemoved = true + end + end + + return PlayerUnitRemoved +end + --- Add a scoring to the mission. -- @param #MISSION self -- @return #MISSION self @@ -83,28 +176,57 @@ function MISSION:GetScoring() return self.Scoring end +--- Get the groups for which TASKS are given in the mission +-- @param #MISSION self +-- @return Core.Set#SET_GROUP +function MISSION:GetGroups() + + local SetGroup = SET_GROUP:New() + + for TaskID, Task in pairs( self:GetTasks() ) do + local Task = Task -- Tasking.Task#TASK + local GroupSet = Task:GetGroups() + GroupSet:ForEachGroup( + function( TaskGroup ) + SetGroup:Add( TaskGroup, TaskGroup ) + end + ) + end + + return SetGroup + +end + --- Sets the Planned Task menu. -- @param #MISSION self -function MISSION:SetPlannedMenu() +-- @param Core.Menu#MENU_COALITION CommandCenterMenu +function MISSION:SetMenu() + self:F() - for _, Task in pairs( self.Tasks ) do - local Task = Task -- Task#TASK_BASE - Task:RemoveMenu() - Task:SetPlannedMenu() + for _, Task in pairs( self:GetTasks() ) do + local Task = Task -- Tasking.Task#TASK + Task:SetMenu() end - +end + + +--- Gets the COMMANDCENTER. +-- @param #MISSION self +-- @return Tasking.CommandCenter#COMMANDCENTER +function MISSION:GetCommandCenter() + return self.CommandCenter end --- Sets the Assigned Task menu. -- @param #MISSION self --- @param Task#TASK_BASE Task +-- @param Tasking.Task#TASK Task -- @param #string MenuText The menu text. -- @return #MISSION self function MISSION:SetAssignedMenu( Task ) for _, Task in pairs( self.Tasks ) do - local Task = Task -- Task#TASK_BASE + local Task = Task -- Tasking.Task#TASK Task:RemoveMenu() Task:SetAssignedMenu() end @@ -113,7 +235,7 @@ end --- Removes a Task menu. -- @param #MISSION self --- @param Task#TASK_BASE Task +-- @param Tasking.Task#TASK Task -- @return #MISSION self function MISSION:RemoveTaskMenu( Task ) @@ -123,11 +245,19 @@ end --- Gets the mission menu for the coalition. -- @param #MISSION self --- @param Group#GROUP TaskGroup --- @return Menu#MENU_COALITION self +-- @param Wrapper.Group#GROUP TaskGroup +-- @return Core.Menu#MENU_COALITION self function MISSION:GetMissionMenu( TaskGroup ) + + local CommandCenter = self:GetCommandCenter() + local CommandCenterMenu = CommandCenter.CommandCenterMenu + + local MissionName = self:GetName() + local TaskGroupName = TaskGroup:GetName() - return self.MenuMission[TaskGroupName] + local MissionMenu = MENU_GROUP:New( TaskGroup, MissionName, CommandCenterMenu ) + + return MissionMenu end @@ -140,9 +270,8 @@ function MISSION:ClearMissionMenu() end --- Get the TASK identified by the TaskNumber from the Mission. This function is useful in GoalFunctions. --- @param #string TaskIndex is the Index of the @{Task} within the @{Mission}. --- @param #number TaskID is the ID of the @{Task} within the @{Mission}. --- @return Task#TASK_BASE The Task +-- @param #string TaskName The Name of the @{Task} within the @{Mission}. +-- @return Tasking.Task#TASK The Task -- @return #nil Returns nil if no task was found. function MISSION:GetTask( TaskName ) self:F( { TaskName } ) @@ -155,15 +284,18 @@ end -- Note that there can be multiple @{Task}s registered to be completed. -- Each Task can be set a certain Goals. The Mission will not be completed until all Goals are reached. -- @param #MISSION self --- @param Task#TASK_BASE Task is the @{Task} object. --- @return Task#TASK_BASE The task added. +-- @param Tasking.Task#TASK Task is the @{Task} object. +-- @return Tasking.Task#TASK The task added. function MISSION:AddTask( Task ) local TaskName = Task:GetTaskName() self:F( TaskName ) + self.Tasks[TaskName] = self.Tasks[TaskName] or { n = 0 } - + self.Tasks[TaskName] = Task + + self:GetCommandCenter():SetMenu() return Task end @@ -172,27 +304,30 @@ end -- Note that there can be multiple @{Task}s registered to be completed. -- Each Task can be set a certain Goals. The Mission will not be completed until all Goals are reached. -- @param #MISSION self --- @param Task#TASK_BASE Task is the @{Task} object. +-- @param Tasking.Task#TASK Task is the @{Task} object. -- @return #nil The cleaned Task reference. function MISSION:RemoveTask( Task ) local TaskName = Task:GetTaskName() + self:F( TaskName ) self.Tasks[TaskName] = self.Tasks[TaskName] or { n = 0 } - Task:CleanUp() -- Cleans all events and sets task to nil to get Garbage Collected - -- Ensure everything gets garbarge collected. self.Tasks[TaskName] = nil Task = nil + collectgarbage() + + self:GetCommandCenter():SetMenu() + return nil end --- Return the next @{Task} ID to be completed within the @{Mission}. -- @param #MISSION self --- @param Task#TASK_BASE Task is the @{Task} object. --- @return Task#TASK_BASE The task added. +-- @param Tasking.Task#TASK Task is the @{Task} object. +-- @return Tasking.Task#TASK The task added. function MISSION:GetNextTaskID( Task ) local TaskName = Task:GetTaskName() @@ -274,25 +409,96 @@ function MISSION:StatusToClients() end end ---- Handles the reporting. After certain time intervals, a MISSION report MESSAGE will be shown to All Players. -function MISSION:ReportTrigger() - self:F() +function MISSION:HasGroup( TaskGroup ) + local Has = false + + for TaskID, Task in pairs( self:GetTasks() ) do + local Task = Task -- Tasking.Task#TASK + if Task:HasGroup( TaskGroup ) then + Has = true + break + end + end + + return Has +end - if self.MissionReportShow == true then - self.MissionReportShow = false - return true - else - if self.MissionReportFlash == true then - if timer.getTime() >= self.MissionReportTrigger then - self.MissionReportTrigger = timer.getTime() + self.MissionTimeInterval - return true - else - return false - end - else - return false - end - end +--- Create a summary report of the Mission (one line). +-- @param #MISSION self +-- @return #string +function MISSION:ReportSummary() + + local Report = REPORT:New() + + -- List the name of the mission. + local Name = self:GetName() + + -- Determine the status of the mission. + local Status = self:GetState() + + -- Determine how many tasks are remaining. + local TasksRemaining = 0 + for TaskID, Task in pairs( self:GetTasks() ) do + local Task = Task -- Tasking.Task#TASK + if Task:IsStateSuccess() or Task:IsStateFailed() then + else + TasksRemaining = TasksRemaining + 1 + end + end + + Report:Add( "Mission " .. Name .. " - " .. Status .. " - " .. TasksRemaining .. " tasks remaining." ) + + return Report:Text() +end + +--- Create a overview report of the Mission (multiple lines). +-- @param #MISSION self +-- @return #string +function MISSION:ReportOverview() + + local Report = REPORT:New() + + -- List the name of the mission. + local Name = self:GetName() + + -- Determine the status of the mission. + local Status = self:GetState() + + Report:Add( "Mission " .. Name .. " - State '" .. Status .. "'" ) + + -- Determine how many tasks are remaining. + local TasksRemaining = 0 + for TaskID, Task in pairs( self:GetTasks() ) do + local Task = Task -- Tasking.Task#TASK + Report:Add( "- " .. Task:ReportSummary() ) + end + + return Report:Text() +end + +--- Create a detailed report of the Mission, listing all the details of the Task. +-- @param #MISSION self +-- @return #string +function MISSION:ReportDetails() + + local Report = REPORT:New() + + -- List the name of the mission. + local Name = self:GetName() + + -- Determine the status of the mission. + local Status = self:GetState() + + Report:Add( "Mission " .. Name .. " - State '" .. Status .. "'" ) + + -- Determine how many tasks are remaining. + local TasksRemaining = 0 + for TaskID, Task in pairs( self:GetTasks() ) do + local Task = Task -- Tasking.Task#TASK + Report:Add( Task:ReportDetails() ) + end + + return Report:Text() end --- Report the status of all MISSIONs to all active Clients. @@ -405,7 +611,7 @@ end function MISSION:GetTasks() self:F() - return self._Tasks + return self.Tasks end @@ -456,7 +662,7 @@ function MISSIONSCHEDULER.Scheduler() for ClientID, ClientData in pairs( Mission._Clients ) do - local Client = ClientData -- Client#CLIENT + local Client = ClientData -- Wrapper.Client#CLIENT if Client:IsAlive() then diff --git a/Moose Development/Moose/Tasking/Task.lua b/Moose Development/Moose/Tasking/Task.lua new file mode 100644 index 000000000..b5b4fa110 --- /dev/null +++ b/Moose Development/Moose/Tasking/Task.lua @@ -0,0 +1,1003 @@ +--- This module contains the TASK class. +-- +-- 1) @{#TASK} class, extends @{Core.Base#BASE} +-- ============================================ +-- 1.1) The @{#TASK} class implements the methods for task orchestration within MOOSE. +-- ---------------------------------------------------------------------------------------- +-- The class provides a couple of methods to: +-- +-- * @{#TASK.AssignToGroup}():Assign a task to a group (of players). +-- * @{#TASK.AddProcess}():Add a @{Process} to a task. +-- * @{#TASK.RemoveProcesses}():Remove a running @{Process} from a running task. +-- * @{#TASK.SetStateMachine}():Set a @{Fsm} to a task. +-- * @{#TASK.RemoveStateMachine}():Remove @{Fsm} from a task. +-- * @{#TASK.HasStateMachine}():Enquire if the task has a @{Fsm} +-- * @{#TASK.AssignToUnit}(): Assign a task to a unit. (Needs to be implemented in the derived classes from @{#TASK}. +-- * @{#TASK.UnAssignFromUnit}(): Unassign the task from a unit. +-- +-- 1.2) Set and enquire task status (beyond the task state machine processing). +-- ---------------------------------------------------------------------------- +-- A task needs to implement as a minimum the following task states: +-- +-- * **Success**: Expresses the successful execution and finalization of the task. +-- * **Failed**: Expresses the failure of a task. +-- * **Planned**: Expresses that the task is created, but not yet in execution and is not assigned yet. +-- * **Assigned**: Expresses that the task is assigned to a Group of players, and that the task is in execution mode. +-- +-- A task may also implement the following task states: +-- +-- * **Rejected**: Expresses that the task is rejected by a player, who was requested to accept the task. +-- * **Cancelled**: Expresses that the task is cancelled by HQ or through a logical situation where a cancellation of the task is required. +-- +-- A task can implement more statusses than the ones outlined above. Please consult the documentation of the specific tasks to understand the different status modelled. +-- +-- The status of tasks can be set by the methods **State** followed by the task status. An example is `StateAssigned()`. +-- The status of tasks can be enquired by the methods **IsState** followed by the task status name. An example is `if IsStateAssigned() then`. +-- +-- 1.3) Add scoring when reaching a certain task status: +-- ----------------------------------------------------- +-- Upon reaching a certain task status in a task, additional scoring can be given. If the Mission has a scoring system attached, the scores will be added to the mission scoring. +-- Use the method @{#TASK.AddScore}() to add scores when a status is reached. +-- +-- 1.4) Task briefing: +-- ------------------- +-- A task briefing can be given that is shown to the player when he is assigned to the task. +-- +-- === +-- +-- ### Authors: FlightControl - Design and Programming +-- +-- @module Task + +--- The TASK class +-- @type TASK +-- @field Core.Scheduler#SCHEDULER TaskScheduler +-- @field Tasking.Mission#MISSION Mission +-- @field Core.Set#SET_GROUP SetGroup The Set of Groups assigned to the Task +-- @field Core.Fsm#FSM_PROCESS FsmTemplate +-- @field Tasking.Mission#MISSION Mission +-- @field Tasking.CommandCenter#COMMANDCENTER CommandCenter +-- @extends Core.Fsm#FSM_TASK +TASK = { + ClassName = "TASK", + TaskScheduler = nil, + ProcessClasses = {}, -- The container of the Process classes that will be used to create and assign new processes for the task to ProcessUnits. + Processes = {}, -- The container of actual process objects instantiated and assigned to ProcessUnits. + Players = nil, + Scores = {}, + Menu = {}, + SetGroup = nil, + FsmTemplate = nil, + Mission = nil, + CommandCenter = nil, +} + +--- FSM PlayerAborted event handler prototype for TASK. +-- @function [parent=#TASK] OnAfterPlayerAborted +-- @param #TASK self +-- @param Wrapper.Unit#UNIT PlayerUnit The Unit of the Player when he went back to spectators or left the mission. +-- @param #string PlayerName The name of the Player. + +--- FSM PlayerCrashed event handler prototype for TASK. +-- @function [parent=#TASK] OnAfterPlayerCrashed +-- @param #TASK self +-- @param Wrapper.Unit#UNIT PlayerUnit The Unit of the Player when he crashed in the mission. +-- @param #string PlayerName The name of the Player. + +--- FSM PlayerDead event handler prototype for TASK. +-- @function [parent=#TASK] OnAfterPlayerDead +-- @param #TASK self +-- @param Wrapper.Unit#UNIT PlayerUnit The Unit of the Player when he died in the mission. +-- @param #string PlayerName The name of the Player. + +--- FSM Fail synchronous event function for TASK. +-- Use this event to Fail the Task. +-- @function [parent=#TASK] Fail +-- @param #TASK self + +--- FSM Fail asynchronous event function for TASK. +-- Use this event to Fail the Task. +-- @function [parent=#TASK] __Fail +-- @param #TASK self + +--- FSM Abort synchronous event function for TASK. +-- Use this event to Abort the Task. +-- @function [parent=#TASK] Abort +-- @param #TASK self + +--- FSM Abort asynchronous event function for TASK. +-- Use this event to Abort the Task. +-- @function [parent=#TASK] __Abort +-- @param #TASK self + +--- FSM Success synchronous event function for TASK. +-- Use this event to make the Task a Success. +-- @function [parent=#TASK] Success +-- @param #TASK self + +--- FSM Success asynchronous event function for TASK. +-- Use this event to make the Task a Success. +-- @function [parent=#TASK] __Success +-- @param #TASK self + +--- FSM Cancel synchronous event function for TASK. +-- Use this event to Cancel the Task. +-- @function [parent=#TASK] Cancel +-- @param #TASK self + +--- FSM Cancel asynchronous event function for TASK. +-- Use this event to Cancel the Task. +-- @function [parent=#TASK] __Cancel +-- @param #TASK self + +--- FSM Replan synchronous event function for TASK. +-- Use this event to Replan the Task. +-- @function [parent=#TASK] Replan +-- @param #TASK self + +--- FSM Replan asynchronous event function for TASK. +-- Use this event to Replan the Task. +-- @function [parent=#TASK] __Replan +-- @param #TASK self + + +--- Instantiates a new TASK. Should never be used. Interface Class. +-- @param #TASK self +-- @param Tasking.Mission#MISSION Mission The mission wherein the Task is registered. +-- @param Core.Set#SET_GROUP SetGroupAssign The set of groups for which the Task can be assigned. +-- @param #string TaskName The name of the Task +-- @param #string TaskType The type of the Task +-- @return #TASK self +function TASK:New( Mission, SetGroupAssign, TaskName, TaskType ) + + local self = BASE:Inherit( self, FSM_TASK:New() ) -- Core.Fsm#FSM_TASK + + self:SetStartState( "Planned" ) + self:AddTransition( "Planned", "Assign", "Assigned" ) + self:AddTransition( "Assigned", "AssignUnit", "Assigned" ) + self:AddTransition( "Assigned", "Success", "Success" ) + self:AddTransition( "Assigned", "Fail", "Failed" ) + self:AddTransition( "Assigned", "Abort", "Aborted" ) + self:AddTransition( "Assigned", "Cancel", "Cancelled" ) + self:AddTransition( "*", "PlayerCrashed", "*" ) + self:AddTransition( "*", "PlayerAborted", "*" ) + self:AddTransition( "*", "PlayerDead", "*" ) + self:AddTransition( { "Failed", "Aborted", "Cancelled" }, "Replan", "Planned" ) + + self:E( "New TASK " .. TaskName ) + + self.Processes = {} + self.Fsm = {} + + self.Mission = Mission + self.CommandCenter = Mission:GetCommandCenter() + + self.SetGroup = SetGroupAssign + + self:SetType( TaskType ) + self:SetName( TaskName ) + self:SetID( Mission:GetNextTaskID( self ) ) -- The Mission orchestrates the task sequences .. + + self.TaskBriefing = "You are invited for the task: " .. self.TaskName .. "." + + self.FsmTemplate = self.FsmTemplate or FSM_PROCESS:New() + + -- Handle the birth of new planes within the assigned set. + + + -- Handle when a player crashes ... + -- The Task is UnAssigned from the Unit. + -- When there is no Unit left running the Task, and all of the Players crashed, the Task goes into Failed ... +-- self:EventOnCrash( +-- --- @param #TASK self +-- -- @param Core.Event#EVENTDATA EventData +-- function( self, EventData ) +-- self:E( "In LeaveUnit" ) +-- self:E( { "State", self:GetState() } ) +-- if self:IsStateAssigned() then +-- local TaskUnit = EventData.IniUnit +-- local TaskGroup = EventData.IniUnit:GetGroup() +-- self:E( self.SetGroup:IsIncludeObject( TaskGroup ) ) +-- if self.SetGroup:IsIncludeObject( TaskGroup ) then +-- self:UnAssignFromUnit( TaskUnit ) +-- end +-- self:MessageToGroups( TaskUnit:GetPlayerName() .. " crashed!, and has aborted Task " .. self:GetName() ) +-- end +-- end +-- ) +-- + + Mission:AddTask( self ) + + return self +end + +--- Get the Task FSM Process Template +-- @param #TASK self +-- @return Core.Fsm#FSM_PROCESS +function TASK:GetUnitProcess() + + return self.FsmTemplate +end + +--- Sets the Task FSM Process Template +-- @param #TASK self +-- @param Core.Fsm#FSM_PROCESS +function TASK:SetUnitProcess( FsmTemplate ) + + self.FsmTemplate = FsmTemplate +end + +--- Add a PlayerUnit to join the Task. +-- For each Group within the Task, the Unit is check if it can join the Task. +-- If the Unit was not part of the Task, false is returned. +-- If the Unit is part of the Task, true is returned. +-- @param #TASK self +-- @param Wrapper.Unit#UNIT PlayerUnit The CLIENT or UNIT of the Player joining the Mission. +-- @return #boolean true if Unit is part of the Task. +function TASK:JoinUnit( PlayerUnit ) + self:F( { PlayerUnit = PlayerUnit } ) + + local PlayerUnitAdded = false + + local PlayerGroups = self:GetGroups() + local PlayerGroup = PlayerUnit:GetGroup() + + -- Is the PlayerGroup part of the PlayerGroups? + if PlayerGroups:IsIncludeObject( PlayerGroup ) then + + -- Check if the PlayerGroup is already assigned to the Task. If yes, the PlayerGroup is added to the Task. + -- If the PlayerGroup is not assigned to the Task, the menu needs to be set. In that case, the PlayerUnit will become the GroupPlayer leader. + if self:IsStatePlanned() or self:IsStateReplanned() then + self:SetMenuForGroup( PlayerGroup ) + self:MessageToGroups( PlayerUnit:GetPlayerName() .. " is planning to join Task " .. self:GetName() ) + end + if self:IsStateAssigned() then + local IsAssignedToGroup = self:IsAssignedToGroup( PlayerGroup ) + self:E( { IsAssignedToGroup = IsAssignedToGroup } ) + if IsAssignedToGroup then + self:AssignToUnit( PlayerUnit ) + self:MessageToGroups( PlayerUnit:GetPlayerName() .. " joined Task " .. self:GetName() ) + end + end + end + + return PlayerUnitAdded +end + +--- Abort a PlayerUnit from a Task. +-- If the Unit was not part of the Task, false is returned. +-- If the Unit is part of the Task, true is returned. +-- @param #TASK self +-- @param Wrapper.Unit#UNIT PlayerUnit The CLIENT or UNIT of the Player aborting the Task. +-- @return #boolean true if Unit is part of the Task. +function TASK:AbortUnit( PlayerUnit ) + self:F( { PlayerUnit = PlayerUnit } ) + + local PlayerUnitAborted = false + + local PlayerGroups = self:GetGroups() + local PlayerGroup = PlayerUnit:GetGroup() + + -- Is the PlayerGroup part of the PlayerGroups? + if PlayerGroups:IsIncludeObject( PlayerGroup ) then + + -- Check if the PlayerGroup is already assigned to the Task. If yes, the PlayerGroup is aborted from the Task. + -- If the PlayerUnit was the last unit of the PlayerGroup, the menu needs to be removed from the Group. + if self:IsStateAssigned() then + local IsAssignedToGroup = self:IsAssignedToGroup( PlayerGroup ) + self:E( { IsAssignedToGroup = IsAssignedToGroup } ) + if IsAssignedToGroup then + self:UnAssignFromUnit( PlayerUnit ) + self:MessageToGroups( PlayerUnit:GetPlayerName() .. " aborted Task " .. self:GetName() ) + self:E( { TaskGroup = PlayerGroup:GetName(), GetUnits = PlayerGroup:GetUnits() } ) + if #PlayerGroup:GetUnits() == 1 then + PlayerGroup:SetState( PlayerGroup, "Assigned", nil ) + self:RemoveMenuForGroup( PlayerGroup ) + end + self:PlayerAborted( PlayerUnit ) + end + end + end + + return PlayerUnitAborted +end + +--- A PlayerUnit crashed in a Task. Abort the Player. +-- If the Unit was not part of the Task, false is returned. +-- If the Unit is part of the Task, true is returned. +-- @param #TASK self +-- @param Wrapper.Unit#UNIT PlayerUnit The CLIENT or UNIT of the Player aborting the Task. +-- @return #boolean true if Unit is part of the Task. +function TASK:CrashUnit( PlayerUnit ) + self:F( { PlayerUnit = PlayerUnit } ) + + local PlayerUnitCrashed = false + + local PlayerGroups = self:GetGroups() + local PlayerGroup = PlayerUnit:GetGroup() + + -- Is the PlayerGroup part of the PlayerGroups? + if PlayerGroups:IsIncludeObject( PlayerGroup ) then + + -- Check if the PlayerGroup is already assigned to the Task. If yes, the PlayerGroup is aborted from the Task. + -- If the PlayerUnit was the last unit of the PlayerGroup, the menu needs to be removed from the Group. + if self:IsStateAssigned() then + local IsAssignedToGroup = self:IsAssignedToGroup( PlayerGroup ) + self:E( { IsAssignedToGroup = IsAssignedToGroup } ) + if IsAssignedToGroup then + self:UnAssignFromUnit( PlayerUnit ) + self:MessageToGroups( PlayerUnit:GetPlayerName() .. " crashed in Task " .. self:GetName() ) + self:E( { TaskGroup = PlayerGroup:GetName(), GetUnits = PlayerGroup:GetUnits() } ) + if #PlayerGroup:GetUnits() == 1 then + PlayerGroup:SetState( PlayerGroup, "Assigned", nil ) + self:RemoveMenuForGroup( PlayerGroup ) + end + self:PlayerCrashed( PlayerUnit ) + end + end + end + + return PlayerUnitCrashed +end + + + +--- Gets the Mission to where the TASK belongs. +-- @param #TASK self +-- @return Tasking.Mission#MISSION +function TASK:GetMission() + + return self.Mission +end + + +--- Gets the SET_GROUP assigned to the TASK. +-- @param #TASK self +-- @return Core.Set#SET_GROUP +function TASK:GetGroups() + return self.SetGroup +end + + + +--- Assign the @{Task}to a @{Group}. +-- @param #TASK self +-- @param Wrapper.Group#GROUP TaskGroup +-- @return #TASK +function TASK:AssignToGroup( TaskGroup ) + self:F2( TaskGroup:GetName() ) + + local TaskGroupName = TaskGroup:GetName() + + TaskGroup:SetState( TaskGroup, "Assigned", self ) + + self:RemoveMenuForGroup( TaskGroup ) + self:SetAssignedMenuForGroup( TaskGroup ) + + local TaskUnits = TaskGroup:GetUnits() + for UnitID, UnitData in pairs( TaskUnits ) do + local TaskUnit = UnitData -- Wrapper.Unit#UNIT + local PlayerName = TaskUnit:GetPlayerName() + self:E(PlayerName) + if PlayerName ~= nil or PlayerName ~= "" then + self:AssignToUnit( TaskUnit ) + end + end + + return self +end + +--- +-- @param #TASK self +-- @param Wrapper.Group#GROUP FindGroup +-- @return #boolean +function TASK:HasGroup( FindGroup ) + + self:GetGroups():FilterOnce() -- Ensure that the filter is updated. + return self:GetGroups():IsIncludeObject( FindGroup ) + +end + +--- Assign the @{Task} to an alive @{Unit}. +-- @param #TASK self +-- @param Wrapper.Unit#UNIT TaskUnit +-- @return #TASK self +function TASK:AssignToUnit( TaskUnit ) + self:F( TaskUnit:GetName() ) + + local FsmTemplate = self:GetUnitProcess() + + -- Assign a new FsmUnit to TaskUnit. + local FsmUnit = self:SetStateMachine( TaskUnit, FsmTemplate:Copy( TaskUnit, self ) ) -- Core.Fsm#FSM_PROCESS + self:E({"Address FsmUnit", tostring( FsmUnit ) } ) + + FsmUnit:SetStartState( "Planned" ) + FsmUnit:Accept() -- Each Task needs to start with an Accept event to start the flow. + + return self +end + +--- UnAssign the @{Task} from an alive @{Unit}. +-- @param #TASK self +-- @param Wrapper.Unit#UNIT TaskUnit +-- @return #TASK self +function TASK:UnAssignFromUnit( TaskUnit ) + self:F( TaskUnit ) + + self:RemoveStateMachine( TaskUnit ) + + return self +end + +--- Send a message of the @{Task} to the assigned @{Group}s. +-- @param #TASK self +function TASK:MessageToGroups( Message ) + self:F( { Message = Message } ) + + local Mission = self:GetMission() + local CC = Mission:GetCommandCenter() + + for TaskGroupName, TaskGroup in pairs( self.SetGroup:GetSet() ) do + CC:MessageToGroup( Message, TaskGroup ) + end +end + + +--- Send the briefng message of the @{Task} to the assigned @{Group}s. +-- @param #TASK self +function TASK:SendBriefingToAssignedGroups() + self:F2() + + for TaskGroupName, TaskGroup in pairs( self.SetGroup:GetSet() ) do + + if self:IsAssignedToGroup( TaskGroup ) then + TaskGroup:Message( self.TaskBriefing, 60 ) + end + end +end + + +--- Assign the @{Task} from the @{Group}s. +-- @param #TASK self +function TASK:UnAssignFromGroups() + self:F2() + + for TaskGroupName, TaskGroup in pairs( self.SetGroup:GetSet() ) do + + TaskGroup:SetState( TaskGroup, "Assigned", nil ) + + self:RemoveMenuForGroup( TaskGroup ) + + local TaskUnits = TaskGroup:GetUnits() + for UnitID, UnitData in pairs( TaskUnits ) do + local TaskUnit = UnitData -- Wrapper.Unit#UNIT + local PlayerName = TaskUnit:GetPlayerName() + if PlayerName ~= nil or PlayerName ~= "" then + self:UnAssignFromUnit( TaskUnit ) + end + end + end +end + +--- Returns if the @{Task} is assigned to the Group. +-- @param #TASK self +-- @param Wrapper.Group#GROUP TaskGroup +-- @return #boolean +function TASK:IsAssignedToGroup( TaskGroup ) + + local TaskGroupName = TaskGroup:GetName() + + if self:IsStateAssigned() then + if TaskGroup:GetState( TaskGroup, "Assigned" ) == self then + return true + end + end + + return false +end + +--- Returns if the @{Task} has still alive and assigned Units. +-- @param #TASK self +-- @return #boolean +function TASK:HasAliveUnits() + self:F() + + for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do + if self:IsStateAssigned() then + if self:IsAssignedToGroup( TaskGroup ) then + for TaskUnitID, TaskUnit in pairs( TaskGroup:GetUnits() ) do + if TaskUnit:IsAlive() then + self:T( { HasAliveUnits = true } ) + return true + end + end + end + end + end + + self:T( { HasAliveUnits = false } ) + return false +end + +--- Set the menu options of the @{Task} to all the groups in the SetGroup. +-- @param #TASK self +function TASK:SetMenu() + self:F() + + self.SetGroup:Flush() + for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do + self:RemoveMenuForGroup( TaskGroup ) + if self:IsStatePlanned() or self:IsStateReplanned() then + self:SetMenuForGroup( TaskGroup ) + end + end +end + + +--- Remove the menu options of the @{Task} to all the groups in the SetGroup. +-- @param #TASK self +-- @return #TASK self +function TASK:RemoveMenu() + + for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do + self:RemoveMenuForGroup( TaskGroup ) + end +end + + +--- Set the Menu for a Group +-- @param #TASK self +function TASK:SetMenuForGroup( TaskGroup ) + + if not self:IsAssignedToGroup( TaskGroup ) then + self:SetPlannedMenuForGroup( TaskGroup, self:GetTaskName() ) + else + self:SetAssignedMenuForGroup( TaskGroup ) + end +end + + +--- Set the planned menu option of the @{Task}. +-- @param #TASK self +-- @param Wrapper.Group#GROUP TaskGroup +-- @param #string MenuText The menu text. +-- @return #TASK self +function TASK:SetPlannedMenuForGroup( TaskGroup, MenuText ) + self:E( TaskGroup:GetName() ) + + local Mission = self:GetMission() + local MissionMenu = Mission:GetMissionMenu( TaskGroup ) + + local TaskType = self:GetType() + local TaskTypeMenu = MENU_GROUP:New( TaskGroup, TaskType, MissionMenu ) + local TaskMenu = MENU_GROUP_COMMAND:New( TaskGroup, MenuText, TaskTypeMenu, self.MenuAssignToGroup, { self = self, TaskGroup = TaskGroup } ) + + return self +end + +--- Set the assigned menu options of the @{Task}. +-- @param #TASK self +-- @param Wrapper.Group#GROUP TaskGroup +-- @return #TASK self +function TASK:SetAssignedMenuForGroup( TaskGroup ) + self:E( TaskGroup:GetName() ) + + local Mission = self:GetMission() + local MissionMenu = Mission:GetMissionMenu( TaskGroup ) + + self:E( { MissionMenu = MissionMenu } ) + + local TaskTypeMenu = MENU_GROUP_COMMAND:New( TaskGroup, "Task Status", MissionMenu, self.MenuTaskStatus, { self = self, TaskGroup = TaskGroup } ) + local TaskMenu = MENU_GROUP_COMMAND:New( TaskGroup, "Abort Task", MissionMenu, self.MenuTaskAbort, { self = self, TaskGroup = TaskGroup } ) + + return self +end + +--- Remove the menu option of the @{Task} for a @{Group}. +-- @param #TASK self +-- @param Wrapper.Group#GROUP TaskGroup +-- @return #TASK self +function TASK:RemoveMenuForGroup( TaskGroup ) + + local Mission = self:GetMission() + local MissionName = Mission:GetName() + + local MissionMenu = Mission:GetMissionMenu( TaskGroup ) + MissionMenu:Remove() +end + +function TASK.MenuAssignToGroup( MenuParam ) + + local self = MenuParam.self + local TaskGroup = MenuParam.TaskGroup + + self:E( "Assigned menu selected") + + self:AssignToGroup( TaskGroup ) +end + +function TASK.MenuTaskStatus( MenuParam ) + + local self = MenuParam.self + local TaskGroup = MenuParam.TaskGroup + + --self:AssignToGroup( TaskGroup ) +end + +function TASK.MenuTaskAbort( MenuParam ) + + local self = MenuParam.self + local TaskGroup = MenuParam.TaskGroup + + --self:AssignToGroup( TaskGroup ) +end + + + +--- Returns the @{Task} name. +-- @param #TASK self +-- @return #string TaskName +function TASK:GetTaskName() + return self.TaskName +end + + + + +--- Get the default or currently assigned @{Process} template with key ProcessName. +-- @param #TASK self +-- @param #string ProcessName +-- @return Core.Fsm#FSM_PROCESS +function TASK:GetProcessTemplate( ProcessName ) + + local ProcessTemplate = self.ProcessClasses[ProcessName] + + return ProcessTemplate +end + + + +-- TODO: Obscolete? +--- Fail processes from @{Task} with key @{Unit} +-- @param #TASK self +-- @param #string TaskUnitName +-- @return #TASK self +function TASK:FailProcesses( TaskUnitName ) + + for ProcessID, ProcessData in pairs( self.Processes[TaskUnitName] ) do + local Process = ProcessData + Process.Fsm:Fail() + end +end + +--- Add a FiniteStateMachine to @{Task} with key Task@{Unit} +-- @param #TASK self +-- @param Wrapper.Unit#UNIT TaskUnit +-- @return #TASK self +function TASK:SetStateMachine( TaskUnit, Fsm ) + self:F( { TaskUnit, self.Fsm[TaskUnit] ~= nil } ) + + self.Fsm[TaskUnit] = Fsm + + return Fsm +end + +--- Remove FiniteStateMachines from @{Task} with key Task@{Unit} +-- @param #TASK self +-- @param Wrapper.Unit#UNIT TaskUnit +-- @return #TASK self +function TASK:RemoveStateMachine( TaskUnit ) + self:F( { TaskUnit, self.Fsm[TaskUnit] ~= nil } ) + + self.Fsm[TaskUnit] = nil + collectgarbage() + self:T( "Garbage Collected, Processes should be finalized now ...") +end + +--- Checks if there is a FiniteStateMachine assigned to Task@{Unit} for @{Task} +-- @param #TASK self +-- @param Wrapper.Unit#UNIT TaskUnit +-- @return #TASK self +function TASK:HasStateMachine( TaskUnit ) + self:F( { TaskUnit, self.Fsm[TaskUnit] ~= nil } ) + + return ( self.Fsm[TaskUnit] ~= nil ) +end + + +--- Gets the Scoring of the task +-- @param #TASK self +-- @return Functional.Scoring#SCORING Scoring +function TASK:GetScoring() + return self.Mission:GetScoring() +end + + +--- Gets the Task Index, which is a combination of the Task type, the Task name. +-- @param #TASK self +-- @return #string The Task ID +function TASK:GetTaskIndex() + + local TaskType = self:GetType() + local TaskName = self:GetName() + + return TaskType .. "." .. TaskName +end + +--- Sets the Name of the Task +-- @param #TASK self +-- @param #string TaskName +function TASK:SetName( TaskName ) + self.TaskName = TaskName +end + +--- Gets the Name of the Task +-- @param #TASK self +-- @return #string The Task Name +function TASK:GetName() + return self.TaskName +end + +--- Sets the Type of the Task +-- @param #TASK self +-- @param #string TaskType +function TASK:SetType( TaskType ) + self.TaskType = TaskType +end + +--- Gets the Type of the Task +-- @param #TASK self +-- @return #string TaskType +function TASK:GetType() + return self.TaskType +end + +--- Sets the ID of the Task +-- @param #TASK self +-- @param #string TaskID +function TASK:SetID( TaskID ) + self.TaskID = TaskID +end + +--- Gets the ID of the Task +-- @param #TASK self +-- @return #string TaskID +function TASK:GetID() + return self.TaskID +end + + +--- Sets a @{Task} to status **Success**. +-- @param #TASK self +function TASK:StateSuccess() + self:SetState( self, "State", "Success" ) + return self +end + +--- Is the @{Task} status **Success**. +-- @param #TASK self +function TASK:IsStateSuccess() + return self:Is( "Success" ) +end + +--- Sets a @{Task} to status **Failed**. +-- @param #TASK self +function TASK:StateFailed() + self:SetState( self, "State", "Failed" ) + return self +end + +--- Is the @{Task} status **Failed**. +-- @param #TASK self +function TASK:IsStateFailed() + return self:Is( "Failed" ) +end + +--- Sets a @{Task} to status **Planned**. +-- @param #TASK self +function TASK:StatePlanned() + self:SetState( self, "State", "Planned" ) + return self +end + +--- Is the @{Task} status **Planned**. +-- @param #TASK self +function TASK:IsStatePlanned() + return self:Is( "Planned" ) +end + +--- Sets a @{Task} to status **Assigned**. +-- @param #TASK self +function TASK:StateAssigned() + self:SetState( self, "State", "Assigned" ) + return self +end + +--- Is the @{Task} status **Assigned**. +-- @param #TASK self +function TASK:IsStateAssigned() + return self:Is( "Assigned" ) +end + +--- Sets a @{Task} to status **Hold**. +-- @param #TASK self +function TASK:StateHold() + self:SetState( self, "State", "Hold" ) + return self +end + +--- Is the @{Task} status **Hold**. +-- @param #TASK self +function TASK:IsStateHold() + return self:Is( "Hold" ) +end + +--- Sets a @{Task} to status **Replanned**. +-- @param #TASK self +function TASK:StateReplanned() + self:SetState( self, "State", "Replanned" ) + return self +end + +--- Is the @{Task} status **Replanned**. +-- @param #TASK self +function TASK:IsStateReplanned() + return self:Is( "Replanned" ) +end + +--- Gets the @{Task} status. +-- @param #TASK self +function TASK:GetStateString() + return self:GetState( self, "State" ) +end + +--- Sets a @{Task} briefing. +-- @param #TASK self +-- @param #string TaskBriefing +-- @return #TASK self +function TASK:SetBriefing( TaskBriefing ) + self.TaskBriefing = TaskBriefing + return self +end + + + + +--- FSM function for a TASK +-- @param #TASK self +-- @param #string Event +-- @param #string From +-- @param #string To +function TASK:onenterAssigned( Event, From, To ) + + self:E("Task Assigned") + + self:MessageToGroups( "Task " .. self:GetName() .. " has been assigned!" ) + self:GetMission():__Start() +end + + +--- FSM function for a TASK +-- @param #TASK self +-- @param #string Event +-- @param #string From +-- @param #string To +function TASK:onenterSuccess( Event, From, To ) + + self:E( "Task Success" ) + + self:MessageToGroups( "Task " .. self:GetName() .. " is successful! Good job!" ) + self:UnAssignFromGroups() + + self:GetMission():__Complete() + +end + + +--- FSM function for a TASK +-- @param #TASK self +-- @param #string Event +-- @param #string From +-- @param #string To +function TASK:onenterAborted( Event, From, To ) + + self:E( "Task Aborted" ) + + self:GetMission():GetCommandCenter():MessageToCoalition( "Task " .. self:GetName() .. " has been aborted! Task may be replanned." ) + + self:UnAssignFromGroups() +end + +--- FSM function for a TASK +-- @param #TASK self +-- @param #string Event +-- @param #string From +-- @param #string To +function TASK:onenterFailed( Event, From, To ) + + self:E( "Task Failed" ) + + self:GetMission():GetCommandCenter():MessageToCoalition( "Task " .. self:GetName() .. " has failed!" ) + + self:UnAssignFromGroups() +end + +--- FSM function for a TASK +-- @param #TASK self +-- @param #string Event +-- @param #string From +-- @param #string To +function TASK:onstatechange( Event, From, To ) + + if self:IsTrace() then + MESSAGE:New( "@ Task " .. self.TaskName .. " : " .. Event .. " changed to state " .. To, 2 ):ToAll() + end + + if self.Scores[To] then + local Scoring = self:GetScoring() + if Scoring then + self:E( { self.Scores[To].ScoreText, self.Scores[To].Score } ) + Scoring:_AddMissionScore( self.Mission, self.Scores[To].ScoreText, self.Scores[To].Score ) + end + end + +end + +do -- Reporting + +--- Create a summary report of the Task. +-- List the Task Name and Status +-- @param #TASK self +-- @return #string +function TASK:ReportSummary() + + local Report = REPORT:New() + + -- List the name of the Task. + local Name = self:GetName() + + -- Determine the status of the Task. + local State = self:GetState() + + Report:Add( "Task " .. Name .. " - State '" .. State ) + + return Report:Text() +end + + +--- Create a detailed report of the Task. +-- List the Task Status, and the Players assigned to the Task. +-- @param #TASK self +-- @return #string +function TASK:ReportDetails() + + local Report = REPORT:New() + + -- List the name of the Task. + local Name = self:GetName() + + -- Determine the status of the Task. + local State = self:GetState() + + + -- Loop each Unit active in the Task, and find Player Names. + local PlayerNames = {} + for PlayerGroupID, PlayerGroup in pairs( self:GetGroups():GetSet() ) do + local Player = PlayerGroup -- Wrapper.Group#GROUP + for PlayerUnitID, PlayerUnit in pairs( PlayerGroup:GetUnits() ) do + local PlayerUnit = PlayerUnit -- Wrapper.Unit#UNIT + if PlayerUnit and PlayerUnit:IsAlive() then + local PlayerName = PlayerUnit:GetPlayerName() + PlayerNames[#PlayerNames+1] = PlayerName + end + end + PlayerNameText = table.concat( PlayerNames, ", " ) + Report:Add( "Task " .. Name .. " - State '" .. State .. "' - Players " .. PlayerNameText ) + end + + return Report:Text() +end + + +end -- Reporting diff --git a/Moose Development/Moose/TaskMenu.lua b/Moose Development/Moose/Tasking/TaskMenu.lua similarity index 86% rename from Moose Development/Moose/TaskMenu.lua rename to Moose Development/Moose/Tasking/TaskMenu.lua index 75467a81a..7b81bdcac 100644 --- a/Moose Development/Moose/TaskMenu.lua +++ b/Moose Development/Moose/Tasking/TaskMenu.lua @@ -2,9 +2,9 @@ --- TASK2_MENU_CLIENT class -- @type TASK2_MENU_CLIENT --- @field Unit#UNIT TaskUnit --- @field Set#SET_UNIT TargetSet --- @field Menu#MENU_CLIENT_COMMAND MenuTask +-- @field Wrapper.Unit#UNIT TaskUnit +-- @field Core.Set#SET_UNIT TargetSet +-- @field Core.Menu#MENU_CLIENT_COMMAND MenuTask -- @extends Task2#TASK2 TASK2_MENU_CLIENT = { ClassName = "TASK2_MENU_CLIENT", @@ -14,8 +14,8 @@ TASK2_MENU_CLIENT = { --- Creates a new MENU handling machine. -- @param #TASK2_MENU_CLIENT self --- @param Mission#MISSION Mission --- @param Unit#UNIT TaskUnit +-- @param Tasking.Mission#MISSION Mission +-- @param Wrapper.Unit#UNIT TaskUnit -- @param #string MenuText The text of the menu item. -- @return #TASK2_MENU_CLIENT self function TASK2_MENU_CLIENT:New( Mission, TaskUnit, MenuText ) @@ -25,7 +25,7 @@ function TASK2_MENU_CLIENT:New( Mission, TaskUnit, MenuText ) self.MenuText = MenuText - self.Fsm = STATEMACHINE_TASK:New( self, { + self.Fsm = FSM_TASK:New( self, { initial = 'Unassigned', events = { { name = 'Menu', from = 'Unassigned', to = 'AwaitingMenu' }, @@ -47,7 +47,7 @@ end --- StateMachine callback function for a TASK2 -- @param #TASK2_MENU_CLIENT self --- @param StateMachine#STATEMACHINE_TASK Fsm +-- @param Core.Fsm#FSM_TASK Fsm -- @param #string Event -- @param #string From -- @param #string To @@ -71,7 +71,7 @@ end --- StateMachine callback function for a TASK2 -- @param #TASK2_MENU_CLIENT self --- @param StateMachine#STATEMACHINE_TASK Fsm +-- @param Core.Fsm#FSM_TASK Fsm -- @param #string Event -- @param #string From -- @param #string To diff --git a/Moose Development/Moose/Tasking/Task_A2G.lua b/Moose Development/Moose/Tasking/Task_A2G.lua new file mode 100644 index 000000000..35f49a277 --- /dev/null +++ b/Moose Development/Moose/Tasking/Task_A2G.lua @@ -0,0 +1,84 @@ +--- (AI) (SP) (MP) Tasking for Air to Ground Processes. +-- +-- 1) @{#TASK_A2G} class, extends @{Tasking.Task#TASK} +-- ================================================= +-- The @{#TASK_A2G} class defines a CAS or BAI task of a @{Set} of Target Units, +-- located at a Target Zone, based on the tasking capabilities defined in @{Tasking.Task#TASK}. +-- The TASK_A2G is implemented using a @{Statemachine#FSM_TASK}, and has the following statuses: +-- +-- * **None**: Start of the process +-- * **Planned**: The SEAD task is planned. Upon Planned, the sub-process @{Process_Fsm.Assign#ACT_ASSIGN_ACCEPT} is started to accept the task. +-- * **Assigned**: The SEAD task is assigned to a @{Wrapper.Group#GROUP}. Upon Assigned, the sub-process @{Process_Fsm.Route#ACT_ROUTE} is started to route the active Units in the Group to the attack zone. +-- * **Success**: The SEAD task is successfully completed. Upon Success, the sub-process @{Process_SEAD#PROCESS_SEAD} is started to follow-up successful SEADing of the targets assigned in the task. +-- * **Failed**: The SEAD task has failed. This will happen if the player exists the task early, without communicating a possible cancellation to HQ. +-- +-- === +-- +-- ### Authors: FlightControl - Design and Programming +-- +-- @module Task_A2G + + +do -- TASK_A2G + + --- The TASK_A2G class + -- @type TASK_A2G + -- @extends Tasking.Task#TASK + TASK_A2G = { + ClassName = "TASK_A2G", + } + + --- Instantiates a new TASK_A2G. + -- @param #TASK_A2G self + -- @param Tasking.Mission#MISSION Mission + -- @param Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. + -- @param #string TaskName The name of the Task. + -- @param #string TaskType BAI or CAS + -- @param Set#SET_UNIT UnitSetTargets + -- @param Core.Zone#ZONE_BASE TargetZone + -- @return #TASK_A2G self + function TASK_A2G:New( Mission, SetGroup, TaskName, TaskType, TargetSetUnit, TargetZone, FACUnit ) + local self = BASE:Inherit( self, TASK:New( Mission, SetGroup, TaskName, TaskType ) ) + self:F() + + self.TargetSetUnit = TargetSetUnit + self.TargetZone = TargetZone + self.FACUnit = FACUnit + + local Fsm = self:GetUnitProcess() + + Fsm:AddProcess( "Planned", "Accept", ACT_ASSIGN_ACCEPT:New( "Attack the Area" ), { Assigned = "Route", Rejected = "Eject" } ) + Fsm:AddProcess( "Assigned", "Route", ACT_ROUTE_ZONE:New( self.TargetZone ), { Arrived = "Update" } ) + Fsm:AddAction ( "Rejected", "Eject", "Planned" ) + Fsm:AddAction ( "Arrived", "Update", "Updated" ) + Fsm:AddProcess( "Updated", "Account", ACT_ACCOUNT_DEADS:New( self.TargetSetUnit, "Attack" ), { Accounted = "Success" } ) + Fsm:AddProcess( "Updated", "Smoke", ACT_ASSIST_SMOKE_TARGETS_ZONE:New( self.TargetSetUnit, self.TargetZone ) ) + --Fsm:AddProcess( "Updated", "JTAC", PROCESS_JTAC:New( self, TaskUnit, self.TargetSetUnit, self.FACUnit ) ) + Fsm:AddAction ( "Accounted", "Success", "Success" ) + Fsm:AddAction ( "Failed", "Fail", "Failed" ) + + function Fsm:onenterUpdated( TaskUnit ) + self:E( { self } ) + self:Account() + self:Smoke() + end + + + + --_EVENTDISPATCHER:OnPlayerLeaveUnit( self._EventPlayerLeaveUnit, self ) + --_EVENTDISPATCHER:OnDead( self._EventDead, self ) + --_EVENTDISPATCHER:OnCrash( self._EventDead, self ) + --_EVENTDISPATCHER:OnPilotDead( self._EventDead, self ) + + return self + end + + --- @param #TASK_A2G self + function TASK_A2G:GetPlannedMenuText() + return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.TargetSetUnit:GetUnitTypesText() .. " )" + end + + end + + + diff --git a/Moose Development/Moose/AIBalancer.lua b/Moose Development/Moose/Tasking/Task_AIBalancer.lua similarity index 60% rename from Moose Development/Moose/AIBalancer.lua rename to Moose Development/Moose/Tasking/Task_AIBalancer.lua index c9bf675a9..d68c991f8 100644 --- a/Moose Development/Moose/AIBalancer.lua +++ b/Moose Development/Moose/Tasking/Task_AIBalancer.lua @@ -1,69 +1,92 @@ ---- This module contains the AIBALANCER class. +--- This module contains the AI_BALANCER class. -- -- === -- --- 1) @{AIBalancer#AIBALANCER} class, extends @{Base#BASE} --- ================================================ --- The @{AIBalancer#AIBALANCER} class controls the dynamic spawning of AI GROUPS depending on a SET_CLIENT. +-- 1) @{AI.AI_Balancer#AI_BALANCER} class, extends @{Core.Base#BASE} +-- ======================================================= +-- The @{AI.AI_Balancer#AI_BALANCER} class controls the dynamic spawning of AI GROUPS depending on a SET_CLIENT. -- There will be as many AI GROUPS spawned as there at CLIENTS in SET_CLIENT not spawned. +-- The AI_Balancer uses the @{PatrolCore.Zone#AI_PATROLZONE} class to make AI patrol an zone until the fuel treshold is reached. -- --- 1.1) AIBALANCER construction method: +-- 1.1) AI_BALANCER construction method: -- ------------------------------------ --- Create a new AIBALANCER object with the @{#AIBALANCER.New} method: +-- Create a new AI_BALANCER object with the @{#AI_BALANCER.New} method: -- --- * @{#AIBALANCER.New}: Creates a new AIBALANCER object. +-- * @{#AI_BALANCER.New}: Creates a new AI_BALANCER object. -- --- 1.2) AIBALANCER returns AI to Airbases: +-- 1.2) AI_BALANCER returns AI to Airbases: -- --------------------------------------- -- You can configure to have the AI to return to: -- --- * @{#AIBALANCER.ReturnToHomeAirbase}: Returns the AI to the home @{Airbase#AIRBASE}. --- * @{#AIBALANCER.ReturnToNearestAirbases}: Returns the AI to the nearest friendly @{Airbase#AIRBASE}. +-- * @{#AI_BALANCER.ReturnToHomeAirbase}: Returns the AI to the home @{Wrapper.Airbase#AIRBASE}. +-- * @{#AI_BALANCER.ReturnToNearestAirbases}: Returns the AI to the nearest friendly @{Wrapper.Airbase#AIRBASE}. -- --- 1.3) AIBALANCER allows AI to patrol specific zones: +-- 1.3) AI_BALANCER allows AI to patrol specific zones: -- --------------------------------------------------- --- Use @{AIBalancer#AIBALANCER.SetPatrolZone}() to specify a zone where the AI needs to patrol. +-- Use @{AI.AI_Balancer#AI_BALANCER.SetPatrolZone}() to specify a zone where the AI needs to patrol. -- -- === -- +-- **API CHANGE HISTORY** +-- ====================== +-- +-- The underlying change log documents the API changes. Please read this carefully. The following notation is used: +-- +-- * **Added** parts are expressed in bold type face. +-- * _Removed_ parts are expressed in italic type face. +-- +-- Hereby the change log: +-- +-- 2016-08-17: SPAWN:**InitCleanUp**( SpawnCleanUpInterval ) replaces SPAWN:_CleanUp_( SpawnCleanUpInterval ) +-- +-- * Want to ensure that the methods starting with **Init** are the first called methods before any _Spawn_ method is called! +-- * This notation makes it now more clear which methods are initialization methods and which methods are Spawn enablement methods. +-- +-- === +-- +-- AUTHORS and CONTRIBUTIONS +-- ========================= +-- -- ### Contributions: -- --- * **Dutch_Baron (James)** Who you can search on the Eagle Dynamics Forums. --- Working together with James has resulted in the creation of the AIBALANCER class. +-- * **Dutch_Baron (James)**: Who you can search on the Eagle Dynamics Forums. +-- Working together with James has resulted in the creation of the AI_BALANCER class. -- James has shared his ideas on balancing AI with air units, and together we made a first design which you can use now :-) -- --- * **SNAFU** +-- * **SNAFU**: -- Had a couple of mails with the guys to validate, if the same concept in the GCI/CAP script could be reworked within MOOSE. --- None of the script code has been used however within the new AIBALANCER moose class. +-- None of the script code has been used however within the new AI_BALANCER moose class. -- -- ### Authors: -- --- * FlightControl - Framework Design & Programming +-- * FlightControl: Framework Design & Programming -- --- @module AIBalancer +-- @module AI_Balancer ---- AIBALANCER class --- @type AIBALANCER --- @field Set#SET_CLIENT SetClient --- @field Spawn#SPAWN SpawnAI + + +--- AI_BALANCER class +-- @type AI_BALANCER +-- @field Core.Set#SET_CLIENT SetClient +-- @field Functional.Spawn#SPAWN SpawnAI -- @field #boolean ToNearestAirbase --- @field Set#SET_AIRBASE ReturnAirbaseSet --- @field DCSTypes#Distance ReturnTresholdRange +-- @field Core.Set#SET_AIRBASE ReturnAirbaseSet +-- @field Dcs.DCSTypes#Distance ReturnTresholdRange -- @field #boolean ToHomeAirbase --- @field PatrolZone#PATROLZONE PatrolZone --- @extends Base#BASE -AIBALANCER = { - ClassName = "AIBALANCER", +-- @field PatrolCore.Zone#AI_PATROLZONE PatrolZone +-- @extends Core.Base#BASE +AI_BALANCER = { + ClassName = "AI_BALANCER", PatrolZones = {}, AIGroups = {}, } ---- Creates a new AIBALANCER object, building a set of units belonging to a coalitions, categories, countries, types or with defined prefix names. --- @param #AIBALANCER self +--- Creates a new AI_BALANCER object, building a set of units belonging to a coalitions, categories, countries, types or with defined prefix names. +-- @param #AI_BALANCER self -- @param SetClient A SET_CLIENT object that will contain the CLIENT objects to be monitored if they are alive or not (joined by a player). -- @param SpawnAI A SPAWN object that will spawn the AI units required, balancing the SetClient. --- @return #AIBALANCER self -function AIBALANCER:New( SetClient, SpawnAI ) +-- @return #AI_BALANCER self +function AI_BALANCER:New( SetClient, SpawnAI ) -- Inherits from BASE local self = BASE:Inherit( self, BASE:New() ) @@ -98,40 +121,57 @@ function AIBALANCER:New( SetClient, SpawnAI ) return self end ---- Returns the AI to the nearest friendly @{Airbase#AIRBASE}. --- @param #AIBALANCER self --- @param DCSTypes#Distance ReturnTresholdRange If there is an enemy @{Client#CLIENT} within the ReturnTresholdRange given in meters, the AI will not return to the nearest @{Airbase#AIRBASE}. --- @param Set#SET_AIRBASE ReturnAirbaseSet The SET of @{Set#SET_AIRBASE}s to evaluate where to return to. -function AIBALANCER:ReturnToNearestAirbases( ReturnTresholdRange, ReturnAirbaseSet ) +--- Returns the AI to the nearest friendly @{Wrapper.Airbase#AIRBASE}. +-- @param #AI_BALANCER self +-- @param Dcs.DCSTypes#Distance ReturnTresholdRange If there is an enemy @{Wrapper.Client#CLIENT} within the ReturnTresholdRange given in meters, the AI will not return to the nearest @{Wrapper.Airbase#AIRBASE}. +-- @param Core.Set#SET_AIRBASE ReturnAirbaseSet The SET of @{Core.Set#SET_AIRBASE}s to evaluate where to return to. +function AI_BALANCER:ReturnToNearestAirbases( ReturnTresholdRange, ReturnAirbaseSet ) self.ToNearestAirbase = true self.ReturnTresholdRange = ReturnTresholdRange self.ReturnAirbaseSet = ReturnAirbaseSet end ---- Returns the AI to the home @{Airbase#AIRBASE}. --- @param #AIBALANCER self --- @param DCSTypes#Distance ReturnTresholdRange If there is an enemy @{Client#CLIENT} within the ReturnTresholdRange given in meters, the AI will not return to the nearest @{Airbase#AIRBASE}. -function AIBALANCER:ReturnToHomeAirbase( ReturnTresholdRange ) +--- Returns the AI to the home @{Wrapper.Airbase#AIRBASE}. +-- @param #AI_BALANCER self +-- @param Dcs.DCSTypes#Distance ReturnTresholdRange If there is an enemy @{Wrapper.Client#CLIENT} within the ReturnTresholdRange given in meters, the AI will not return to the nearest @{Wrapper.Airbase#AIRBASE}. +function AI_BALANCER:ReturnToHomeAirbase( ReturnTresholdRange ) self.ToHomeAirbase = true self.ReturnTresholdRange = ReturnTresholdRange end --- Let the AI patrol a @{Zone} with a given Speed range and Altitude range. --- @param #AIBALANCER self --- @param PatrolZone#PATROLZONE PatrolZone The @{PatrolZone} where the AI needs to patrol. --- @return PatrolZone#PATROLZONE self -function AIBALANCER:SetPatrolZone( PatrolZone ) +-- @param #AI_BALANCER self +-- @param PatrolCore.Zone#AI_PATROLZONE PatrolZone The @{PatrolZone} where the AI needs to patrol. +-- @return PatrolCore.Zone#AI_PATROLZONE self +function AI_BALANCER:SetPatrolZone( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed ) - self.PatrolZone = PatrolZone + self.PatrolZone = AI_PATROLZONE:New( + self.SpawnAI, + PatrolZone, + PatrolFloorAltitude, + PatrolCeilingAltitude, + PatrolMinSpeed, + PatrolMaxSpeed + ) end ---- @param #AIBALANCER self -function AIBALANCER:_ClientAliveMonitorScheduler() +--- Get the @{PatrolZone} object assigned by the @{AI_Balancer} object. +-- @param #AI_BALANCER self +-- @return PatrolCore.Zone#AI_PATROLZONE PatrolZone The @{PatrolZone} where the AI needs to patrol. +function AI_BALANCER:GetPatrolZone() + + return self.PatrolZone +end + + + +--- @param #AI_BALANCER self +function AI_BALANCER:_ClientAliveMonitorScheduler() self.SetClient:ForEachClient( - --- @param Client#CLIENT Client + --- @param Wrapper.Client#CLIENT Client function( Client ) local ClientAIAliveState = Client:GetState( self, 'AIAlive' ) self:T( ClientAIAliveState ) @@ -139,7 +179,7 @@ function AIBALANCER:_ClientAliveMonitorScheduler() if ClientAIAliveState == true then Client:SetState( self, 'AIAlive', false ) - local AIGroup = self.AIGroups[Client.UnitName] -- Group#GROUP + local AIGroup = self.AIGroups[Client.UnitName] -- Wrapper.Group#GROUP -- local PatrolZone = Client:GetState( self, "PatrolZone" ) -- if PatrolZone then @@ -160,7 +200,7 @@ function AIBALANCER:_ClientAliveMonitorScheduler() self:E( RangeZone ) _DATABASE:ForEachPlayer( - --- @param Unit#UNIT RangeTestUnit + --- @param Wrapper.Unit#UNIT RangeTestUnit function( RangeTestUnit, RangeZone, AIGroup, PlayerInRange ) self:E( { PlayerInRange, RangeTestUnit.UnitName, RangeZone.ZoneName } ) if RangeTestUnit:IsInZone( RangeZone ) == true then @@ -172,8 +212,8 @@ function AIBALANCER:_ClientAliveMonitorScheduler() end end, - --- @param Zone#ZONE_RADIUS RangeZone - -- @param Group#GROUP AIGroup + --- @param Core.Zone#ZONE_RADIUS RangeZone + -- @param Wrapper.Group#GROUP AIGroup function( RangeZone, AIGroup, PlayerInRange ) local AIGroupTemplate = AIGroup:GetTemplate() if PlayerInRange.Value == false then @@ -215,7 +255,7 @@ function AIBALANCER:_ClientAliveMonitorScheduler() --- Now test if the AIGroup needs to patrol a zone, otherwise let it follow its route... if self.PatrolZone then - self.PatrolZones[#self.PatrolZones+1] = PATROLZONE:New( + self.PatrolZones[#self.PatrolZones+1] = AI_PATROLZONE:New( self.PatrolZone.PatrolZone, self.PatrolZone.PatrolFloorAltitude, self.PatrolZone.PatrolCeilingAltitude, diff --git a/Moose Development/Moose/Task_Pickup.lua b/Moose Development/Moose/Tasking/Task_Pickup.lua similarity index 79% rename from Moose Development/Moose/Task_Pickup.lua rename to Moose Development/Moose/Tasking/Task_Pickup.lua index cb73cb894..a227ab71d 100644 --- a/Moose Development/Moose/Task_Pickup.lua +++ b/Moose Development/Moose/Tasking/Task_Pickup.lua @@ -1,14 +1,14 @@ --- This module contains the TASK_PICKUP classes. -- --- 1) @{#TASK_PICKUP} class, extends @{Task#TASK_BASE} +-- 1) @{#TASK_PICKUP} class, extends @{Tasking.Task#TASK} -- =================================================== -- The @{#TASK_PICKUP} class defines a pickup task of a @{Set} of @{CARGO} objects defined within the mission. --- based on the tasking capabilities defined in @{Task#TASK_BASE}. --- The TASK_PICKUP is implemented using a @{Statemachine#STATEMACHINE_TASK}, and has the following statuses: +-- based on the tasking capabilities defined in @{Tasking.Task#TASK}. +-- The TASK_PICKUP is implemented using a @{Statemachine#FSM_TASK}, and has the following statuses: -- -- * **None**: Start of the process --- * **Planned**: The SEAD task is planned. Upon Planned, the sub-process @{Process_Assign#PROCESS_ASSIGN_ACCEPT} is started to accept the task. --- * **Assigned**: The SEAD task is assigned to a @{Group#GROUP}. Upon Assigned, the sub-process @{Process_Route#PROCESS_ROUTE} is started to route the active Units in the Group to the attack zone. +-- * **Planned**: The SEAD task is planned. Upon Planned, the sub-process @{Process_Fsm.Assign#ACT_ASSIGN_ACCEPT} is started to accept the task. +-- * **Assigned**: The SEAD task is assigned to a @{Wrapper.Group#GROUP}. Upon Assigned, the sub-process @{Process_Fsm.Route#ACT_ROUTE} is started to route the active Units in the Group to the attack zone. -- * **Success**: The SEAD task is successfully completed. Upon Success, the sub-process @{Process_SEAD#PROCESS_SEAD} is started to follow-up successful SEADing of the targets assigned in the task. -- * **Failed**: The SEAD task has failed. This will happen if the player exists the task early, without communicating a possible cancellation to HQ. -- @@ -23,22 +23,22 @@ do -- TASK_PICKUP --- The TASK_PICKUP class -- @type TASK_PICKUP - -- @extends Task#TASK_BASE + -- @extends Tasking.Task#TASK TASK_PICKUP = { ClassName = "TASK_PICKUP", } --- Instantiates a new TASK_PICKUP. -- @param #TASK_PICKUP self - -- @param Mission#MISSION Mission + -- @param Tasking.Mission#MISSION Mission -- @param Set#SET_GROUP AssignedSetGroup The set of groups for which the Task can be assigned. -- @param #string TaskName The name of the Task. -- @param #string TaskType BAI or CAS -- @param Set#SET_UNIT UnitSetTargets - -- @param Zone#ZONE_BASE TargetZone + -- @param Core.Zone#ZONE_BASE TargetZone -- @return #TASK_PICKUP self function TASK_PICKUP:New( Mission, AssignedSetGroup, TaskName, TaskType ) - local self = BASE:Inherit( self, TASK_BASE:New( Mission, AssignedSetGroup, TaskName, TaskType, "PICKUP" ) ) + local self = BASE:Inherit( self, TASK:New( Mission, AssignedSetGroup, TaskName, TaskType, "PICKUP" ) ) self:F() _EVENTDISPATCHER:OnPlayerLeaveUnit( self._EventPlayerLeaveUnit, self ) @@ -62,15 +62,15 @@ do -- TASK_PICKUP --- Assign the @{Task} to a @{Unit}. -- @param #TASK_PICKUP self - -- @param Unit#UNIT TaskUnit + -- @param Wrapper.Unit#UNIT TaskUnit -- @return #TASK_PICKUP self function TASK_PICKUP:AssignToUnit( TaskUnit ) self:F( TaskUnit:GetName() ) - local ProcessAssign = self:AddProcess( TaskUnit, PROCESS_ASSIGN_ACCEPT:New( self, TaskUnit, self.TaskBriefing ) ) + local ProcessAssign = self:AddProcess( TaskUnit, ACT_ASSIGN_ACCEPT:New( self, TaskUnit, self.TaskBriefing ) ) local ProcessPickup = self:AddProcess( TaskUnit, PROCESS_PICKUP:New( self, self.TaskType, TaskUnit ) ) - local Process = self:AddStateMachine( TaskUnit, STATEMACHINE_TASK:New( self, TaskUnit, { + local Process = self:AddStateMachine( TaskUnit, FSM_TASK:New( self, TaskUnit, { initial = 'None', events = { { name = 'Next', from = 'None', to = 'Planned' }, @@ -98,7 +98,7 @@ do -- TASK_PICKUP --- StateMachine callback function for a TASK -- @param #TASK_PICKUP self - -- @param StateMachine#STATEMACHINE_TASK Fsm + -- @param Core.Fsm#FSM_TASK Fsm -- @param #string Event -- @param #string From -- @param #string To diff --git a/Moose Development/Moose/Tasking/Task_SEAD.lua b/Moose Development/Moose/Tasking/Task_SEAD.lua new file mode 100644 index 000000000..d9acf5157 --- /dev/null +++ b/Moose Development/Moose/Tasking/Task_SEAD.lua @@ -0,0 +1,78 @@ +--- This module contains the TASK_SEAD classes. +-- +-- 1) @{#TASK_SEAD} class, extends @{Tasking.Task#TASK} +-- ================================================= +-- The @{#TASK_SEAD} class defines a SEAD task for a @{Set} of Target Units, located at a Target Zone, +-- based on the tasking capabilities defined in @{Tasking.Task#TASK}. +-- The TASK_SEAD is implemented using a @{Statemachine#FSM_TASK}, and has the following statuses: +-- +-- * **None**: Start of the process +-- * **Planned**: The SEAD task is planned. Upon Planned, the sub-process @{Process_Fsm.Assign#ACT_ASSIGN_ACCEPT} is started to accept the task. +-- * **Assigned**: The SEAD task is assigned to a @{Wrapper.Group#GROUP}. Upon Assigned, the sub-process @{Process_Fsm.Route#ACT_ROUTE} is started to route the active Units in the Group to the attack zone. +-- * **Success**: The SEAD task is successfully completed. Upon Success, the sub-process @{Process_SEAD#PROCESS_SEAD} is started to follow-up successful SEADing of the targets assigned in the task. +-- * **Failed**: The SEAD task has failed. This will happen if the player exists the task early, without communicating a possible cancellation to HQ. +-- +-- === +-- +-- ### Authors: FlightControl - Design and Programming +-- +-- @module Task_SEAD + + + +do -- TASK_SEAD + + --- The TASK_SEAD class + -- @type TASK_SEAD + -- @field Set#SET_UNIT TargetSetUnit + -- @extends Tasking.Task#TASK + TASK_SEAD = { + ClassName = "TASK_SEAD", + } + + --- Instantiates a new TASK_SEAD. + -- @param #TASK_SEAD self + -- @param Tasking.Mission#MISSION Mission + -- @param Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. + -- @param #string TaskName The name of the Task. + -- @param Set#SET_UNIT UnitSetTargets + -- @param Core.Zone#ZONE_BASE TargetZone + -- @return #TASK_SEAD self + function TASK_SEAD:New( Mission, SetGroup, TaskName, TargetSetUnit, TargetZone ) + local self = BASE:Inherit( self, TASK:New( Mission, SetGroup, TaskName, "SEAD" ) ) -- Tasking.Task_SEAD#TASK_SEAD + self:F() + + self.TargetSetUnit = TargetSetUnit + self.TargetZone = TargetZone + + local Fsm = self:GetUnitProcess() + + Fsm:AddProcess( "Planned", "Accept", ACT_ASSIGN_ACCEPT:New( self.TaskBriefing ), { Assigned = "Route", Rejected = "Eject" } ) + Fsm:AddProcess( "Assigned", "Route", ACT_ROUTE_ZONE:New( self.TargetZone ), { Arrived = "Update" } ) + Fsm:AddAction ( "Rejected", "Eject", "Planned" ) + Fsm:AddAction ( "Arrived", "Update", "Updated" ) + Fsm:AddProcess( "Updated", "Account", ACT_ACCOUNT_DEADS:New( self.TargetSetUnit, "SEAD" ), { Accounted = "Success" } ) + Fsm:AddProcess( "Updated", "Smoke", ACT_ASSIST_SMOKE_TARGETS_ZONE:New( self.TargetSetUnit, self.TargetZone ) ) + Fsm:AddAction ( "Accounted", "Success", "Success" ) + Fsm:AddAction ( "Failed", "Fail", "Failed" ) + + function Fsm:onenterUpdated( TaskUnit ) + self:E( { self } ) + self:Account() + self:Smoke() + end + +-- _EVENTDISPATCHER:OnPlayerLeaveUnit( self._EventPlayerLeaveUnit, self ) +-- _EVENTDISPATCHER:OnDead( self._EventDead, self ) +-- _EVENTDISPATCHER:OnCrash( self._EventDead, self ) +-- _EVENTDISPATCHER:OnPilotDead( self._EventDead, self ) + + return self + end + + --- @param #TASK_SEAD self + function TASK_SEAD:GetPlannedMenuText() + return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.TargetSetUnit:GetUnitTypesText() .. " )" + end + +end diff --git a/Moose Development/Moose/Routines.lua b/Moose Development/Moose/Utilities/Routines.lua similarity index 100% rename from Moose Development/Moose/Routines.lua rename to Moose Development/Moose/Utilities/Routines.lua diff --git a/Moose Development/Moose/StatHandler.lua b/Moose Development/Moose/Utilities/StatHandler.lua similarity index 100% rename from Moose Development/Moose/StatHandler.lua rename to Moose Development/Moose/Utilities/StatHandler.lua diff --git a/Moose Development/Moose/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua similarity index 91% rename from Moose Development/Moose/Utils.lua rename to Moose Development/Moose/Utilities/Utils.lua index 512bc2de0..3cf7c34ec 100644 --- a/Moose Development/Moose/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -1,3 +1,16 @@ +--- This module contains derived utilities taken from the MIST framework, +-- which are excellent tools to be reused in an OO environment!. +-- +-- ### Authors: +-- +-- * Grimes : Design & Programming of the MIST framework. +-- +-- ### Contributions: +-- +-- * FlightControl : Rework to OO framework +-- +-- @module Utils + --- @type SMOKECOLOR -- @field Green @@ -100,11 +113,11 @@ UTILS.OneLineSerialize = function( tbl ) -- serialization of a table all on a s tbl_str[#tbl_str + 1] = table.concat(val_str) end elseif type(val) == 'function' then - -- tbl_str[#tbl_str + 1] = "function " .. tostring(ind) - -- tbl_str[#tbl_str + 1] = ',' --I think this is right, I just added it + tbl_str[#tbl_str + 1] = "f() " .. tostring(ind) + tbl_str[#tbl_str + 1] = ',' --I think this is right, I just added it else --- env.info('unable to serialize value type ' .. routines.utils.basicSerialize(type(val)) .. ' at index ' .. tostring(ind)) --- env.info( debug.traceback() ) + env.info('unable to serialize value type ' .. routines.utils.basicSerialize(type(val)) .. ' at index ' .. tostring(ind)) + env.info( debug.traceback() ) end end diff --git a/Moose Development/Moose/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua similarity index 89% rename from Moose Development/Moose/Airbase.lua rename to Moose Development/Moose/Wrapper/Airbase.lua index 8d695872e..063bf33a8 100644 --- a/Moose Development/Moose/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -2,7 +2,7 @@ -- -- === -- --- 1) @{Airbase#AIRBASE} class, extends @{Positionable#POSITIONABLE} +-- 1) @{Wrapper.Airbase#AIRBASE} class, extends @{Wrapper.Positionable#POSITIONABLE} -- ================================================================= -- The @{AIRBASE} class is a wrapper class to handle the DCS Airbase objects: -- @@ -33,7 +33,7 @@ -- --------------------- -- The DCS Airbase APIs are used extensively within MOOSE. The AIRBASE class has for each DCS Airbase API a corresponding method. -- To be able to distinguish easily in your code the difference between a AIRBASE API call and a DCS Airbase API call, --- the first letter of the method is also capitalized. So, by example, the DCS Airbase method @{DCSAirbase#Airbase.getName}() +-- the first letter of the method is also capitalized. So, by example, the DCS Airbase method @{Dcs.DCSWrapper.Airbase#Airbase.getName}() -- is implemented in the AIRBASE class as @{#AIRBASE.GetName}(). -- -- More functions will be added @@ -49,7 +49,7 @@ --- The AIRBASE class -- @type AIRBASE --- @extends Positionable#POSITIONABLE +-- @extends Wrapper.Positionable#POSITIONABLE AIRBASE = { ClassName="AIRBASE", CategoryName = { @@ -64,7 +64,7 @@ AIRBASE = { --- Create a new AIRBASE from DCSAirbase. -- @param #AIRBASE self -- @param #string AirbaseName The name of the airbase. --- @return Airbase#AIRBASE +-- @return Wrapper.Airbase#AIRBASE function AIRBASE:Register( AirbaseName ) local self = BASE:Inherit( self, POSITIONABLE:New( AirbaseName ) ) @@ -76,8 +76,8 @@ end --- Finds a AIRBASE from the _DATABASE using a DCSAirbase object. -- @param #AIRBASE self --- @param DCSAirbase#Airbase DCSAirbase An existing DCS Airbase object reference. --- @return Airbase#AIRBASE self +-- @param Dcs.DCSWrapper.Airbase#Airbase DCSAirbase An existing DCS Airbase object reference. +-- @return Wrapper.Airbase#AIRBASE self function AIRBASE:Find( DCSAirbase ) local AirbaseName = DCSAirbase:getName() @@ -88,7 +88,7 @@ end --- Find a AIRBASE in the _DATABASE using the name of an existing DCS Airbase. -- @param #AIRBASE self -- @param #string AirbaseName The Airbase Name. --- @return Airbase#AIRBASE self +-- @return Wrapper.Airbase#AIRBASE self function AIRBASE:FindByName( AirbaseName ) local AirbaseFound = _DATABASE:FindAirbase( AirbaseName ) diff --git a/Moose Development/Moose/Client.lua b/Moose Development/Moose/Wrapper/Client.lua similarity index 93% rename from Moose Development/Moose/Client.lua rename to Moose Development/Moose/Wrapper/Client.lua index 157d13824..e603afa20 100644 --- a/Moose Development/Moose/Client.lua +++ b/Moose Development/Moose/Wrapper/Client.lua @@ -1,10 +1,10 @@ --- This module contains the CLIENT class. -- --- 1) @{Client#CLIENT} class, extends @{Unit#UNIT} +-- 1) @{Wrapper.Client#CLIENT} class, extends @{Wrapper.Unit#UNIT} -- =============================================== -- Clients are those **Units** defined within the Mission Editor that have the skillset defined as __Client__ or __Player__. -- Note that clients are NOT the same as Units, they are NOT necessarily alive. --- The @{Client#CLIENT} class is a wrapper class to handle the DCS Unit objects that have the skillset defined as __Client__ or __Player__: +-- The @{Wrapper.Client#CLIENT} class is a wrapper class to handle the DCS Unit objects that have the skillset defined as __Client__ or __Player__: -- -- * Wraps the DCS Unit objects with skill level set to Player or Client. -- * Support all DCS Unit APIs. @@ -39,7 +39,7 @@ --- The CLIENT class -- @type CLIENT --- @extends Unit#UNIT +-- @extends Wrapper.Unit#UNIT CLIENT = { ONBOARDSIDE = { NONE = 0, @@ -92,6 +92,7 @@ end -- @param #CLIENT self -- @param #string ClientName Name of the DCS **Unit** as defined within the Mission Editor. -- @param #string ClientBriefing Text that describes the briefing of the mission when a Player logs into the Client. +-- @param #boolean Error A flag that indicates whether an error should be raised if the CLIENT cannot be found. By default an error will be raised. -- @return #CLIENT -- @usage -- -- Create new Clients. @@ -102,7 +103,7 @@ end -- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*RAMP-Deploy Troops 3' ):Transport() ) -- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*HOT-Deploy Troops 2' ):Transport() ) -- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*RAMP-Deploy Troops 4' ):Transport() ) -function CLIENT:FindByName( ClientName, ClientBriefing ) +function CLIENT:FindByName( ClientName, ClientBriefing, Error ) local ClientFound = _DATABASE:FindClient( ClientName ) if ClientFound then @@ -113,7 +114,9 @@ function CLIENT:FindByName( ClientName, ClientBriefing ) return ClientFound end - error( "CLIENT not found for: " .. ClientName ) + if not Error then + error( "CLIENT not found for: " .. ClientName ) + end end function CLIENT:Register( ClientName ) @@ -236,7 +239,7 @@ end --- @param #CLIENT self function CLIENT:_AliveCheckScheduler( SchedulerName ) - self:F( { SchedulerName, self.ClientName, self.ClientAlive2, self.ClientBriefingShown, self.ClientCallBack } ) + self:F3( { SchedulerName, self.ClientName, self.ClientAlive2, self.ClientBriefingShown, self.ClientCallBack } ) if self:IsAlive() then if self.ClientAlive2 == false then @@ -259,7 +262,7 @@ end --- Return the DCSGroup of a Client. -- This function is modified to deal with a couple of bugs in DCS 1.5.3 -- @param #CLIENT self --- @return DCSGroup#Group +-- @return Dcs.DCSWrapper.Group#Group function CLIENT:GetDCSGroup() self:F3() @@ -333,10 +336,10 @@ function CLIENT:GetDCSGroup() end --- TODO: Check DCSTypes#Group.ID +-- TODO: Check Dcs.DCSTypes#Group.ID --- Get the group ID of the client. -- @param #CLIENT self --- @return DCSTypes#Group.ID +-- @return Dcs.DCSTypes#Group.ID function CLIENT:GetClientGroupID() local ClientGroup = self:GetDCSGroup() @@ -359,7 +362,7 @@ end --- Returns the UNIT of the CLIENT. -- @param #CLIENT self --- @return Unit#UNIT +-- @return Wrapper.Unit#UNIT function CLIENT:GetClientGroupUnit() self:F2() @@ -375,7 +378,7 @@ end --- Returns the DCSUnit of the CLIENT. -- @param #CLIENT self --- @return DCSTypes#Unit +-- @return Dcs.DCSTypes#Unit function CLIENT:GetClientGroupDCSUnit() self:F2() @@ -396,8 +399,8 @@ function CLIENT:IsTransport() return self.ClientTransport end ---- Shows the @{Cargo#CARGO} contained within the CLIENT to the player as a message. --- The @{Cargo#CARGO} is shown using the @{Message#MESSAGE} distribution system. +--- Shows the @{AI.AI_Cargo#CARGO} contained within the CLIENT to the player as a message. +-- The @{AI.AI_Cargo#CARGO} is shown using the @{Core.Message#MESSAGE} distribution system. -- @param #CLIENT self function CLIENT:ShowCargo() self:F() @@ -430,7 +433,7 @@ end -- @param #string Message is the text describing the message. -- @param #number MessageDuration is the duration in seconds that the Message should be displayed. -- @param #string MessageCategory is the category of the message (the title). --- @param #number MessageInterval is the interval in seconds between the display of the @{Message#MESSAGE} when the CLIENT is in the air. +-- @param #number MessageInterval is the interval in seconds between the display of the @{Core.Message#MESSAGE} when the CLIENT is in the air. -- @param #string MessageID is the identifier of the message when displayed with intervals. function CLIENT:Message( Message, MessageDuration, MessageCategory, MessageInterval, MessageID ) self:F( { Message, MessageDuration, MessageCategory, MessageInterval } ) diff --git a/Moose Development/Moose/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua similarity index 82% rename from Moose Development/Moose/Controllable.lua rename to Moose Development/Moose/Wrapper/Controllable.lua index c0753dbe9..af21429ba 100644 --- a/Moose Development/Moose/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -1,8 +1,8 @@ --- This module contains the CONTROLLABLE class. -- --- 1) @{Controllable#CONTROLLABLE} class, extends @{Positionable#POSITIONABLE} +-- 1) @{Wrapper.Controllable#CONTROLLABLE} class, extends @{Wrapper.Positionable#POSITIONABLE} -- =========================================================== --- The @{Controllable#CONTROLLABLE} class is a wrapper class to handle the DCS Controllable objects: +-- The @{Wrapper.Controllable#CONTROLLABLE} class is a wrapper class to handle the DCS Controllable objects: -- -- * Support all DCS Controllable APIs. -- * Enhance with Controllable specific APIs not in the DCS Controllable API set. @@ -18,7 +18,7 @@ -- 1.2) CONTROLLABLE task methods -- ------------------------------ -- Several controllable task methods are available that help you to prepare tasks. --- These methods return a string consisting of the task description, which can then be given to either a @{Controllable#CONTROLLABLE.PushTask} or @{Controllable#SetTask} method to assign the task to the CONTROLLABLE. +-- These methods return a string consisting of the task description, which can then be given to either a @{Wrapper.Controllable#CONTROLLABLE.PushTask} or @{Wrapper.Controllable#SetTask} method to assign the task to the CONTROLLABLE. -- Tasks are specific for the category of the CONTROLLABLE, more specific, for AIR, GROUND or AIR and GROUND. -- Each task description where applicable indicates for which controllable category the task is valid. -- There are 2 main subdivisions of tasks: Assigned tasks and EnRoute tasks. @@ -44,7 +44,7 @@ -- * @{#CONTROLLABLE.TaskHold}: (GROUND) Hold ground controllable from moving. -- * @{#CONTROLLABLE.TaskHoldPosition}: (AIR) Hold position at the current position of the first unit of the controllable. -- * @{#CONTROLLABLE.TaskLand}: (AIR HELICOPTER) Landing at the ground. For helicopters only. --- * @{#CONTROLLABLE.TaskLandAtZone}: (AIR) Land the controllable at a @{Zone#ZONE_RADIUS). +-- * @{#CONTROLLABLE.TaskLandAtZone}: (AIR) Land the controllable at a @{Core.Zone#ZONE_RADIUS). -- * @{#CONTROLLABLE.TaskOrbitCircle}: (AIR) Orbit at the current position of the first unit of the controllable at a specified alititude. -- * @{#CONTROLLABLE.TaskOrbitCircleAtVec2}: (AIR) Orbit at a specified position at a specified alititude during a specified duration with a specified speed. -- * @{#CONTROLLABLE.TaskRefueling}: (AIR) Refueling from the nearest tanker. No parameters. @@ -127,8 +127,8 @@ --- The CONTROLLABLE class -- @type CONTROLLABLE --- @extends Positionable#POSITIONABLE --- @field DCSControllable#Controllable DCSControllable The DCS controllable class. +-- @extends Wrapper.Positionable#POSITIONABLE +-- @field Dcs.DCSWrapper.Controllable#Controllable DCSControllable The DCS controllable class. -- @field #string ControllableName The name of the controllable. CONTROLLABLE = { ClassName = "CONTROLLABLE", @@ -138,7 +138,7 @@ CONTROLLABLE = { --- Create a new CONTROLLABLE from a DCSControllable -- @param #CONTROLLABLE self --- @param DCSControllable#Controllable ControllableName The DCS Controllable name +-- @param Dcs.DCSWrapper.Controllable#Controllable ControllableName The DCS Controllable name -- @return #CONTROLLABLE self function CONTROLLABLE:New( ControllableName ) local self = BASE:Inherit( self, POSITIONABLE:New( ControllableName ) ) @@ -151,7 +151,7 @@ end --- Get the controller for the CONTROLLABLE. -- @param #CONTROLLABLE self --- @return DCSController#Controller +-- @return Dcs.DCSController#Controller function CONTROLLABLE:_GetController() self:F2( { self.ControllableName } ) local DCSControllable = self:GetDCSObject() @@ -171,7 +171,7 @@ end --- Popping current Task from the controllable. -- @param #CONTROLLABLE self --- @return Controllable#CONTROLLABLE self +-- @return Wrapper.Controllable#CONTROLLABLE self function CONTROLLABLE:PopCurrentTask() self:F2() @@ -188,7 +188,7 @@ end --- Pushing Task on the queue from the controllable. -- @param #CONTROLLABLE self --- @return Controllable#CONTROLLABLE self +-- @return Wrapper.Controllable#CONTROLLABLE self function CONTROLLABLE:PushTask( DCSTask, WaitTime ) self:F2() @@ -215,7 +215,7 @@ end --- Clearing the Task Queue and Setting the Task on the queue from the controllable. -- @param #CONTROLLABLE self --- @return Controllable#CONTROLLABLE self +-- @return Wrapper.Controllable#CONTROLLABLE self function CONTROLLABLE:SetTask( DCSTask, WaitTime ) self:F2( { DCSTask } ) @@ -231,9 +231,10 @@ function CONTROLLABLE:SetTask( DCSTask, WaitTime ) -- Controller.setTask( Controller, DCSTask ) if not WaitTime then - WaitTime = 1 + Controller:setTask( DCSTask ) + else + SCHEDULER:New( Controller, Controller.setTask, { DCSTask }, WaitTime ) end - SCHEDULER:New( Controller, Controller.setTask, { DCSTask }, WaitTime ) return self end @@ -244,13 +245,13 @@ end --- Return a condition section for a controlled task. -- @param #CONTROLLABLE self --- @param DCSTime#Time time +-- @param Dcs.DCSTime#Time time -- @param #string userFlag -- @param #boolean userFlagValue -- @param #string condition --- @param DCSTime#Time duration +-- @param Dcs.DCSTime#Time duration -- @param #number lastWayPoint --- return DCSTask#Task +-- return Dcs.DCSTasking.Task#Task function CONTROLLABLE:TaskCondition( time, userFlag, userFlagValue, condition, duration, lastWayPoint ) self:F2( { time, userFlag, userFlagValue, condition, duration, lastWayPoint } ) @@ -268,9 +269,9 @@ end --- Return a Controlled Task taking a Task and a TaskCondition. -- @param #CONTROLLABLE self --- @param DCSTask#Task DCSTask +-- @param Dcs.DCSTasking.Task#Task DCSTask -- @param #DCSStopCondition DCSStopCondition --- @return DCSTask#Task +-- @return Dcs.DCSTasking.Task#Task function CONTROLLABLE:TaskControlled( DCSTask, DCSStopCondition ) self:F2( { DCSTask, DCSStopCondition } ) @@ -290,8 +291,8 @@ end --- Return a Combo Task taking an array of Tasks. -- @param #CONTROLLABLE self --- @param DCSTask#TaskArray DCSTasks Array of @{DCSTask#Task} --- @return DCSTask#Task +-- @param Dcs.DCSTasking.Task#TaskArray DCSTasks Array of @{Dcs.DCSTasking.Task#Task} +-- @return Dcs.DCSTasking.Task#Task function CONTROLLABLE:TaskCombo( DCSTasks ) self:F2( { DCSTasks } ) @@ -310,8 +311,8 @@ end --- Return a WrappedAction Task taking a Command. -- @param #CONTROLLABLE self --- @param DCSCommand#Command DCSCommand --- @return DCSTask#Task +-- @param Dcs.DCSCommand#Command DCSCommand +-- @return Dcs.DCSTasking.Task#Task function CONTROLLABLE:TaskWrappedAction( DCSCommand, Index ) self:F2( { DCSCommand } ) @@ -333,7 +334,7 @@ end --- Executes a command action -- @param #CONTROLLABLE self --- @param DCSCommand#Command DCSCommand +-- @param Dcs.DCSCommand#Command DCSCommand -- @return #CONTROLLABLE self function CONTROLLABLE:SetCommand( DCSCommand ) self:F2( DCSCommand ) @@ -353,7 +354,7 @@ end -- @param #CONTROLLABLE self -- @param #number FromWayPoint -- @param #number ToWayPoint --- @return DCSTask#Task +-- @return Dcs.DCSTasking.Task#Task -- @usage -- --- This test demonstrates the use(s) of the SwitchWayPoint method of the GROUP class. -- HeliGroup = GROUP:FindByName( "Helicopter" ) @@ -384,7 +385,7 @@ end --- Perform stop route command -- @param #CONTROLLABLE self -- @param #boolean StopRoute --- @return DCSTask#Task +-- @return Dcs.DCSTasking.Task#Task function CONTROLLABLE:CommandStopRoute( StopRoute, Index ) self:F2( { StopRoute, Index } ) @@ -405,14 +406,14 @@ end --- (AIR) Attack a Controllable. -- @param #CONTROLLABLE self --- @param Controllable#CONTROLLABLE AttackGroup The Controllable to be attacked. +-- @param Wrapper.Controllable#CONTROLLABLE AttackGroup The Controllable to be attacked. -- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. +-- @param Dcs.DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. -- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. --- @param DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. --- @param DCSTypes#Distance Altitude (optional) Desired attack start altitude. Controllable/aircraft will make its attacks from the altitude. If the altitude is too low or too high to use weapon aircraft/controllable will choose closest altitude to the desired attack start altitude. If the desired altitude is defined controllable/aircraft will not attack from safe altitude. +-- @param Dcs.DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. +-- @param Dcs.DCSTypes#Distance Altitude (optional) Desired attack start altitude. Controllable/aircraft will make its attacks from the altitude. If the altitude is too low or too high to use weapon aircraft/controllable will choose closest altitude to the desired attack start altitude. If the desired altitude is defined controllable/aircraft will not attack from safe altitude. -- @param #boolean AttackQtyLimit (optional) The flag determines how to interpret attackQty parameter. If the flag is true then attackQty is a limit on maximal attack quantity for "AttackControllable" and "AttackUnit" tasks. If the flag is false then attackQty is a desired attack quantity for "Bombing" and "BombingRunway" tasks. --- @return DCSTask#Task The DCS task structure. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. function CONTROLLABLE:TaskAttackGroup( AttackGroup, WeaponType, WeaponExpend, AttackQty, Direction, Altitude, AttackQtyLimit ) self:F2( { self.ControllableName, AttackGroup, WeaponType, WeaponExpend, AttackQty, Direction, Altitude, AttackQtyLimit } ) @@ -463,14 +464,14 @@ end --- (AIR) Attack the Unit. -- @param #CONTROLLABLE self --- @param Unit#UNIT AttackUnit The unit. +-- @param Wrapper.Unit#UNIT AttackUnit The unit. -- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. +-- @param Dcs.DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. -- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. --- @param DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. +-- @param Dcs.DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. -- @param #boolean AttackQtyLimit (optional) The flag determines how to interpret attackQty parameter. If the flag is true then attackQty is a limit on maximal attack quantity for "AttackControllable" and "AttackUnit" tasks. If the flag is false then attackQty is a desired attack quantity for "Bombing" and "BombingRunway" tasks. -- @param #boolean ControllableAttack (optional) Flag indicates that the target must be engaged by all aircrafts of the controllable. Has effect only if the task is assigned to a controllable, not to a single aircraft. --- @return DCSTask#Task The DCS task structure. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. function CONTROLLABLE:TaskAttackUnit( AttackUnit, WeaponType, WeaponExpend, AttackQty, Direction, AttackQtyLimit, ControllableAttack ) self:F2( { self.ControllableName, AttackUnit, WeaponType, WeaponExpend, AttackQty, Direction, AttackQtyLimit, ControllableAttack } ) @@ -507,13 +508,13 @@ end --- (AIR) Delivering weapon at the point on the ground. -- @param #CONTROLLABLE self --- @param DCSTypes#Vec2 Vec2 2D-coordinates of the point to deliver weapon at. +-- @param Dcs.DCSTypes#Vec2 Vec2 2D-coordinates of the point to deliver weapon at. -- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. +-- @param Dcs.DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. -- @param #number AttackQty (optional) Desired quantity of passes. The parameter is not the same in AttackControllable and AttackUnit tasks. --- @param DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. +-- @param Dcs.DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. -- @param #boolean ControllableAttack (optional) Flag indicates that the target must be engaged by all aircrafts of the controllable. Has effect only if the task is assigned to a controllable, not to a single aircraft. --- @return DCSTask#Task The DCS task structure. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. function CONTROLLABLE:TaskBombing( Vec2, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack ) self:F2( { self.ControllableName, Vec2, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack } ) @@ -547,7 +548,7 @@ end --- (AIR) Orbit at a specified position at a specified alititude during a specified duration with a specified speed. -- @param #CONTROLLABLE self --- @param DCSTypes#Vec2 Point The point to hold the position. +-- @param Dcs.DCSTypes#Vec2 Point The point to hold the position. -- @param #number Altitude The altitude to hold the position. -- @param #number Speed The speed flying when holding the position. -- @return #CONTROLLABLE self @@ -625,13 +626,13 @@ end --- (AIR) Attacking the map object (building, structure, e.t.c). -- @param #CONTROLLABLE self --- @param DCSTypes#Vec2 Vec2 2D-coordinates of the point the map object is closest to. The distance between the point and the map object must not be greater than 2000 meters. Object id is not used here because Mission Editor doesn't support map object identificators. +-- @param Dcs.DCSTypes#Vec2 Vec2 2D-coordinates of the point the map object is closest to. The distance between the point and the map object must not be greater than 2000 meters. Object id is not used here because Mission Editor doesn't support map object identificators. -- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. +-- @param Dcs.DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. -- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. --- @param DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. +-- @param Dcs.DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. -- @param #boolean ControllableAttack (optional) Flag indicates that the target must be engaged by all aircrafts of the controllable. Has effect only if the task is assigned to a controllable, not to a single aircraft. --- @return DCSTask#Task The DCS task structure. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. function CONTROLLABLE:TaskAttackMapObject( Vec2, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack ) self:F2( { self.ControllableName, Vec2, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack } ) @@ -666,13 +667,13 @@ end --- (AIR) Delivering weapon on the runway. -- @param #CONTROLLABLE self --- @param Airbase#AIRBASE Airbase Airbase to attack. +-- @param Wrapper.Airbase#AIRBASE Airbase Airbase to attack. -- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. +-- @param Dcs.DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. -- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. --- @param DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. +-- @param Dcs.DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. -- @param #boolean ControllableAttack (optional) Flag indicates that the target must be engaged by all aircrafts of the controllable. Has effect only if the task is assigned to a controllable, not to a single aircraft. --- @return DCSTask#Task The DCS task structure. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. function CONTROLLABLE:TaskBombingRunway( Airbase, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack ) self:F2( { self.ControllableName, Airbase, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack } ) @@ -707,7 +708,7 @@ end --- (AIR) Refueling from the nearest tanker. No parameters. -- @param #CONTROLLABLE self --- @return DCSTask#Task The DCS task structure. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. function CONTROLLABLE:TaskRefueling() self:F2( { self.ControllableName } ) @@ -729,7 +730,7 @@ end --- (AIR HELICOPTER) Landing at the ground. For helicopters only. -- @param #CONTROLLABLE self --- @param DCSTypes#Vec2 Point The point where to land. +-- @param Dcs.DCSTypes#Vec2 Point The point where to land. -- @param #number Duration The duration in seconds to stay on the ground. -- @return #CONTROLLABLE self function CONTROLLABLE:TaskLandAtVec2( Point, Duration ) @@ -766,9 +767,9 @@ function CONTROLLABLE:TaskLandAtVec2( Point, Duration ) return DCSTask end ---- (AIR) Land the controllable at a @{Zone#ZONE_RADIUS). +--- (AIR) Land the controllable at a @{Core.Zone#ZONE_RADIUS). -- @param #CONTROLLABLE self --- @param Zone#ZONE Zone The zone where to land. +-- @param Core.Zone#ZONE Zone The zone where to land. -- @param #number Duration The duration in seconds to stay on the ground. -- @return #CONTROLLABLE self function CONTROLLABLE:TaskLandAtZone( Zone, Duration, RandomPoint ) @@ -793,10 +794,10 @@ end -- The unit / controllable will follow lead unit of another controllable, wingmens of both controllables will continue following their leaders. -- If another controllable is on land the unit / controllable will orbit around. -- @param #CONTROLLABLE self --- @param Controllable#CONTROLLABLE FollowControllable The controllable to be followed. --- @param DCSTypes#Vec3 Vec3 Position of the unit / lead unit of the controllable relative lead unit of another controllable in frame reference oriented by course of lead unit of another controllable. If another controllable is on land the unit / controllable will orbit around. +-- @param Wrapper.Controllable#CONTROLLABLE FollowControllable The controllable to be followed. +-- @param Dcs.DCSTypes#Vec3 Vec3 Position of the unit / lead unit of the controllable relative lead unit of another controllable in frame reference oriented by course of lead unit of another controllable. If another controllable is on land the unit / controllable will orbit around. -- @param #number LastWaypointIndex Detach waypoint of another controllable. Once reached the unit / controllable Follow task is finished. --- @return DCSTask#Task The DCS task structure. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. function CONTROLLABLE:TaskFollow( FollowControllable, Vec3, LastWaypointIndex ) self:F2( { self.ControllableName, FollowControllable, Vec3, LastWaypointIndex } ) @@ -835,12 +836,12 @@ end -- The unit / controllable will follow lead unit of another controllable, wingmens of both controllables will continue following their leaders. -- The unit / controllable will also protect that controllable from threats of specified types. -- @param #CONTROLLABLE self --- @param Controllable#CONTROLLABLE EscortControllable The controllable to be escorted. --- @param DCSTypes#Vec3 Vec3 Position of the unit / lead unit of the controllable relative lead unit of another controllable in frame reference oriented by course of lead unit of another controllable. If another controllable is on land the unit / controllable will orbit around. +-- @param Wrapper.Controllable#CONTROLLABLE EscortControllable The controllable to be escorted. +-- @param Dcs.DCSTypes#Vec3 Vec3 Position of the unit / lead unit of the controllable relative lead unit of another controllable in frame reference oriented by course of lead unit of another controllable. If another controllable is on land the unit / controllable will orbit around. -- @param #number LastWaypointIndex Detach waypoint of another controllable. Once reached the unit / controllable Follow task is finished. -- @param #number EngagementDistanceMax Maximal distance from escorted controllable to threat. If the threat is already engaged by escort escort will disengage if the distance becomes greater than 1.5 * engagementDistMax. --- @param DCSTypes#AttributeNameArray TargetTypes Array of AttributeName that is contains threat categories allowed to engage. --- @return DCSTask#Task The DCS task structure. +-- @param Dcs.DCSTypes#AttributeNameArray TargetTypes Array of AttributeName that is contains threat categories allowed to engage. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. function CONTROLLABLE:TaskEscort( FollowControllable, Vec3, LastWaypointIndex, EngagementDistance, TargetTypes ) self:F2( { self.ControllableName, FollowControllable, Vec3, LastWaypointIndex, EngagementDistance, TargetTypes } ) @@ -882,9 +883,9 @@ end --- (GROUND) Fire at a VEC2 point until ammunition is finished. -- @param #CONTROLLABLE self --- @param DCSTypes#Vec2 Vec2 The point to fire at. --- @param DCSTypes#Distance Radius The radius of the zone to deploy the fire at. --- @return DCSTask#Task The DCS task structure. +-- @param Dcs.DCSTypes#Vec2 Vec2 The point to fire at. +-- @param Dcs.DCSTypes#Distance Radius The radius of the zone to deploy the fire at. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius ) self:F2( { self.ControllableName, Vec2, Radius } ) @@ -910,7 +911,7 @@ end --- (GROUND) Hold ground controllable from moving. -- @param #CONTROLLABLE self --- @return DCSTask#Task The DCS task structure. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. function CONTROLLABLE:TaskHold() self:F2( { self.ControllableName } ) @@ -937,11 +938,11 @@ end -- The killer is player-controlled allied CAS-aircraft that is in contact with the FAC. -- If the task is assigned to the controllable lead unit will be a FAC. -- @param #CONTROLLABLE self --- @param Controllable#CONTROLLABLE AttackGroup Target CONTROLLABLE. +-- @param Wrapper.Controllable#CONTROLLABLE AttackGroup Target CONTROLLABLE. -- @param #number WeaponType Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param DCSTypes#AI.Task.Designation Designation (optional) Designation type. +-- @param Dcs.DCSTypes#AI.Task.Designation Designation (optional) Designation type. -- @param #boolean Datalink (optional) Allows to use datalink to send the target information to attack aircraft. Enabled by default. --- @return DCSTask#Task The DCS task structure. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. function CONTROLLABLE:TaskFAC_AttackGroup( AttackGroup, WeaponType, Designation, Datalink ) self:F2( { self.ControllableName, AttackGroup, WeaponType, Designation, Datalink } ) @@ -969,14 +970,14 @@ function CONTROLLABLE:TaskFAC_AttackGroup( AttackGroup, WeaponType, Designation, return DCSTask end --- EN-ROUTE TASKS FOR AIRBORNE CONTROLLABLES +-- EN-ACT_ROUTE TASKS FOR AIRBORNE CONTROLLABLES --- (AIR) Engaging targets of defined types. -- @param #CONTROLLABLE self --- @param DCSTypes#Distance Distance Maximal distance from the target to a route leg. If the target is on a greater distance it will be ignored. --- @param DCSTypes#AttributeNameArray TargetTypes Array of target categories allowed to engage. +-- @param Dcs.DCSTypes#Distance Distance Maximal distance from the target to a route leg. If the target is on a greater distance it will be ignored. +-- @param Dcs.DCSTypes#AttributeNameArray TargetTypes Array of target categories allowed to engage. -- @param #number Priority All enroute tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. --- @return DCSTask#Task The DCS task structure. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. function CONTROLLABLE:EnRouteTaskEngageTargets( Distance, TargetTypes, Priority ) self:F2( { self.ControllableName, Distance, TargetTypes, Priority } ) @@ -1006,11 +1007,11 @@ end --- (AIR) Engaging a targets of defined types at circle-shaped zone. -- @param #CONTROLLABLE self --- @param DCSTypes#Vec2 Vec2 2D-coordinates of the zone. --- @param DCSTypes#Distance Radius Radius of the zone. --- @param DCSTypes#AttributeNameArray TargetTypes Array of target categories allowed to engage. +-- @param Dcs.DCSTypes#Vec2 Vec2 2D-coordinates of the zone. +-- @param Dcs.DCSTypes#Distance Radius Radius of the zone. +-- @param Dcs.DCSTypes#AttributeNameArray TargetTypes Array of target categories allowed to engage. -- @param #number Priority All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. --- @return DCSTask#Task The DCS task structure. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. function CONTROLLABLE:EnRouteTaskEngageTargets( Vec2, Radius, TargetTypes, Priority ) self:F2( { self.ControllableName, Vec2, Radius, TargetTypes, Priority } ) @@ -1041,15 +1042,15 @@ end --- (AIR) Engaging a controllable. The task does not assign the target controllable to the unit/controllable to attack now; it just allows the unit/controllable to engage the target controllable as well as other assigned targets. -- @param #CONTROLLABLE self --- @param Controllable#CONTROLLABLE AttackGroup The Controllable to be attacked. +-- @param Wrapper.Controllable#CONTROLLABLE AttackGroup The Controllable to be attacked. -- @param #number Priority All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. -- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. +-- @param Dcs.DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. -- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. --- @param DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. --- @param DCSTypes#Distance Altitude (optional) Desired attack start altitude. Controllable/aircraft will make its attacks from the altitude. If the altitude is too low or too high to use weapon aircraft/controllable will choose closest altitude to the desired attack start altitude. If the desired altitude is defined controllable/aircraft will not attack from safe altitude. +-- @param Dcs.DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. +-- @param Dcs.DCSTypes#Distance Altitude (optional) Desired attack start altitude. Controllable/aircraft will make its attacks from the altitude. If the altitude is too low or too high to use weapon aircraft/controllable will choose closest altitude to the desired attack start altitude. If the desired altitude is defined controllable/aircraft will not attack from safe altitude. -- @param #boolean AttackQtyLimit (optional) The flag determines how to interpret attackQty parameter. If the flag is true then attackQty is a limit on maximal attack quantity for "AttackControllable" and "AttackUnit" tasks. If the flag is false then attackQty is a desired attack quantity for "Bombing" and "BombingRunway" tasks. --- @return DCSTask#Task The DCS task structure. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. function CONTROLLABLE:EnRouteTaskEngageGroup( AttackGroup, Priority, WeaponType, WeaponExpend, AttackQty, Direction, Altitude, AttackQtyLimit ) self:F2( { self.ControllableName, AttackGroup, Priority, WeaponType, WeaponExpend, AttackQty, Direction, Altitude, AttackQtyLimit } ) @@ -1102,15 +1103,15 @@ end --- (AIR) Attack the Unit. -- @param #CONTROLLABLE self --- @param Unit#UNIT AttackUnit The UNIT. +-- @param Wrapper.Unit#UNIT AttackUnit The UNIT. -- @param #number Priority All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. -- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. +-- @param Dcs.DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. -- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. --- @param DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. +-- @param Dcs.DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. -- @param #boolean AttackQtyLimit (optional) The flag determines how to interpret attackQty parameter. If the flag is true then attackQty is a limit on maximal attack quantity for "AttackControllable" and "AttackUnit" tasks. If the flag is false then attackQty is a desired attack quantity for "Bombing" and "BombingRunway" tasks. -- @param #boolean ControllableAttack (optional) Flag indicates that the target must be engaged by all aircrafts of the controllable. Has effect only if the task is assigned to a controllable, not to a single aircraft. --- @return DCSTask#Task The DCS task structure. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. function CONTROLLABLE:EnRouteTaskEngageUnit( AttackUnit, Priority, WeaponType, WeaponExpend, AttackQty, Direction, AttackQtyLimit, ControllableAttack ) self:F2( { self.ControllableName, AttackUnit, Priority, WeaponType, WeaponExpend, AttackQty, Direction, AttackQtyLimit, ControllableAttack } ) @@ -1150,7 +1151,7 @@ end --- (AIR) Aircraft will act as an AWACS for friendly units (will provide them with information about contacts). No parameters. -- @param #CONTROLLABLE self --- @return DCSTask#Task The DCS task structure. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. function CONTROLLABLE:EnRouteTaskAWACS( ) self:F2( { self.ControllableName } ) @@ -1173,7 +1174,7 @@ end --- (AIR) Aircraft will act as a tanker for friendly units. No parameters. -- @param #CONTROLLABLE self --- @return DCSTask#Task The DCS task structure. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. function CONTROLLABLE:EnRouteTaskTanker( ) self:F2( { self.ControllableName } ) @@ -1198,7 +1199,7 @@ end --- (GROUND) Ground unit (EW-radar) will act as an EWR for friendly units (will provide them with information about contacts). No parameters. -- @param #CONTROLLABLE self --- @return DCSTask#Task The DCS task structure. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. function CONTROLLABLE:EnRouteTaskEWR( ) self:F2( { self.ControllableName } ) @@ -1225,12 +1226,12 @@ end -- The killer is player-controlled allied CAS-aircraft that is in contact with the FAC. -- If the task is assigned to the controllable lead unit will be a FAC. -- @param #CONTROLLABLE self --- @param Controllable#CONTROLLABLE AttackGroup Target CONTROLLABLE. +-- @param Wrapper.Controllable#CONTROLLABLE AttackGroup Target CONTROLLABLE. -- @param #number Priority All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. -- @param #number WeaponType Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param DCSTypes#AI.Task.Designation Designation (optional) Designation type. +-- @param Dcs.DCSTypes#AI.Task.Designation Designation (optional) Designation type. -- @param #boolean Datalink (optional) Allows to use datalink to send the target information to attack aircraft. Enabled by default. --- @return DCSTask#Task The DCS task structure. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. function CONTROLLABLE:EnRouteTaskFAC_EngageGroup( AttackGroup, Priority, WeaponType, Designation, Datalink ) self:F2( { self.ControllableName, AttackGroup, WeaponType, Priority, Designation, Datalink } ) @@ -1265,9 +1266,9 @@ end -- The killer is player-controlled allied CAS-aircraft that is in contact with the FAC. -- If the task is assigned to the controllable lead unit will be a FAC. -- @param #CONTROLLABLE self --- @param DCSTypes#Distance Radius The maximal distance from the FAC to a target. +-- @param Dcs.DCSTypes#Distance Radius The maximal distance from the FAC to a target. -- @param #number Priority All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. --- @return DCSTask#Task The DCS task structure. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. function CONTROLLABLE:EnRouteTaskFAC( Radius, Priority ) self:F2( { self.ControllableName, Radius, Priority } ) @@ -1296,10 +1297,10 @@ end --- (AIR) Move the controllable to a Vec2 Point, wait for a defined duration and embark a controllable. -- @param #CONTROLLABLE self --- @param DCSTypes#Vec2 Point The point where to wait. +-- @param Dcs.DCSTypes#Vec2 Point The point where to wait. -- @param #number Duration The duration in seconds to wait. -- @param #CONTROLLABLE EmbarkingControllable The controllable to be embarked. --- @return DCSTask#Task The DCS task structure +-- @return Dcs.DCSTasking.Task#Task The DCS task structure function CONTROLLABLE:TaskEmbarking( Point, Duration, EmbarkingControllable ) self:F2( { self.ControllableName, Point, Duration, EmbarkingControllable.DCSControllable } ) @@ -1323,13 +1324,13 @@ end --- Move to a defined Vec2 Point, and embark to a controllable when arrived within a defined Radius. -- @param #CONTROLLABLE self --- @param DCSTypes#Vec2 Point The point where to wait. +-- @param Dcs.DCSTypes#Vec2 Point The point where to wait. -- @param #number Radius The radius of the embarking zone around the Point. --- @return DCSTask#Task The DCS task structure. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. function CONTROLLABLE:TaskEmbarkToTransport( Point, Radius ) self:F2( { self.ControllableName, Point, Radius } ) - local DCSTask --DCSTask#Task + local DCSTask --Dcs.DCSTasking.Task#Task DCSTask = { id = 'EmbarkToTransport', params = { x = Point.x, y = Point.y, @@ -1346,7 +1347,7 @@ end --- (AIR + GROUND) Return a mission task from a mission template. -- @param #CONTROLLABLE self -- @param #table TaskMission A table containing the mission task. --- @return DCSTask#Task +-- @return Dcs.DCSTasking.Task#Task function CONTROLLABLE:TaskMission( TaskMission ) self:F2( Points ) @@ -1360,7 +1361,7 @@ end --- Return a Misson task to follow a given route defined by Points. -- @param #CONTROLLABLE self -- @param #table Points A table of route points. --- @return DCSTask#Task +-- @return Dcs.DCSTasking.Task#Task function CONTROLLABLE:TaskRoute( Points ) self:F2( Points ) @@ -1373,7 +1374,7 @@ end --- (AIR + GROUND) Make the Controllable move to fly to a given point. -- @param #CONTROLLABLE self --- @param DCSTypes#Vec3 Point The destination point in Vec3 format. +-- @param Dcs.DCSTypes#Vec3 Point The destination point in Vec3 format. -- @param #number Speed The speed to travel. -- @return #CONTROLLABLE self function CONTROLLABLE:TaskRouteToVec2( Point, Speed ) @@ -1424,7 +1425,7 @@ end --- (AIR + GROUND) Make the Controllable move to a given point. -- @param #CONTROLLABLE self --- @param DCSTypes#Vec3 Point The destination point in Vec3 format. +-- @param Dcs.DCSTypes#Vec3 Point The destination point in Vec3 format. -- @param #number Speed The speed to travel. -- @return #CONTROLLABLE self function CONTROLLABLE:TaskRouteToVec3( Point, Speed ) @@ -1507,7 +1508,7 @@ end -- A speed can be given in km/h. -- A given formation can be given. -- @param #CONTROLLABLE self --- @param Zone#ZONE Zone The zone where to route to. +-- @param Core.Zone#ZONE Zone The zone where to route to. -- @param #boolean Randomize Defines whether to target point gets randomized within the Zone. -- @param #number Speed The speed. -- @param Base#FORMATION Formation The formation string. @@ -1565,11 +1566,11 @@ function CONTROLLABLE:TaskRouteToZone( Zone, Randomize, Speed, Formation ) return nil end ---- (AIR) Return the Controllable to an @{Airbase#AIRBASE} +--- (AIR) Return the Controllable to an @{Wrapper.Airbase#AIRBASE} -- A speed can be given in km/h. -- A given formation can be given. -- @param #CONTROLLABLE self --- @param Airbase#AIRBASE ReturnAirbase The @{Airbase#AIRBASE} to return to. +-- @param Wrapper.Airbase#AIRBASE ReturnAirbase The @{Wrapper.Airbase#AIRBASE} to return to. -- @param #number Speed (optional) The speed. -- @return #string The route function CONTROLLABLE:RouteReturnToAirbase( ReturnAirbase, Speed ) @@ -1689,7 +1690,7 @@ function CONTROLLABLE:GetTaskRoute() return routines.utils.deepCopy( _DATABASE.Templates.Controllables[self.ControllableName].Template.route.points ) end ---- Return the route of a controllable by using the @{Database#DATABASE} class. +--- Return the route of a controllable by using the @{Core.Database#DATABASE} class. -- @param #CONTROLLABLE self -- @param #number Begin The route point from where the copy will start. The base route point is 0. -- @param #number End The route point where the copy will end. The End point is the last point - the End point. The last point has base 0. @@ -1744,7 +1745,7 @@ end --- Return the detected targets of the controllable. -- The optional parametes specify the detection methods that can be applied. -- If no detection method is given, the detection will use all the available methods by default. --- @param Controllable#CONTROLLABLE self +-- @param Wrapper.Controllable#CONTROLLABLE self -- @param #boolean DetectVisual (optional) -- @param #boolean DetectOptical (optional) -- @param #boolean DetectRadar (optional) @@ -1813,8 +1814,8 @@ function CONTROLLABLE:OptionROEHoldFirePossible() end --- Holding weapons. --- @param Controllable#CONTROLLABLE self --- @return Controllable#CONTROLLABLE self +-- @param Wrapper.Controllable#CONTROLLABLE self +-- @return Wrapper.Controllable#CONTROLLABLE self function CONTROLLABLE:OptionROEHoldFire() self:F2( { self.ControllableName } ) @@ -2114,7 +2115,7 @@ function CONTROLLABLE:OptionROTVertical() end --- Retrieve the controllable mission and allow to place function hooks within the mission waypoint plan. --- Use the method @{Controllable#CONTROLLABLE:WayPointFunction} to define the hook functions for specific waypoints. +-- Use the method @{Wrapper.Controllable#CONTROLLABLE:WayPointFunction} to define the hook functions for specific waypoints. -- Use the method @{Controllable@CONTROLLABLE:WayPointExecute) to start the execution of the new mission plan. -- Note that when WayPointInitialize is called, the Mission of the controllable is RESTARTED! -- @param #CONTROLLABLE self @@ -2154,7 +2155,7 @@ function CONTROLLABLE:TaskFunction( WayPoint, WayPointIndex, FunctionString, Fun local DCSTask local DCSScript = {} - DCSScript[#DCSScript+1] = "local MissionControllable = CONTROLLABLE:Find( ... ) " + DCSScript[#DCSScript+1] = "local MissionControllable = GROUP:Find( ... ) " if FunctionArguments and #FunctionArguments > 0 then DCSScript[#DCSScript+1] = FunctionString .. "( MissionControllable, " .. table.concat( FunctionArguments, "," ) .. ")" @@ -2202,118 +2203,4 @@ end -- Message APIs ---- Returns a message with the callsign embedded (if there is one). --- @param #CONTROLLABLE self --- @param #string Message The message text --- @param DCSTypes#Duration Duration The duration of the message. --- @return Message#MESSAGE -function CONTROLLABLE:GetMessage( Message, Duration ) - - local DCSObject = self:GetDCSObject() - if DCSObject then - return MESSAGE:New( Message, Duration, self:GetCallsign() .. " (" .. self:GetTypeName() .. ")" ) - end - - return nil -end - ---- Send a message to all coalitions. --- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. --- @param #CONTROLLABLE self --- @param #string Message The message text --- @param DCSTypes#Duration Duration The duration of the message. -function CONTROLLABLE:MessageToAll( Message, Duration ) - self:F2( { Message, Duration } ) - - local DCSObject = self:GetDCSObject() - if DCSObject then - self:GetMessage( Message, Duration ):ToAll() - end - - return nil -end - ---- Send a message to the red coalition. --- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. --- @param #CONTROLLABLE self --- @param #string Message The message text --- @param DCSTYpes#Duration Duration The duration of the message. -function CONTROLLABLE:MessageToRed( Message, Duration ) - self:F2( { Message, Duration } ) - - local DCSObject = self:GetDCSObject() - if DCSObject then - self:GetMessage( Message, Duration ):ToRed() - end - - return nil -end - ---- Send a message to the blue coalition. --- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. --- @param #CONTROLLABLE self --- @param #string Message The message text --- @param DCSTypes#Duration Duration The duration of the message. -function CONTROLLABLE:MessageToBlue( Message, Duration ) - self:F2( { Message, Duration } ) - - local DCSObject = self:GetDCSObject() - if DCSObject then - self:GetMessage( Message, Duration ):ToBlue() - end - - return nil -end - ---- Send a message to a client. --- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. --- @param #CONTROLLABLE self --- @param #string Message The message text --- @param DCSTypes#Duration Duration The duration of the message. --- @param Client#CLIENT Client The client object receiving the message. -function CONTROLLABLE:MessageToClient( Message, Duration, Client ) - self:F2( { Message, Duration } ) - - local DCSObject = self:GetDCSObject() - if DCSObject then - self:GetMessage( Message, Duration ):ToClient( Client ) - end - - return nil -end - ---- Send a message to a @{Group}. --- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. --- @param #CONTROLLABLE self --- @param #string Message The message text --- @param DCSTypes#Duration Duration The duration of the message. --- @param Group#GROUP MessageGroup The GROUP object receiving the message. -function CONTROLLABLE:MessageToGroup( Message, Duration, MessageGroup ) - self:F2( { Message, Duration } ) - - local DCSObject = self:GetDCSObject() - if DCSObject then - if DCSObject:isExist() then - self:GetMessage( Message, Duration ):ToGroup( MessageGroup ) - end - end - - return nil -end - ---- Send a message to the players in the @{Group}. --- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. --- @param #CONTROLLABLE self --- @param #string Message The message text --- @param DCSTypes#Duration Duration The duration of the message. -function CONTROLLABLE:Message( Message, Duration ) - self:F2( { Message, Duration } ) - - local DCSObject = self:GetDCSObject() - if DCSObject then - self:GetMessage( Message, Duration ):ToGroup( self ) - end - - return nil -end diff --git a/Moose Development/Moose/Group.lua b/Moose Development/Moose/Wrapper/Group.lua similarity index 72% rename from Moose Development/Moose/Group.lua rename to Moose Development/Moose/Wrapper/Group.lua index 9e3a82bbb..c547eca2d 100644 --- a/Moose Development/Moose/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -1,8 +1,8 @@ --- This module contains the GROUP class. -- --- 1) @{Group#GROUP} class, extends @{Controllable#CONTROLLABLE} +-- 1) @{Wrapper.Group#GROUP} class, extends @{Wrapper.Controllable#CONTROLLABLE} -- ============================================================= --- The @{Group#GROUP} class is a wrapper class to handle the DCS Group objects: +-- The @{Wrapper.Group#GROUP} class is a wrapper class to handle the DCS Group objects: -- -- * Support all DCS Group APIs. -- * Enhance with Group specific APIs not in the DCS Group API set. @@ -32,7 +32,7 @@ -- ----------------------- -- Several group task methods are available that help you to prepare tasks. -- These methods return a string consisting of the task description, which can then be given to either a --- @{Controllable#CONTROLLABLE.PushTask} or @{Controllable#CONTROLLABLE.SetTask} method to assign the task to the GROUP. +-- @{Wrapper.Controllable#CONTROLLABLE.PushTask} or @{Wrapper.Controllable#CONTROLLABLE.SetTask} method to assign the task to the GROUP. -- Tasks are specific for the category of the GROUP, more specific, for AIR, GROUND or AIR and GROUND. -- Each task description where applicable indicates for which group category the task is valid. -- There are 2 main subdivisions of tasks: Assigned tasks and EnRoute tasks. @@ -44,63 +44,63 @@ -- -- Find below a list of the **assigned task** methods: -- --- * @{Controllable#CONTROLLABLE.TaskAttackGroup}: (AIR) Attack a Group. --- * @{Controllable#CONTROLLABLE.TaskAttackMapObject}: (AIR) Attacking the map object (building, structure, e.t.c). --- * @{Controllable#CONTROLLABLE.TaskAttackUnit}: (AIR) Attack the Unit. --- * @{Controllable#CONTROLLABLE.TaskBombing}: (Controllable#CONTROLLABLEDelivering weapon at the point on the ground. --- * @{Controllable#CONTROLLABLE.TaskBombingRunway}: (AIR) Delivering weapon on the runway. --- * @{Controllable#CONTROLLABLE.TaskEmbarking}: (AIR) Move the group to a Vec2 Point, wait for a defined duration and embark a group. --- * @{Controllable#CONTROLLABLE.TaskEmbarkToTransport}: (GROUND) Embark to a Transport landed at a location. --- * @{Controllable#CONTROLLABLE.TaskEscort}: (AIR) Escort another airborne group. --- * @{Controllable#CONTROLLABLE.TaskFAC_AttackGroup}: (AIR + GROUND) The task makes the group/unit a FAC and orders the FAC to control the target (enemy ground group) destruction. --- * @{Controllable#CONTROLLABLE.TaskFireAtPoint}: (GROUND) Fire at a VEC2 point until ammunition is finished. --- * @{Controllable#CONTROLLABLE.TaskFollow}: (AIR) Following another airborne group. --- * @{Controllable#CONTROLLABLE.TaskHold}: (GROUND) Hold ground group from moving. --- * @{Controllable#CONTROLLABLE.TaskHoldPosition}: (AIR) Hold position at the current position of the first unit of the group. --- * @{Controllable#CONTROLLABLE.TaskLand}: (AIR HELICOPTER) Landing at the ground. For helicopters only. --- * @{Controllable#CONTROLLABLE.TaskLandAtZone}: (AIR) Land the group at a @{Zone#ZONE_RADIUS). --- * @{Controllable#CONTROLLABLE.TaskOrbitCircle}: (AIR) Orbit at the current position of the first unit of the group at a specified alititude. --- * @{Controllable#CONTROLLABLE.TaskOrbitCircleAtVec2}: (AIR) Orbit at a specified position at a specified alititude during a specified duration with a specified speed. --- * @{Controllable#CONTROLLABLE.TaskRefueling}: (AIR) Refueling from the nearest tanker. No parameters. --- * @{Controllable#CONTROLLABLE.TaskRoute}: (AIR + GROUND) Return a Misson task to follow a given route defined by Points. --- * @{Controllable#CONTROLLABLE.TaskRouteToVec2}: (AIR + GROUND) Make the Group move to a given point. --- * @{Controllable#CONTROLLABLE.TaskRouteToVec3}: (AIR + GROUND) Make the Group move to a given point. --- * @{Controllable#CONTROLLABLE.TaskRouteToZone}: (AIR + GROUND) Route the group to a given zone. --- * @{Controllable#CONTROLLABLE.TaskReturnToBase}: (AIR) Route the group to an airbase. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskAttackGroup}: (AIR) Attack a Group. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskAttackMapObject}: (AIR) Attacking the map object (building, structure, e.t.c). +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskAttackUnit}: (AIR) Attack the Unit. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskBombing}: (Wrapper.Controllable#CONTROLLABLEDelivering weapon at the point on the ground. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskBombingRunway}: (AIR) Delivering weapon on the runway. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskEmbarking}: (AIR) Move the group to a Vec2 Point, wait for a defined duration and embark a group. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskEmbarkToTransport}: (GROUND) Embark to a Transport landed at a location. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskEscort}: (AIR) Escort another airborne group. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskFAC_AttackGroup}: (AIR + GROUND) The task makes the group/unit a FAC and orders the FAC to control the target (enemy ground group) destruction. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskFireAtPoint}: (GROUND) Fire at a VEC2 point until ammunition is finished. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskFollow}: (AIR) Following another airborne group. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskHold}: (GROUND) Hold ground group from moving. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskHoldPosition}: (AIR) Hold position at the current position of the first unit of the group. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskLand}: (AIR HELICOPTER) Landing at the ground. For helicopters only. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskLandAtZone}: (AIR) Land the group at a @{Core.Zone#ZONE_RADIUS). +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskOrbitCircle}: (AIR) Orbit at the current position of the first unit of the group at a specified alititude. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskOrbitCircleAtVec2}: (AIR) Orbit at a specified position at a specified alititude during a specified duration with a specified speed. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskRefueling}: (AIR) Refueling from the nearest tanker. No parameters. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskRoute}: (AIR + GROUND) Return a Misson task to follow a given route defined by Points. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskRouteToVec2}: (AIR + GROUND) Make the Group move to a given point. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskRouteToVec3}: (AIR + GROUND) Make the Group move to a given point. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskRouteToZone}: (AIR + GROUND) Route the group to a given zone. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskReturnToBase}: (AIR) Route the group to an airbase. -- -- ### 1.2.2) EnRoute task methods -- -- EnRoute tasks require the targets of the task need to be detected by the group (using its sensors) before the task can be executed: -- --- * @{Controllable#CONTROLLABLE.EnRouteTaskAWACS}: (AIR) Aircraft will act as an AWACS for friendly units (will provide them with information about contacts). No parameters. --- * @{Controllable#CONTROLLABLE.EnRouteTaskEngageGroup}: (AIR) Engaging a group. The task does not assign the target group to the unit/group to attack now; it just allows the unit/group to engage the target group as well as other assigned targets. --- * @{Controllable#CONTROLLABLE.EnRouteTaskEngageTargets}: (AIR) Engaging targets of defined types. --- * @{Controllable#CONTROLLABLE.EnRouteTaskEWR}: (AIR) Attack the Unit. --- * @{Controllable#CONTROLLABLE.EnRouteTaskFAC}: (AIR + GROUND) The task makes the group/unit a FAC and lets the FAC to choose a targets (enemy ground group) around as well as other assigned targets. --- * @{Controllable#CONTROLLABLE.EnRouteTaskFAC_EngageGroup}: (AIR + GROUND) The task makes the group/unit a FAC and lets the FAC to choose the target (enemy ground group) as well as other assigned targets. --- * @{Controllable#CONTROLLABLE.EnRouteTaskTanker}: (AIR) Aircraft will act as a tanker for friendly units. No parameters. +-- * @{Wrapper.Controllable#CONTROLLABLE.EnRouteTaskAWACS}: (AIR) Aircraft will act as an AWACS for friendly units (will provide them with information about contacts). No parameters. +-- * @{Wrapper.Controllable#CONTROLLABLE.EnRouteTaskEngageGroup}: (AIR) Engaging a group. The task does not assign the target group to the unit/group to attack now; it just allows the unit/group to engage the target group as well as other assigned targets. +-- * @{Wrapper.Controllable#CONTROLLABLE.EnRouteTaskEngageTargets}: (AIR) Engaging targets of defined types. +-- * @{Wrapper.Controllable#CONTROLLABLE.EnRouteTaskEWR}: (AIR) Attack the Unit. +-- * @{Wrapper.Controllable#CONTROLLABLE.EnRouteTaskFAC}: (AIR + GROUND) The task makes the group/unit a FAC and lets the FAC to choose a targets (enemy ground group) around as well as other assigned targets. +-- * @{Wrapper.Controllable#CONTROLLABLE.EnRouteTaskFAC_EngageGroup}: (AIR + GROUND) The task makes the group/unit a FAC and lets the FAC to choose the target (enemy ground group) as well as other assigned targets. +-- * @{Wrapper.Controllable#CONTROLLABLE.EnRouteTaskTanker}: (AIR) Aircraft will act as a tanker for friendly units. No parameters. -- -- ### 1.2.3) Preparation task methods -- -- There are certain task methods that allow to tailor the task behaviour: -- --- * @{Controllable#CONTROLLABLE.TaskWrappedAction}: Return a WrappedAction Task taking a Command. --- * @{Controllable#CONTROLLABLE.TaskCombo}: Return a Combo Task taking an array of Tasks. --- * @{Controllable#CONTROLLABLE.TaskCondition}: Return a condition section for a controlled task. --- * @{Controllable#CONTROLLABLE.TaskControlled}: Return a Controlled Task taking a Task and a TaskCondition. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskWrappedAction}: Return a WrappedAction Task taking a Command. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskCombo}: Return a Combo Task taking an array of Tasks. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskCondition}: Return a condition section for a controlled task. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskControlled}: Return a Controlled Task taking a Task and a TaskCondition. -- -- ### 1.2.4) Obtain the mission from group templates -- -- Group templates contain complete mission descriptions. Sometimes you want to copy a complete mission from a group and assign it to another: -- --- * @{Controllable#CONTROLLABLE.TaskMission}: (AIR + GROUND) Return a mission task from a mission template. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskMission}: (AIR + GROUND) Return a mission task from a mission template. -- -- 1.3) GROUP Command methods -- -------------------------- --- Group **command methods** prepare the execution of commands using the @{Controllable#CONTROLLABLE.SetCommand} method: +-- Group **command methods** prepare the execution of commands using the @{Wrapper.Controllable#CONTROLLABLE.SetCommand} method: -- --- * @{Controllable#CONTROLLABLE.CommandDoScript}: Do Script command. --- * @{Controllable#CONTROLLABLE.CommandSwitchWayPoint}: Perform a switch waypoint command. +-- * @{Wrapper.Controllable#CONTROLLABLE.CommandDoScript}: Do Script command. +-- * @{Wrapper.Controllable#CONTROLLABLE.CommandSwitchWayPoint}: Perform a switch waypoint command. -- -- 1.4) GROUP Option methods -- ------------------------- @@ -108,31 +108,31 @@ -- -- ### 1.4.1) Rule of Engagement: -- --- * @{Controllable#CONTROLLABLE.OptionROEWeaponFree} --- * @{Controllable#CONTROLLABLE.OptionROEOpenFire} --- * @{Controllable#CONTROLLABLE.OptionROEReturnFire} --- * @{Controllable#CONTROLLABLE.OptionROEEvadeFire} +-- * @{Wrapper.Controllable#CONTROLLABLE.OptionROEWeaponFree} +-- * @{Wrapper.Controllable#CONTROLLABLE.OptionROEOpenFire} +-- * @{Wrapper.Controllable#CONTROLLABLE.OptionROEReturnFire} +-- * @{Wrapper.Controllable#CONTROLLABLE.OptionROEEvadeFire} -- -- To check whether an ROE option is valid for a specific group, use: -- --- * @{Controllable#CONTROLLABLE.OptionROEWeaponFreePossible} --- * @{Controllable#CONTROLLABLE.OptionROEOpenFirePossible} --- * @{Controllable#CONTROLLABLE.OptionROEReturnFirePossible} --- * @{Controllable#CONTROLLABLE.OptionROEEvadeFirePossible} +-- * @{Wrapper.Controllable#CONTROLLABLE.OptionROEWeaponFreePossible} +-- * @{Wrapper.Controllable#CONTROLLABLE.OptionROEOpenFirePossible} +-- * @{Wrapper.Controllable#CONTROLLABLE.OptionROEReturnFirePossible} +-- * @{Wrapper.Controllable#CONTROLLABLE.OptionROEEvadeFirePossible} -- -- ### 1.4.2) Rule on thread: -- --- * @{Controllable#CONTROLLABLE.OptionROTNoReaction} --- * @{Controllable#CONTROLLABLE.OptionROTPassiveDefense} --- * @{Controllable#CONTROLLABLE.OptionROTEvadeFire} --- * @{Controllable#CONTROLLABLE.OptionROTVertical} +-- * @{Wrapper.Controllable#CONTROLLABLE.OptionROTNoReaction} +-- * @{Wrapper.Controllable#CONTROLLABLE.OptionROTPassiveDefense} +-- * @{Wrapper.Controllable#CONTROLLABLE.OptionROTEvadeFire} +-- * @{Wrapper.Controllable#CONTROLLABLE.OptionROTVertical} -- -- To test whether an ROT option is valid for a specific group, use: -- --- * @{Controllable#CONTROLLABLE.OptionROTNoReactionPossible} --- * @{Controllable#CONTROLLABLE.OptionROTPassiveDefensePossible} --- * @{Controllable#CONTROLLABLE.OptionROTEvadeFirePossible} --- * @{Controllable#CONTROLLABLE.OptionROTVerticalPossible} +-- * @{Wrapper.Controllable#CONTROLLABLE.OptionROTNoReactionPossible} +-- * @{Wrapper.Controllable#CONTROLLABLE.OptionROTPassiveDefensePossible} +-- * @{Wrapper.Controllable#CONTROLLABLE.OptionROTEvadeFirePossible} +-- * @{Wrapper.Controllable#CONTROLLABLE.OptionROTVerticalPossible} -- -- 1.5) GROUP Zone validation methods -- ---------------------------------- @@ -143,14 +143,14 @@ -- * @{#GROUP.IsPartlyInZone}: Returns true if some units of the group are within a @{Zone}. -- * @{#GROUP.IsNotInZone}: Returns true if none of the group units of the group are within a @{Zone}. -- --- The zone can be of any @{Zone} class derived from @{Zone#ZONE_BASE}. So, these methods are polymorphic to the zones tested on. +-- The zone can be of any @{Zone} class derived from @{Core.Zone#ZONE_BASE}. So, these methods are polymorphic to the zones tested on. -- -- @module Group -- @author FlightControl --- The GROUP class -- @type GROUP --- @extends Controllable#CONTROLLABLE +-- @extends Wrapper.Controllable#CONTROLLABLE -- @field #string GroupName The name of the group. GROUP = { ClassName = "GROUP", @@ -158,7 +158,7 @@ GROUP = { --- Create a new GROUP from a DCSGroup -- @param #GROUP self --- @param DCSGroup#Group GroupName The DCS Group name +-- @param Dcs.DCSWrapper.Group#Group GroupName The DCS Group name -- @return #GROUP self function GROUP:Register( GroupName ) local self = BASE:Inherit( self, CONTROLLABLE:New( GroupName ) ) @@ -171,11 +171,11 @@ end --- Find the GROUP wrapper class instance using the DCS Group. -- @param #GROUP self --- @param DCSGroup#Group DCSGroup The DCS Group. +-- @param Dcs.DCSWrapper.Group#Group DCSGroup The DCS Group. -- @return #GROUP The GROUP. function GROUP:Find( DCSGroup ) - local GroupName = DCSGroup:getName() -- Group#GROUP + local GroupName = DCSGroup:getName() -- Wrapper.Group#GROUP local GroupFound = _DATABASE:FindGroup( GroupName ) return GroupFound end @@ -194,7 +194,7 @@ end --- Returns the DCS Group. -- @param #GROUP self --- @return DCSGroup#Group The DCS Group. +-- @return Dcs.DCSWrapper.Group#Group The DCS Group. function GROUP:GetDCSObject() local DCSGroup = Group.getByName( self.GroupName ) @@ -246,7 +246,7 @@ end --- Returns category of the DCS Group. -- @param #GROUP self --- @return DCSGroup#Group.Category The category ID +-- @return Dcs.DCSWrapper.Group#Group.Category The category ID function GROUP:GetCategory() self:F2( self.GroupName ) @@ -286,7 +286,7 @@ end --- Returns the coalition of the DCS Group. -- @param #GROUP self --- @return DCSCoalitionObject#coalition.side The coalition side of the DCS Group. +-- @return Dcs.DCSCoalitionWrapper.Object#coalition.side The coalition side of the DCS Group. function GROUP:GetCoalition() self:F2( self.GroupName ) @@ -302,7 +302,7 @@ end --- Returns the country of the DCS Group. -- @param #GROUP self --- @return DCScountry#country.id The country identifier. +-- @return Dcs.DCScountry#country.id The country identifier. -- @return #nil The DCS Group is not existing or alive. function GROUP:GetCountry() self:F2( self.GroupName ) @@ -321,7 +321,7 @@ end -- If the underlying DCS Unit does not exist, the method will return nil. . -- @param #GROUP self -- @param #number UnitNumber The number of the UNIT wrapper class to be returned. --- @return Unit#UNIT The UNIT wrapper class. +-- @return Wrapper.Unit#UNIT The UNIT wrapper class. function GROUP:GetUnit( UnitNumber ) self:F2( { self.GroupName, UnitNumber } ) @@ -341,7 +341,7 @@ end -- If the underlying DCS Unit does not exist, the method will return nil. . -- @param #GROUP self -- @param #number UnitNumber The number of the DCS Unit to be returned. --- @return DCSUnit#Unit The DCS Unit. +-- @return Dcs.DCSWrapper.Unit#Unit The DCS Unit. function GROUP:GetDCSUnit( UnitNumber ) self:F2( { self.GroupName, UnitNumber } ) @@ -474,7 +474,7 @@ end --- Returns the current point (Vec2 vector) of the first DCS Unit in the DCS Group. -- @param #GROUP self --- @return DCSTypes#Vec2 Current Vec2 point of the first DCS Unit of the DCS Group. +-- @return Dcs.DCSTypes#Vec2 Current Vec2 point of the first DCS Unit of the DCS Group. function GROUP:GetVec2() self:F2( self.GroupName ) @@ -486,7 +486,7 @@ function GROUP:GetVec2() end --- Returns the current Vec3 vector of the first DCS Unit in the GROUP. --- @return DCSTypes#Vec3 Current Vec3 of the first DCS Unit of the GROUP. +-- @return Dcs.DCSTypes#Vec3 Current Vec3 of the first DCS Unit of the GROUP. function GROUP:GetVec3() self:F2( self.GroupName ) @@ -501,13 +501,13 @@ end --- Returns true if all units of the group are within a @{Zone}. -- @param #GROUP self --- @param Zone#ZONE_BASE Zone The zone to test. --- @return #boolean Returns true if the Group is completely within the @{Zone#ZONE_BASE} +-- @param Core.Zone#ZONE_BASE Zone The zone to test. +-- @return #boolean Returns true if the Group is completely within the @{Core.Zone#ZONE_BASE} function GROUP:IsCompletelyInZone( Zone ) self:F2( { self.GroupName, Zone } ) for UnitID, UnitData in pairs( self:GetUnits() ) do - local Unit = UnitData -- Unit#UNIT + local Unit = UnitData -- Wrapper.Unit#UNIT -- TODO: Rename IsPointVec3InZone to IsVec3InZone if Zone:IsPointVec3InZone( Unit:GetVec3() ) then else @@ -520,13 +520,13 @@ end --- Returns true if some units of the group are within a @{Zone}. -- @param #GROUP self --- @param Zone#ZONE_BASE Zone The zone to test. --- @return #boolean Returns true if the Group is completely within the @{Zone#ZONE_BASE} +-- @param Core.Zone#ZONE_BASE Zone The zone to test. +-- @return #boolean Returns true if the Group is completely within the @{Core.Zone#ZONE_BASE} function GROUP:IsPartlyInZone( Zone ) self:F2( { self.GroupName, Zone } ) for UnitID, UnitData in pairs( self:GetUnits() ) do - local Unit = UnitData -- Unit#UNIT + local Unit = UnitData -- Wrapper.Unit#UNIT if Zone:IsPointVec3InZone( Unit:GetVec3() ) then return true end @@ -537,13 +537,13 @@ end --- Returns true if none of the group units of the group are within a @{Zone}. -- @param #GROUP self --- @param Zone#ZONE_BASE Zone The zone to test. --- @return #boolean Returns true if the Group is completely within the @{Zone#ZONE_BASE} +-- @param Core.Zone#ZONE_BASE Zone The zone to test. +-- @return #boolean Returns true if the Group is completely within the @{Core.Zone#ZONE_BASE} function GROUP:IsNotInZone( Zone ) self:F2( { self.GroupName, Zone } ) for UnitID, UnitData in pairs( self:GetUnits() ) do - local Unit = UnitData -- Unit#UNIT + local Unit = UnitData -- Wrapper.Unit#UNIT if Zone:IsPointVec3InZone( Unit:GetVec3() ) then return false end @@ -673,19 +673,19 @@ function GROUP:GetMaxVelocity() local DCSGroup = self:GetDCSObject() if DCSGroup then - local MaxVelocity = 0 + local GroupVelocityMax = 0 for Index, UnitData in pairs( DCSGroup:getUnits() ) do - local Velocity = UnitData:getVelocity() - local VelocityTotal = math.abs( Velocity.x ) + math.abs( Velocity.y ) + math.abs( Velocity.z ) + local UnitVelocityVec3 = UnitData:getVelocity() + local UnitVelocity = math.abs( UnitVelocityVec3.x ) + math.abs( UnitVelocityVec3.y ) + math.abs( UnitVelocityVec3.z ) - if VelocityTotal < MaxVelocity then - MaxVelocity = VelocityTotal + if UnitVelocity > GroupVelocityMax then + GroupVelocityMax = UnitVelocity end end - return MaxVelocity + return GroupVelocityMax end return nil @@ -712,7 +712,7 @@ end -- SPAWNING --- Respawn the @{GROUP} using a (tweaked) template of the Group. --- The template must be retrieved with the @{Group#GROUP.GetTemplate}() function. +-- The template must be retrieved with the @{Wrapper.Group#GROUP.GetTemplate}() function. -- The template contains all the definitions as declared within the mission file. -- To understand templates, do the following: -- @@ -728,7 +728,7 @@ end -- * When the group is alive, it will tweak the template x, y and heading coordinates of the group and the embedded units to the current units positions. -- * Then it will destroy the current alive group. -- * And it will respawn the group using your new template definition. --- @param Group#GROUP self +-- @param Wrapper.Group#GROUP self -- @param #table Template The template of the Group retrieved with GROUP:GetTemplate() function GROUP:Respawn( Template ) @@ -740,7 +740,7 @@ function GROUP:Respawn( Template ) self:E( #Template.units ) for UnitID, UnitData in pairs( self:GetUnits() ) do - local GroupUnit = UnitData -- Unit#UNIT + local GroupUnit = UnitData -- Wrapper.Unit#UNIT self:E( GroupUnit:GetName() ) if GroupUnit:IsAlive() then local GroupUnitVec3 = GroupUnit:GetVec3() @@ -777,7 +777,7 @@ end --- Sets the CountryID of the group in a Template. -- @param #GROUP self --- @param DCScountry#country.id CountryID The country ID. +-- @param Dcs.DCScountry#country.id CountryID The country ID. -- @return #table function GROUP:SetTemplateCountry( Template, CountryID ) Template.CountryID = CountryID @@ -786,7 +786,7 @@ end --- Sets the CoalitionID of the group in a Template. -- @param #GROUP self --- @param DCSCoalitionObject#coalition.side CoalitionID The coalition ID. +-- @param Dcs.DCSCoalitionWrapper.Object#coalition.side CoalitionID The coalition ID. -- @return #table function GROUP:SetTemplateCoalition( Template, CoalitionID ) Template.CoalitionID = CoalitionID @@ -814,7 +814,7 @@ function GROUP:GetTaskRoute() return routines.utils.deepCopy( _DATABASE.Templates.Groups[self.GroupName].Template.route.points ) end ---- Return the route of a group by using the @{Database#DATABASE} class. +--- Return the route of a group by using the @{Core.Database#DATABASE} class. -- @param #GROUP self -- @param #number Begin The route point from where the copy will start. The base route point is 0. -- @param #number End The route point where the copy will end. The End point is the last point - the End point. The last point has base 0. diff --git a/Moose Development/Moose/Identifiable.lua b/Moose Development/Moose/Wrapper/Identifiable.lua similarity index 78% rename from Moose Development/Moose/Identifiable.lua rename to Moose Development/Moose/Wrapper/Identifiable.lua index cc12bdbb4..865f9c398 100644 --- a/Moose Development/Moose/Identifiable.lua +++ b/Moose Development/Moose/Wrapper/Identifiable.lua @@ -1,8 +1,8 @@ --- This module contains the IDENTIFIABLE class. -- --- 1) @{Identifiable#IDENTIFIABLE} class, extends @{Object#OBJECT} +-- 1) @{#IDENTIFIABLE} class, extends @{Wrapper.Object#OBJECT} -- =============================================================== --- The @{Identifiable#IDENTIFIABLE} class is a wrapper class to handle the DCS Identifiable objects: +-- The @{#IDENTIFIABLE} class is a wrapper class to handle the DCS Identifiable objects: -- -- * Support all DCS Identifiable APIs. -- * Enhance with Identifiable specific APIs not in the DCS Identifiable API set. @@ -12,18 +12,18 @@ -- ------------------------------ -- The IDENTIFIABLE class provides the following functions to construct a IDENTIFIABLE instance: -- --- * @{Identifiable#IDENTIFIABLE.New}(): Create a IDENTIFIABLE instance. +-- * @{#IDENTIFIABLE.New}(): Create a IDENTIFIABLE instance. -- -- 1.2) IDENTIFIABLE methods: -- -------------------------- -- The following methods can be used to identify an identifiable object: -- --- * @{Identifiable#IDENTIFIABLE.GetName}(): Returns the name of the Identifiable. --- * @{Identifiable#IDENTIFIABLE.IsAlive}(): Returns if the Identifiable is alive. --- * @{Identifiable#IDENTIFIABLE.GetTypeName}(): Returns the type name of the Identifiable. --- * @{Identifiable#IDENTIFIABLE.GetCoalition}(): Returns the coalition of the Identifiable. --- * @{Identifiable#IDENTIFIABLE.GetCountry}(): Returns the country of the Identifiable. --- * @{Identifiable#IDENTIFIABLE.GetDesc}(): Returns the descriptor structure of the Identifiable. +-- * @{#IDENTIFIABLE.GetName}(): Returns the name of the Identifiable. +-- * @{#IDENTIFIABLE.IsAlive}(): Returns if the Identifiable is alive. +-- * @{#IDENTIFIABLE.GetTypeName}(): Returns the type name of the Identifiable. +-- * @{#IDENTIFIABLE.GetCoalition}(): Returns the coalition of the Identifiable. +-- * @{#IDENTIFIABLE.GetCountry}(): Returns the country of the Identifiable. +-- * @{#IDENTIFIABLE.GetDesc}(): Returns the descriptor structure of the Identifiable. -- -- -- === @@ -33,7 +33,7 @@ --- The IDENTIFIABLE class -- @type IDENTIFIABLE --- @extends Object#OBJECT +-- @extends Wrapper.Object#OBJECT -- @field #string IdentifiableName The name of the identifiable. IDENTIFIABLE = { ClassName = "IDENTIFIABLE", @@ -50,7 +50,7 @@ local _CategoryName = { --- Create a new IDENTIFIABLE from a DCSIdentifiable -- @param #IDENTIFIABLE self --- @param DCSIdentifiable#Identifiable IdentifiableName The DCS Identifiable name +-- @param Dcs.DCSWrapper.Identifiable#Identifiable IdentifiableName The DCS Identifiable name -- @return #IDENTIFIABLE self function IDENTIFIABLE:New( IdentifiableName ) local self = BASE:Inherit( self, OBJECT:New( IdentifiableName ) ) @@ -60,11 +60,11 @@ function IDENTIFIABLE:New( IdentifiableName ) end --- Returns if the Identifiable is alive. --- @param Identifiable#IDENTIFIABLE self +-- @param #IDENTIFIABLE self -- @return #boolean true if Identifiable is alive. -- @return #nil The DCS Identifiable is not existing or alive. function IDENTIFIABLE:IsAlive() - self:F2( self.IdentifiableName ) + self:F3( self.IdentifiableName ) local DCSIdentifiable = self:GetDCSObject() @@ -81,7 +81,7 @@ end --- Returns DCS Identifiable object name. -- The function provides access to non-activated objects too. --- @param Identifiable#IDENTIFIABLE self +-- @param #IDENTIFIABLE self -- @return #string The name of the DCS Identifiable. -- @return #nil The DCS Identifiable is not existing or alive. function IDENTIFIABLE:GetName() @@ -100,7 +100,7 @@ end --- Returns the type name of the DCS Identifiable. --- @param Identifiable#IDENTIFIABLE self +-- @param #IDENTIFIABLE self -- @return #string The type name of the DCS Identifiable. -- @return #nil The DCS Identifiable is not existing or alive. function IDENTIFIABLE:GetTypeName() @@ -121,7 +121,7 @@ end --- Returns category of the DCS Identifiable. -- @param #IDENTIFIABLE self --- @return DCSObject#Object.Category The category ID +-- @return Dcs.DCSWrapper.Object#Object.Category The category ID function IDENTIFIABLE:GetCategory() self:F2( self.ObjectName ) @@ -137,7 +137,7 @@ end --- Returns the DCS Identifiable category name as defined within the DCS Identifiable Descriptor. --- @param Identifiable#IDENTIFIABLE self +-- @param #IDENTIFIABLE self -- @return #string The DCS Identifiable Category Name function IDENTIFIABLE:GetCategoryName() local DCSIdentifiable = self:GetDCSObject() @@ -152,8 +152,8 @@ function IDENTIFIABLE:GetCategoryName() end --- Returns coalition of the Identifiable. --- @param Identifiable#IDENTIFIABLE self --- @return DCSCoalitionObject#coalition.side The side of the coalition. +-- @param #IDENTIFIABLE self +-- @return Dcs.DCSCoalitionWrapper.Object#coalition.side The side of the coalition. -- @return #nil The DCS Identifiable is not existing or alive. function IDENTIFIABLE:GetCoalition() self:F2( self.IdentifiableName ) @@ -171,8 +171,8 @@ function IDENTIFIABLE:GetCoalition() end --- Returns country of the Identifiable. --- @param Identifiable#IDENTIFIABLE self --- @return DCScountry#country.id The country identifier. +-- @param #IDENTIFIABLE self +-- @return Dcs.DCScountry#country.id The country identifier. -- @return #nil The DCS Identifiable is not existing or alive. function IDENTIFIABLE:GetCountry() self:F2( self.IdentifiableName ) @@ -192,8 +192,8 @@ end --- Returns Identifiable descriptor. Descriptor type depends on Identifiable category. --- @param Identifiable#IDENTIFIABLE self --- @return DCSIdentifiable#Identifiable.Desc The Identifiable descriptor. +-- @param #IDENTIFIABLE self +-- @return Dcs.DCSWrapper.Identifiable#Identifiable.Desc The Identifiable descriptor. -- @return #nil The DCS Identifiable is not existing or alive. function IDENTIFIABLE:GetDesc() self:F2( self.IdentifiableName ) diff --git a/Moose Development/Moose/Object.lua b/Moose Development/Moose/Wrapper/Object.lua similarity index 77% rename from Moose Development/Moose/Object.lua rename to Moose Development/Moose/Wrapper/Object.lua index a2f804e5f..3ac441fbd 100644 --- a/Moose Development/Moose/Object.lua +++ b/Moose Development/Moose/Wrapper/Object.lua @@ -1,8 +1,8 @@ --- This module contains the OBJECT class. -- --- 1) @{Object#OBJECT} class, extends @{Base#BASE} +-- 1) @{Wrapper.Object#OBJECT} class, extends @{Core.Base#BASE} -- =========================================================== --- The @{Object#OBJECT} class is a wrapper class to handle the DCS Object objects: +-- The @{Wrapper.Object#OBJECT} class is a wrapper class to handle the DCS Object objects: -- -- * Support all DCS Object APIs. -- * Enhance with Object specific APIs not in the DCS Object API set. @@ -12,13 +12,13 @@ -- ------------------------------ -- The OBJECT class provides the following functions to construct a OBJECT instance: -- --- * @{Object#OBJECT.New}(): Create a OBJECT instance. +-- * @{Wrapper.Object#OBJECT.New}(): Create a OBJECT instance. -- -- 1.2) OBJECT methods: -- -------------------------- -- The following methods can be used to identify an Object object: -- --- * @{Object#OBJECT.GetID}(): Returns the ID of the Object object. +-- * @{Wrapper.Object#OBJECT.GetID}(): Returns the ID of the Object object. -- -- === -- @@ -27,7 +27,7 @@ --- The OBJECT class -- @type OBJECT --- @extends Base#BASE +-- @extends Core.Base#BASE -- @field #string ObjectName The name of the Object. OBJECT = { ClassName = "OBJECT", @@ -41,7 +41,7 @@ OBJECT = { --- Create a new OBJECT from a DCSObject -- @param #OBJECT self --- @param DCSObject#Object ObjectName The Object name +-- @param Dcs.DCSWrapper.Object#Object ObjectName The Object name -- @return #OBJECT self function OBJECT:New( ObjectName ) local self = BASE:Inherit( self, BASE:New() ) @@ -52,8 +52,8 @@ end --- Returns the unit's unique identifier. --- @param Object#OBJECT self --- @return DCSObject#Object.ID ObjectID +-- @param Wrapper.Object#OBJECT self +-- @return Dcs.DCSWrapper.Object#Object.ID ObjectID -- @return #nil The DCS Object is not existing or alive. function OBJECT:GetID() self:F2( self.ObjectName ) diff --git a/Moose Development/Moose/Wrapper/Positionable.lua b/Moose Development/Moose/Wrapper/Positionable.lua new file mode 100644 index 000000000..8b449bf7e --- /dev/null +++ b/Moose Development/Moose/Wrapper/Positionable.lua @@ -0,0 +1,414 @@ +--- This module contains the POSITIONABLE class. +-- +-- 1) @{Wrapper.Positionable#POSITIONABLE} class, extends @{Wrapper.Identifiable#IDENTIFIABLE} +-- =========================================================== +-- The @{Wrapper.Positionable#POSITIONABLE} class is a wrapper class to handle the POSITIONABLE objects: +-- +-- * Support all DCS APIs. +-- * Enhance with POSITIONABLE specific APIs not in the DCS API set. +-- * Manage the "state" of the POSITIONABLE. +-- +-- 1.1) POSITIONABLE constructor: +-- ------------------------------ +-- The POSITIONABLE class provides the following functions to construct a POSITIONABLE instance: +-- +-- * @{Wrapper.Positionable#POSITIONABLE.New}(): Create a POSITIONABLE instance. +-- +-- 1.2) POSITIONABLE methods: +-- -------------------------- +-- The following methods can be used to identify an measurable object: +-- +-- * @{Wrapper.Positionable#POSITIONABLE.GetID}(): Returns the ID of the measurable object. +-- * @{Wrapper.Positionable#POSITIONABLE.GetName}(): Returns the name of the measurable object. +-- +-- === +-- +-- @module Positionable +-- @author FlightControl + +--- The POSITIONABLE class +-- @type POSITIONABLE +-- @extends Wrapper.Identifiable#IDENTIFIABLE +-- @field #string PositionableName The name of the measurable. +POSITIONABLE = { + ClassName = "POSITIONABLE", + PositionableName = "", +} + +--- A DCSPositionable +-- @type DCSPositionable +-- @field id_ The ID of the controllable in DCS + +--- Create a new POSITIONABLE from a DCSPositionable +-- @param #POSITIONABLE self +-- @param Dcs.DCSWrapper.Positionable#Positionable PositionableName The POSITIONABLE name +-- @return #POSITIONABLE self +function POSITIONABLE:New( PositionableName ) + local self = BASE:Inherit( self, IDENTIFIABLE:New( PositionableName ) ) + + return self +end + +--- Returns the @{Dcs.DCSTypes#Position3} position vectors indicating the point and direction vectors in 3D of the POSITIONABLE within the mission. +-- @param Wrapper.Positionable#POSITIONABLE self +-- @return Dcs.DCSTypes#Position The 3D position vectors of the POSITIONABLE. +-- @return #nil The POSITIONABLE is not existing or alive. +function POSITIONABLE:GetPositionVec3() + self:F2( self.PositionableName ) + + local DCSPositionable = self:GetDCSObject() + + if DCSPositionable then + local PositionablePosition = DCSPositionable:getPosition() + self:T3( PositionablePosition ) + return PositionablePosition + end + + return nil +end + +--- Returns the @{Dcs.DCSTypes#Vec2} vector indicating the point in 2D of the POSITIONABLE within the mission. +-- @param Wrapper.Positionable#POSITIONABLE self +-- @return Dcs.DCSTypes#Vec2 The 2D point vector of the POSITIONABLE. +-- @return #nil The POSITIONABLE is not existing or alive. +function POSITIONABLE:GetVec2() + self:F2( self.PositionableName ) + + local DCSPositionable = self:GetDCSObject() + + if DCSPositionable then + local PositionableVec3 = DCSPositionable:getPosition().p + + local PositionableVec2 = {} + PositionableVec2.x = PositionableVec3.x + PositionableVec2.y = PositionableVec3.z + + self:T2( PositionableVec2 ) + return PositionableVec2 + end + + return nil +end + +--- Returns a POINT_VEC2 object indicating the point in 2D of the POSITIONABLE within the mission. +-- @param Wrapper.Positionable#POSITIONABLE self +-- @return Core.Point#POINT_VEC2 The 2D point vector of the POSITIONABLE. +-- @return #nil The POSITIONABLE is not existing or alive. +function POSITIONABLE:GetPointVec2() + self:F2( self.PositionableName ) + + local DCSPositionable = self:GetDCSObject() + + if DCSPositionable then + local PositionableVec3 = DCSPositionable:getPosition().p + + local PositionablePointVec2 = POINT_VEC2:NewFromVec3( PositionableVec3 ) + + self:T2( PositionablePointVec2 ) + return PositionablePointVec2 + end + + return nil +end + + +--- Returns a random @{Dcs.DCSTypes#Vec3} vector within a range, indicating the point in 3D of the POSITIONABLE within the mission. +-- @param Wrapper.Positionable#POSITIONABLE self +-- @return Dcs.DCSTypes#Vec3 The 3D point vector of the POSITIONABLE. +-- @return #nil The POSITIONABLE is not existing or alive. +function POSITIONABLE:GetRandomVec3( Radius ) + self:F2( self.PositionableName ) + + local DCSPositionable = self:GetDCSObject() + + if DCSPositionable then + local PositionablePointVec3 = DCSPositionable:getPosition().p + local PositionableRandomVec3 = {} + local angle = math.random() * math.pi*2; + PositionableRandomVec3.x = PositionablePointVec3.x + math.cos( angle ) * math.random() * Radius; + PositionableRandomVec3.y = PositionablePointVec3.y + PositionableRandomVec3.z = PositionablePointVec3.z + math.sin( angle ) * math.random() * Radius; + + self:T3( PositionableRandomVec3 ) + return PositionableRandomVec3 + end + + return nil +end + +--- Returns the @{Dcs.DCSTypes#Vec3} vector indicating the 3D vector of the POSITIONABLE within the mission. +-- @param Wrapper.Positionable#POSITIONABLE self +-- @return Dcs.DCSTypes#Vec3 The 3D point vector of the POSITIONABLE. +-- @return #nil The POSITIONABLE is not existing or alive. +function POSITIONABLE:GetVec3() + self:F2( self.PositionableName ) + + local DCSPositionable = self:GetDCSObject() + + if DCSPositionable then + local PositionableVec3 = DCSPositionable:getPosition().p + self:T3( PositionableVec3 ) + return PositionableVec3 + end + + return nil +end + +--- Returns the altitude of the POSITIONABLE. +-- @param Wrapper.Positionable#POSITIONABLE self +-- @return Dcs.DCSTypes#Distance The altitude of the POSITIONABLE. +-- @return #nil The POSITIONABLE is not existing or alive. +function POSITIONABLE:GetAltitude() + self:F2() + + local DCSPositionable = self:GetDCSObject() + + if DCSPositionable then + local PositionablePointVec3 = DCSPositionable:getPoint() --Dcs.DCSTypes#Vec3 + return PositionablePointVec3.y + end + + return nil +end + +--- Returns if the Positionable is located above a runway. +-- @param Wrapper.Positionable#POSITIONABLE self +-- @return #boolean true if Positionable is above a runway. +-- @return #nil The POSITIONABLE is not existing or alive. +function POSITIONABLE:IsAboveRunway() + self:F2( self.PositionableName ) + + local DCSPositionable = self:GetDCSObject() + + if DCSPositionable then + + local Vec2 = self:GetVec2() + local SurfaceType = land.getSurfaceType( Vec2 ) + local IsAboveRunway = SurfaceType == land.SurfaceType.RUNWAY + + self:T2( IsAboveRunway ) + return IsAboveRunway + end + + return nil +end + + + +--- Returns the POSITIONABLE heading in degrees. +-- @param Wrapper.Positionable#POSITIONABLE self +-- @return #number The POSTIONABLE heading +function POSITIONABLE:GetHeading() + local DCSPositionable = self:GetDCSObject() + + if DCSPositionable then + + local PositionablePosition = DCSPositionable:getPosition() + if PositionablePosition then + local PositionableHeading = math.atan2( PositionablePosition.x.z, PositionablePosition.x.x ) + if PositionableHeading < 0 then + PositionableHeading = PositionableHeading + 2 * math.pi + end + PositionableHeading = PositionableHeading * 180 / math.pi + self:T2( PositionableHeading ) + return PositionableHeading + end + end + + return nil +end + + +--- Returns true if the POSITIONABLE is in the air. +-- @param Wrapper.Positionable#POSITIONABLE self +-- @return #boolean true if in the air. +-- @return #nil The POSITIONABLE is not existing or alive. +function POSITIONABLE:InAir() + self:F2( self.PositionableName ) + + local DCSPositionable = self:GetDCSObject() + + if DCSPositionable then + local PositionableInAir = DCSPositionable:inAir() + self:T3( PositionableInAir ) + return PositionableInAir + end + + return nil +end + + +--- Returns the POSITIONABLE velocity vector. +-- @param Wrapper.Positionable#POSITIONABLE self +-- @return Dcs.DCSTypes#Vec3 The velocity vector +-- @return #nil The POSITIONABLE is not existing or alive. +function POSITIONABLE:GetVelocity() + self:F2( self.PositionableName ) + + local DCSPositionable = self:GetDCSObject() + + if DCSPositionable then + local PositionableVelocityVec3 = DCSPositionable:getVelocity() + self:T3( PositionableVelocityVec3 ) + return PositionableVelocityVec3 + end + + return nil +end + +--- Returns the POSITIONABLE velocity in km/h. +-- @param Wrapper.Positionable#POSITIONABLE self +-- @return #number The velocity in km/h +-- @return #nil The POSITIONABLE is not existing or alive. +function POSITIONABLE:GetVelocityKMH() + self:F2( self.PositionableName ) + + local DCSPositionable = self:GetDCSObject() + + if DCSPositionable then + local VelocityVec3 = self:GetVelocity() + local Velocity = ( VelocityVec3.x ^ 2 + VelocityVec3.y ^ 2 + VelocityVec3.z ^ 2 ) ^ 0.5 -- in meters / sec + local Velocity = Velocity * 3.6 -- now it is in km/h. + self:T3( Velocity ) + return Velocity + end + + return nil +end + +--- Returns a message with the callsign embedded (if there is one). +-- @param #POSITIONABLE self +-- @param #string Message The message text +-- @param Dcs.DCSTypes#Duration Duration The duration of the message. +-- @return Core.Message#MESSAGE +function POSITIONABLE:GetMessage( Message, Duration ) + + local DCSObject = self:GetDCSObject() + if DCSObject then + return MESSAGE:New( Message, Duration, self:GetCallsign() .. " (" .. self:GetTypeName() .. ")" ) + end + + return nil +end + +--- Send a message to all coalitions. +-- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. +-- @param #POSITIONABLE self +-- @param #string Message The message text +-- @param Dcs.DCSTypes#Duration Duration The duration of the message. +function POSITIONABLE:MessageToAll( Message, Duration ) + self:F2( { Message, Duration } ) + + local DCSObject = self:GetDCSObject() + if DCSObject then + self:GetMessage( Message, Duration ):ToAll() + end + + return nil +end + +--- Send a message to a coalition. +-- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. +-- @param #POSITIONABLE self +-- @param #string Message The message text +-- @param Dcs.DCSTYpes#Duration Duration The duration of the message. +function POSITIONABLE:MessageToCoalition( Message, Duration, MessageCoalition ) + self:F2( { Message, Duration } ) + + local DCSObject = self:GetDCSObject() + if DCSObject then + self:GetMessage( Message, Duration ):ToCoalition( MessageCoalition ) + end + + return nil +end + + +--- Send a message to the red coalition. +-- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. +-- @param #POSITIONABLE self +-- @param #string Message The message text +-- @param Dcs.DCSTYpes#Duration Duration The duration of the message. +function POSITIONABLE:MessageToRed( Message, Duration ) + self:F2( { Message, Duration } ) + + local DCSObject = self:GetDCSObject() + if DCSObject then + self:GetMessage( Message, Duration ):ToRed() + end + + return nil +end + +--- Send a message to the blue coalition. +-- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. +-- @param #POSITIONABLE self +-- @param #string Message The message text +-- @param Dcs.DCSTypes#Duration Duration The duration of the message. +function POSITIONABLE:MessageToBlue( Message, Duration ) + self:F2( { Message, Duration } ) + + local DCSObject = self:GetDCSObject() + if DCSObject then + self:GetMessage( Message, Duration ):ToBlue() + end + + return nil +end + +--- Send a message to a client. +-- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. +-- @param #POSITIONABLE self +-- @param #string Message The message text +-- @param Dcs.DCSTypes#Duration Duration The duration of the message. +-- @param Wrapper.Client#CLIENT Client The client object receiving the message. +function POSITIONABLE:MessageToClient( Message, Duration, Client ) + self:F2( { Message, Duration } ) + + local DCSObject = self:GetDCSObject() + if DCSObject then + self:GetMessage( Message, Duration ):ToClient( Client ) + end + + return nil +end + +--- Send a message to a @{Group}. +-- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. +-- @param #POSITIONABLE self +-- @param #string Message The message text +-- @param Dcs.DCSTypes#Duration Duration The duration of the message. +-- @param Wrapper.Group#GROUP MessageGroup The GROUP object receiving the message. +function POSITIONABLE:MessageToGroup( Message, Duration, MessageGroup ) + self:F2( { Message, Duration } ) + + local DCSObject = self:GetDCSObject() + if DCSObject then + if DCSObject:isExist() then + self:GetMessage( Message, Duration ):ToGroup( MessageGroup ) + end + end + + return nil +end + +--- Send a message to the players in the @{Group}. +-- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. +-- @param #POSITIONABLE self +-- @param #string Message The message text +-- @param Dcs.DCSTypes#Duration Duration The duration of the message. +function POSITIONABLE:Message( Message, Duration ) + self:F2( { Message, Duration } ) + + local DCSObject = self:GetDCSObject() + if DCSObject then + self:GetMessage( Message, Duration ):ToGroup( self ) + end + + return nil +end + + + + + diff --git a/Moose Development/Moose/Static.lua b/Moose Development/Moose/Wrapper/Static.lua similarity index 91% rename from Moose Development/Moose/Static.lua rename to Moose Development/Moose/Wrapper/Static.lua index 7d6492672..ca81d064e 100644 --- a/Moose Development/Moose/Static.lua +++ b/Moose Development/Moose/Wrapper/Static.lua @@ -1,10 +1,10 @@ --- This module contains the STATIC class. -- --- 1) @{Static#STATIC} class, extends @{Positionable#POSITIONABLE} +-- 1) @{Wrapper.Static#STATIC} class, extends @{Wrapper.Positionable#POSITIONABLE} -- =============================================================== -- Statics are **Static Units** defined within the Mission Editor. -- Note that Statics are almost the same as Units, but they don't have a controller. --- The @{Static#STATIC} class is a wrapper class to handle the DCS Static objects: +-- The @{Wrapper.Static#STATIC} class is a wrapper class to handle the DCS Static objects: -- -- * Wraps the DCS Static objects. -- * Support all DCS Static APIs. @@ -38,7 +38,7 @@ --- The STATIC class -- @type STATIC --- @extends Positionable#POSITIONABLE +-- @extends Wrapper.Positionable#POSITIONABLE STATIC = { ClassName = "STATIC", } diff --git a/Moose Development/Moose/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua similarity index 89% rename from Moose Development/Moose/Unit.lua rename to Moose Development/Moose/Wrapper/Unit.lua index a4aaf6d29..65657e3c5 100644 --- a/Moose Development/Moose/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -1,8 +1,8 @@ --- This module contains the UNIT class. -- --- 1) @{Unit#UNIT} class, extends @{Controllable#CONTROLLABLE} +-- 1) @{#UNIT} class, extends @{Wrapper.Controllable#CONTROLLABLE} -- =========================================================== --- The @{Unit#UNIT} class is a wrapper class to handle the DCS Unit objects: +-- The @{#UNIT} class is a wrapper class to handle the DCS Unit objects: -- -- * Support all DCS Unit APIs. -- * Enhance with Unit specific APIs not in the DCS Unit API set. @@ -33,7 +33,7 @@ -- ------------------ -- The DCS Unit APIs are used extensively within MOOSE. The UNIT class has for each DCS Unit API a corresponding method. -- To be able to distinguish easily in your code the difference between a UNIT API call and a DCS Unit API call, --- the first letter of the method is also capitalized. So, by example, the DCS Unit method @{DCSUnit#Unit.getName}() +-- the first letter of the method is also capitalized. So, by example, the DCS Unit method @{Dcs.DCSWrapper.Unit#Unit.getName}() -- is implemented in the UNIT class as @{#UNIT.GetName}(). -- -- 1.3) Smoke, Flare Units @@ -60,7 +60,7 @@ -- The UNIT class contains methods to test the location or proximity against zones or other objects. -- -- ### 1.6.1) Zones --- To test whether the Unit is within a **zone**, use the @{#UNIT.IsInZone}() or the @{#UNIT.IsNotInZone}() methods. Any zone can be tested on, but the zone must be derived from @{Zone#ZONE_BASE}. +-- To test whether the Unit is within a **zone**, use the @{#UNIT.IsInZone}() or the @{#UNIT.IsNotInZone}() methods. Any zone can be tested on, but the zone must be derived from @{Core.Zone#ZONE_BASE}. -- -- ### 1.6.2) Units -- Test if another DCS Unit is within a given radius of the current DCS Unit, use the @{#UNIT.OtherUnitInRadius}() method. @@ -74,40 +74,11 @@ --- The UNIT class -- @type UNIT --- @extends Controllable#CONTROLLABLE --- @field #UNIT.FlareColor FlareColor --- @field #UNIT.SmokeColor SmokeColor +-- @extends Wrapper.Controllable#CONTROLLABLE UNIT = { ClassName="UNIT", - FlareColor = { - Green = trigger.flareColor.Green, - Red = trigger.flareColor.Red, - White = trigger.flareColor.White, - Yellow = trigger.flareColor.Yellow - }, - SmokeColor = { - Green = trigger.smokeColor.Green, - Red = trigger.smokeColor.Red, - White = trigger.smokeColor.White, - Orange = trigger.smokeColor.Orange, - Blue = trigger.smokeColor.Blue - }, - } +} ---- FlareColor --- @type UNIT.FlareColor --- @field Green --- @field Red --- @field White --- @field Yellow - ---- SmokeColor --- @type UNIT.SmokeColor --- @field Green --- @field Red --- @field White --- @field Orange --- @field Blue --- Unit.SensorType -- @type Unit.SensorType @@ -122,7 +93,7 @@ UNIT = { --- Create a new UNIT from DCSUnit. -- @param #UNIT self -- @param #string UnitName The name of the DCS unit. --- @return Unit#UNIT +-- @return #UNIT function UNIT:Register( UnitName ) local self = BASE:Inherit( self, CONTROLLABLE:New( UnitName ) ) self.UnitName = UnitName @@ -133,8 +104,8 @@ end --- Finds a UNIT from the _DATABASE using a DCSUnit object. -- @param #UNIT self --- @param DCSUnit#Unit DCSUnit An existing DCS Unit object reference. --- @return Unit#UNIT self +-- @param Dcs.DCSWrapper.Unit#Unit DCSUnit An existing DCS Unit object reference. +-- @return #UNIT self function UNIT:Find( DCSUnit ) local UnitName = DCSUnit:getName() @@ -145,7 +116,7 @@ end --- Find a UNIT in the _DATABASE using the name of an existing DCS Unit. -- @param #UNIT self -- @param #string UnitName The Unit Name. --- @return Unit#UNIT self +-- @return #UNIT self function UNIT:FindByName( UnitName ) local UnitFound = _DATABASE:FindUnit( UnitName ) @@ -162,7 +133,7 @@ end --- @param #UNIT self --- @return DCSUnit#Unit +-- @return Dcs.DCSWrapper.Unit#Unit function UNIT:GetDCSObject() local DCSUnit = Unit.getByName( self.UnitName ) @@ -182,24 +153,25 @@ end -- * When the unit is alive, it will tweak the template x, y and heading coordinates of the group and the embedded units to the current units positions. -- * Then it will respawn the re-modelled group. -- --- @param Unit#UNIT self --- @param DCSTypes#Vec3 SpawnVec3 The position where to Spawn the new Unit at. +-- @param #UNIT self +-- @param Dcs.DCSTypes#Vec3 SpawnVec3 The position where to Spawn the new Unit at. -- @param #number Heading The heading of the unit respawn. function UNIT:ReSpawn( SpawnVec3, Heading ) local SpawnGroupTemplate = UTILS.DeepCopy( _DATABASE:GetGroupTemplateFromUnitName( self:Name() ) ) self:T( SpawnGroupTemplate ) + local SpawnGroup = self:GetGroup() if SpawnGroup then local Vec3 = SpawnGroup:GetVec3() - SpawnGroupTemplate.x = Vec3.x - SpawnGroupTemplate.y = Vec3.z + SpawnGroupTemplate.x = SpawnVec3.x + SpawnGroupTemplate.y = SpawnVec3.z self:E( #SpawnGroupTemplate.units ) for UnitID, UnitData in pairs( SpawnGroup:GetUnits() ) do - local GroupUnit = UnitData -- Unit#UNIT + local GroupUnit = UnitData -- #UNIT self:E( GroupUnit:GetName() ) if GroupUnit:IsAlive() then local GroupUnitVec3 = GroupUnit:GetVec3() @@ -222,6 +194,36 @@ function UNIT:ReSpawn( SpawnVec3, Heading ) SpawnGroupTemplate.units[UnitTemplateID].y = SpawnVec3.z SpawnGroupTemplate.units[UnitTemplateID].heading = Heading self:E( { UnitTemplateID, SpawnGroupTemplate.units[UnitTemplateID], SpawnGroupTemplate.units[UnitTemplateID] } ) + else + self:E( SpawnGroupTemplate.units[UnitTemplateID].name ) + local GroupUnit = UNIT:FindByName( SpawnGroupTemplate.units[UnitTemplateID].name ) -- #UNIT + if GroupUnit and GroupUnit:IsAlive() then + local GroupUnitVec3 = GroupUnit:GetVec3() + local GroupUnitHeading = GroupUnit:GetHeading() + UnitTemplateData.alt = GroupUnitVec3.y + UnitTemplateData.x = GroupUnitVec3.x + UnitTemplateData.y = GroupUnitVec3.z + UnitTemplateData.heading = GroupUnitHeading + else + if SpawnGroupTemplate.units[UnitTemplateID].name ~= self:Name() then + self:T("nilling") + SpawnGroupTemplate.units[UnitTemplateID].delete = true + end + end + end + end + + -- Remove obscolete units from the group structure + i = 1 + while i <= #SpawnGroupTemplate.units do + + local UnitTemplateData = SpawnGroupTemplate.units[i] + self:T( UnitTemplateData.name ) + + if UnitTemplateData.delete then + table.remove( SpawnGroupTemplate.units, i ) + else + i = i + 1 end end @@ -231,7 +233,7 @@ end --- Returns if the unit is activated. --- @param Unit#UNIT self +-- @param #UNIT self -- @return #boolean true if Unit is activated. -- @return #nil The DCS Unit is not existing or alive. function UNIT:IsActive() @@ -251,7 +253,7 @@ end --- Returns the Unit's callsign - the localized string. --- @param Unit#UNIT self +-- @param #UNIT self -- @return #string The Callsign of the Unit. -- @return #nil The DCS Unit is not existing or alive. function UNIT:GetCallsign() @@ -270,7 +272,7 @@ end --- Returns name of the player that control the unit or nil if the unit is controlled by A.I. --- @param Unit#UNIT self +-- @param #UNIT self -- @return #string Player Name -- @return #nil The DCS Unit is not existing or alive. function UNIT:GetPlayerName() @@ -294,7 +296,7 @@ end -- The number is the same number the unit has in ME. -- It may not be changed during the mission. -- If any unit in the group is destroyed, the numbers of another units will not be changed. --- @param Unit#UNIT self +-- @param #UNIT self -- @return #number The Unit number. -- @return #nil The DCS Unit is not existing or alive. function UNIT:GetNumber() @@ -311,8 +313,8 @@ function UNIT:GetNumber() end --- Returns the unit's group if it exist and nil otherwise. --- @param Unit#UNIT self --- @return Group#GROUP The Group of the Unit. +-- @param Wrapper.Unit#UNIT self +-- @return Wrapper.Group#GROUP The Group of the Unit. -- @return #nil The DCS Unit is not existing or alive. function UNIT:GetGroup() self:F2( self.UnitName ) @@ -333,7 +335,7 @@ end --- Returns the prefix name of the DCS Unit. A prefix name is a part of the name before a '#'-sign. -- DCS Units spawned with the @{SPAWN} class contain a '#'-sign to indicate the end of the (base) DCS Unit name. -- The spawn sequence number and unit number are contained within the name after the '#' sign. --- @param Unit#UNIT self +-- @param #UNIT self -- @return #string The name of the DCS Unit. -- @return #nil The DCS Unit is not existing or alive. function UNIT:GetPrefix() @@ -351,8 +353,8 @@ function UNIT:GetPrefix() end --- Returns the Unit's ammunition. --- @param Unit#UNIT self --- @return DCSUnit#Unit.Ammo +-- @param #UNIT self +-- @return Dcs.DCSWrapper.Unit#Unit.Ammo -- @return #nil The DCS Unit is not existing or alive. function UNIT:GetAmmo() self:F2( self.UnitName ) @@ -368,8 +370,8 @@ function UNIT:GetAmmo() end --- Returns the unit sensors. --- @param Unit#UNIT self --- @return DCSUnit#Unit.Sensors +-- @param #UNIT self +-- @return Dcs.DCSWrapper.Unit#Unit.Sensors -- @return #nil The DCS Unit is not existing or alive. function UNIT:GetSensors() self:F2( self.UnitName ) @@ -388,7 +390,7 @@ end -- unit:hasSensors(Unit.SensorType.RADAR, Unit.RadarType.AS) --- Returns if the unit has sensors of a certain type. --- @param Unit#UNIT self +-- @param #UNIT self -- @return #boolean returns true if the unit has specified types of sensors. This function is more preferable than Unit.getSensors() if you don't want to get information about all the unit's sensors, and just want to check if the unit has specified types of sensors. -- @return #nil The DCS Unit is not existing or alive. function UNIT:HasSensors( ... ) @@ -405,7 +407,7 @@ function UNIT:HasSensors( ... ) end --- Returns if the unit is SEADable. --- @param Unit#UNIT self +-- @param #UNIT self -- @return #boolean returns true if the unit is SEADable. -- @return #nil The DCS Unit is not existing or alive. function UNIT:HasSEAD() @@ -431,9 +433,9 @@ end -- -- * First value indicates if at least one of the unit's radar(s) is on. -- * Second value is the object of the radar's interest. Not nil only if at least one radar of the unit is tracking a target. --- @param Unit#UNIT self +-- @param #UNIT self -- @return #boolean Indicates if at least one of the unit's radar(s) is on. --- @return DCSObject#Object The object of the radar's interest. Not nil only if at least one radar of the unit is tracking a target. +-- @return Dcs.DCSWrapper.Object#Object The object of the radar's interest. Not nil only if at least one radar of the unit is tracking a target. -- @return #nil The DCS Unit is not existing or alive. function UNIT:GetRadar() self:F2( self.UnitName ) @@ -449,7 +451,7 @@ function UNIT:GetRadar() end --- Returns relative amount of fuel (from 0.0 to 1.0) the unit has in its internal tanks. If there are additional fuel tanks the value may be greater than 1.0. --- @param Unit#UNIT self +-- @param #UNIT self -- @return #number The relative amount of fuel (from 0.0 to 1.0). -- @return #nil The DCS Unit is not existing or alive. function UNIT:GetFuel() @@ -466,7 +468,7 @@ function UNIT:GetFuel() end --- Returns the unit's health. Dead units has health <= 1.0. --- @param Unit#UNIT self +-- @param #UNIT self -- @return #number The Unit's health value. -- @return #nil The DCS Unit is not existing or alive. function UNIT:GetLife() @@ -483,7 +485,7 @@ function UNIT:GetLife() end --- Returns the Unit's initial health. --- @param Unit#UNIT self +-- @param #UNIT self -- @return #number The Unit's initial health value. -- @return #nil The DCS Unit is not existing or alive. function UNIT:GetLife0() @@ -560,8 +562,8 @@ end --- Returns true if the unit is within a @{Zone}. -- @param #UNIT self --- @param Zone#ZONE_BASE Zone The zone to test. --- @return #boolean Returns true if the unit is within the @{Zone#ZONE_BASE} +-- @param Core.Zone#ZONE_BASE Zone The zone to test. +-- @return #boolean Returns true if the unit is within the @{Core.Zone#ZONE_BASE} function UNIT:IsInZone( Zone ) self:F2( { self.UnitName, Zone } ) @@ -577,8 +579,8 @@ end --- Returns true if the unit is not within a @{Zone}. -- @param #UNIT self --- @param Zone#ZONE_BASE Zone The zone to test. --- @return #boolean Returns true if the unit is not within the @{Zone#ZONE_BASE} +-- @param Core.Zone#ZONE_BASE Zone The zone to test. +-- @return #boolean Returns true if the unit is not within the @{Core.Zone#ZONE_BASE} function UNIT:IsNotInZone( Zone ) self:F2( { self.UnitName, Zone } ) @@ -594,8 +596,8 @@ end --- Returns true if there is an **other** DCS Unit within a radius of the current 2D point of the DCS Unit. --- @param Unit#UNIT self --- @param Unit#UNIT AwaitUnit The other UNIT wrapper object. +-- @param #UNIT self +-- @param #UNIT AwaitUnit The other UNIT wrapper object. -- @param Radius The radius in meters with the DCS Unit in the centre. -- @return true If the other DCS Unit is within the radius of the 2D point of the DCS Unit. -- @return #nil The DCS Unit is not existing or alive. @@ -624,6 +626,7 @@ end --- Signal a flare at the position of the UNIT. -- @param #UNIT self +-- @param Utilities.Utils#FLARECOLOR FlareColor function UNIT:Flare( FlareColor ) self:F2() trigger.action.signalFlare( self:GetVec3(), FlareColor , 0 ) diff --git a/Moose Development/Moose/DeployTask.lua b/Moose Development/Old/DeployTask.lua similarity index 100% rename from Moose Development/Moose/DeployTask.lua rename to Moose Development/Old/DeployTask.lua diff --git a/Moose Development/Moose/DestroyBaseTask.lua b/Moose Development/Old/DestroyBaseTask.lua similarity index 98% rename from Moose Development/Moose/DestroyBaseTask.lua rename to Moose Development/Old/DestroyBaseTask.lua index b057605ba..1c1194d58 100644 --- a/Moose Development/Moose/DestroyBaseTask.lua +++ b/Moose Development/Old/DestroyBaseTask.lua @@ -43,7 +43,7 @@ end --- Handle the S_EVENT_DEAD events to validate the destruction of units for the task monitoring. -- @param #DESTROYBASETASK self --- @param Event#EVENTDATA Event structure of MOOSE. +-- @param Core.Event#EVENTDATA Event structure of MOOSE. function DESTROYBASETASK:EventDead( Event ) self:F( { Event } ) diff --git a/Moose Development/Moose/DestroyGroupsTask.lua b/Moose Development/Old/DestroyGroupsTask.lua similarity index 91% rename from Moose Development/Moose/DestroyGroupsTask.lua rename to Moose Development/Old/DestroyGroupsTask.lua index d98d9e710..5f2dd6332 100644 --- a/Moose Development/Moose/DestroyGroupsTask.lua +++ b/Moose Development/Old/DestroyGroupsTask.lua @@ -32,8 +32,8 @@ end --- Report Goal Progress. -- @param #DESTROYGROUPSTASK self --- @param DCSGroup#Group DestroyGroup Group structure describing the group to be evaluated. --- @param DCSUnit#Unit DestroyUnit Unit structure describing the Unit to be evaluated. +-- @param Dcs.DCSWrapper.Group#Group DestroyGroup Group structure describing the group to be evaluated. +-- @param Dcs.DCSWrapper.Unit#Unit DestroyUnit Unit structure describing the Unit to be evaluated. -- @return #number The DestroyCount reflecting the amount of units destroyed within the group. function DESTROYGROUPSTASK:ReportGoalProgress( DestroyGroup, DestroyUnit ) self:F( { DestroyGroup, DestroyUnit, self.DestroyPercentage } ) diff --git a/Moose Development/Moose/DestroyRadarsTask.lua b/Moose Development/Old/DestroyRadarsTask.lua similarity index 100% rename from Moose Development/Moose/DestroyRadarsTask.lua rename to Moose Development/Old/DestroyRadarsTask.lua diff --git a/Moose Development/Moose/DestroyUnitTypesTask.lua b/Moose Development/Old/DestroyUnitTypesTask.lua similarity index 100% rename from Moose Development/Moose/DestroyUnitTypesTask.lua rename to Moose Development/Old/DestroyUnitTypesTask.lua diff --git a/Moose Development/Moose/GoHomeTask.lua b/Moose Development/Old/GoHomeTask.lua similarity index 100% rename from Moose Development/Moose/GoHomeTask.lua rename to Moose Development/Old/GoHomeTask.lua diff --git a/Moose Development/Moose/NoTask.lua b/Moose Development/Old/NoTask.lua similarity index 100% rename from Moose Development/Moose/NoTask.lua rename to Moose Development/Old/NoTask.lua diff --git a/Moose Development/Moose/PickupTask.lua b/Moose Development/Old/PickupTask.lua similarity index 100% rename from Moose Development/Moose/PickupTask.lua rename to Moose Development/Old/PickupTask.lua diff --git a/Moose Development/Moose/RouteTask.lua b/Moose Development/Old/RouteTask.lua similarity index 100% rename from Moose Development/Moose/RouteTask.lua rename to Moose Development/Old/RouteTask.lua diff --git a/Moose Development/Moose/Stage.lua b/Moose Development/Old/Stage.lua similarity index 97% rename from Moose Development/Moose/Stage.lua rename to Moose Development/Old/Stage.lua index 2a08e1820..f6902ab18 100644 --- a/Moose Development/Moose/Stage.lua +++ b/Moose Development/Old/Stage.lua @@ -68,9 +68,9 @@ end --- Execute -- @param #STAGEBRIEF self --- @param Mission#MISSION Mission --- @param Client#CLIENT Client --- @param Task#TASK Task +-- @param Tasking.Mission#MISSION Mission +-- @param Wrapper.Client#CLIENT Client +-- @param Tasking.Task#TASK Task -- @return #boolean function STAGEBRIEF:Execute( Mission, Client, Task ) local Valid = BASE:Inherited(self):Execute( Mission, Client, Task ) @@ -228,9 +228,9 @@ end --- Execute the routing. -- @param #STAGEROUTE self --- @param Mission#MISSION Mission --- @param Client#CLIENT Client --- @param Task#TASK Task +-- @param Tasking.Mission#MISSION Mission +-- @param Wrapper.Client#CLIENT Client +-- @param Tasking.Task#TASK Task function STAGEROUTE:Execute( Mission, Client, Task ) self:F() local Valid = BASE:Inherited(self):Execute( Mission, Client, Task ) @@ -300,9 +300,9 @@ end --- Execute the landing coordination. -- @param #STAGELANDING self --- @param Mission#MISSION Mission --- @param Client#CLIENT Client --- @param Task#TASK Task +-- @param Tasking.Mission#MISSION Mission +-- @param Wrapper.Client#CLIENT Client +-- @param Tasking.Task#TASK Task function STAGELANDING:Execute( Mission, Client, Task ) self:F() @@ -524,9 +524,9 @@ end --- Coordinate UnLoading -- @param #STAGEUNLOAD self --- @param Mission#MISSION Mission --- @param Client#CLIENT Client --- @param Task#TASK Task +-- @param Tasking.Mission#MISSION Mission +-- @param Wrapper.Client#CLIENT Client +-- @param Tasking.Task#TASK Task function STAGEUNLOAD:Execute( Mission, Client, Task ) self:F() @@ -562,9 +562,9 @@ end --- Validate UnLoading -- @param #STAGEUNLOAD self --- @param Mission#MISSION Mission --- @param Client#CLIENT Client --- @param Task#TASK Task +-- @param Tasking.Mission#MISSION Mission +-- @param Wrapper.Client#CLIENT Client +-- @param Tasking.Task#TASK Task function STAGEUNLOAD:Validate( Mission, Client, Task ) self:F() env.info( 'STAGEUNLOAD:Validate()' ) @@ -835,9 +835,9 @@ end --- Execute Arrival -- @param #STAGEARRIVE self --- @param Mission#MISSION Mission --- @param Client#CLIENT Client --- @param Task#TASK Task +-- @param Tasking.Mission#MISSION Mission +-- @param Wrapper.Client#CLIENT Client +-- @param Tasking.Task#TASK Task function STAGEARRIVE:Execute( Mission, Client, Task ) self:F() @@ -915,7 +915,7 @@ end _TransportStage: Defines the different stages of which of transport missions can be in. This table is internal and is used to control the sequence of messages, actions and flow. - _TransportStage.START - - _TransportStage.ROUTE + - _TransportStage.ACT_ROUTE - _TransportStage.LAND - _TransportStage.EXECUTE - _TransportStage.DONE @@ -924,7 +924,7 @@ end _TransportStage = { HOLD = "HOLD", START = "START", - ROUTE = "ROUTE", + ACT_ROUTE = "ACT_ROUTE", LANDING = "LANDING", LANDED = "LANDED", EXECUTING = "EXECUTING", @@ -937,7 +937,7 @@ _TransportStage = { _TransportStageMsgTime = { HOLD = 10, START = 60, - ROUTE = 5, + ACT_ROUTE = 5, LANDING = 10, LANDED = 30, EXECUTING = 30, @@ -950,7 +950,7 @@ _TransportStageMsgTime = { _TransportStageTime = { HOLD = 10, START = 5, - ROUTE = 5, + ACT_ROUTE = 5, LANDING = 1, LANDED = 1, EXECUTING = 5, diff --git a/Moose Development/Release Notes/2016-07 - ReleaseNotes.txt b/Moose Development/ReleaseNotes.txt similarity index 61% rename from Moose Development/Release Notes/2016-07 - ReleaseNotes.txt rename to Moose Development/ReleaseNotes.txt index 0a55ab167..6686615c8 100644 --- a/Moose Development/Release Notes/2016-07 - ReleaseNotes.txt +++ b/Moose Development/ReleaseNotes.txt @@ -1,19 +1,127 @@ +2016-12-06 + + - Renamed the documentation references following the structure of the files. + -- All STATEMACHINE references became FSM + -- FSM_TEMPLATE references are also abbreviated by FSMT + -- Removed Process#PROCESS references. + -- All cross file documentation references updated. That was a lot of work! + + - Tasking is working now. But needs further cleanup. + - Templates are assigned to Tasks. When a task is assigned to a player, the FSMT + - Removed 2nd level in tasking. Now there is only one level. + - Tasking works now with FSM. + - Made FSMT process templates that can be used in Tasks. + - Scoring can now also be added to the templates. + +2016-09-01 + + - Expanded the CARGO classes and implemented the CARGO_GROUPED classes. + -- Finished the correct state machine implementation to Board, UnBoard, Load, UnLoad cargo to and from carriers. + -- Created the CARGO_GROUPED class, which groups CARGO_UNITs into one group. The cargo behaves like groups, but can be transported by carriers. + -- Documented CARGO event functions, state transition functions + overall documentation. + -- Updated test missions located in the directory: Moose_Test_CARGO + + - Expanded the PROCESS_PATROLZONE class. + +2016-08-21 + + - Made a new STATEMACHINE_CONTROLLABLE object, which models a base state machine class to be inherited by AI controllable classes. + -- Created the class as such that intherited AI classes become "finite state machines". + -- Each STATEMACHINE_CONTROLLABLE class contains a Controllable object, which is one Unit, Client or Group object. + -- Added state transition functions that are called before and after the state transition. + -- Event functions are automatically added to the class, based on the FSMT. + -- Added immediate and delayed event processing as part of the STATEMACHINE_CONTROLLABLE class. + --- Events that start with __Event are processed with a delay. The delay is given in seconds as a parameter. + + - Created a new PROCESS_PATROLZONE class, which inherites STATEMACHINE_CONTROLLABLE. + -- This class implements a complete new revamped patrol zone AI pattern. + -- Created a new test directory: Moose_Test_PROCESS_PATROLZONE with test missions. + +2016-08-15 + + - Removed the old classes and moved into an "Old" folder in the Moose/Development folder. + -- Cleaned Moose.lua + Documented class types + -- Cleaned Create_Moose.bat + Documented class types + + - Extend the ZONE_BASE class with a probability randomization factor, that can be used for zone randomization purposes. + + - Documented the Zone module classes. + + - Changed and removed the POINT_VEC3 SmokeColor and FlareColor structure. Replaced with SMOKECOLOR and FLARECOLOR types. + -- Replaced also code in test missions with SMOKECOLOR and FLARECOLOR references. + + - Added change logs of API changes in MOOSE documentation. + + - Added ZONE_BASE:GetName() method. + + - Added ZONE_BASE:GetZoneProbability() method. + + - Added ZONE_BASE:SetZoneProbability() method. + + - Added ZONE_BASE:GetZoneMaybe() method. + + - Added SPAWN:InitRandomizeZones() method. + + - Renamed SPAWN:CleanUp() method to SPAWN:InitCleanUp() method. + + - Reviewed documentation of the PatrolZone module and PATROLZONE class. + + +2016-08-14 + + - Changed Spawn APIs to express Group position and Unit position randomization. + + - Changed the API protocol of SpawnInZone() method. + -- Removed OuterRadius and InnerRadius parameters !!! + + - Changed the API protocol of SpawnFromUnit() method. + -- Removed OuterRadius and InnerRadius parameters !!! + + - Added InitRandomizeUnits() method, taking 3 parameters: + -- RandomizeUnits given the value true, will randomize the units upon spawning, false (default) will not randomize the untis. + -- OuterRadius is the outer radius of the band where the units will be spawned, if RandomizeUnits is true. + -- InnerRadius is the inner radius of the band where the units will not be spawned, if RandomizeUnits is true. + + - Removed SpawnFunction() method. + + - Added OnSpawnGroup() method as the new official CallBack function method to catch when a new function will be called. + -- Documented OnSpawnGroup() method. + + - Renamed Limit() method to InitLimit() method. + + - Renamed Array() method to InitArray() method. + + - Renamed RandomizeRoute() method to InitRandomizeRoute() method. + + - Renamed RandomizeTemplate() method to InitRandomizeTemplate() method. + + - Renamed UnControlled() method to InitUnControlled method. + + - Reviewed all test missions for the changes executed and made adaptions where necessary + re-tests. + + 2016-08-12 + - Temporary release of the new cargo handling. -- Released available functionality to handle one CARGO_UNIT loading, boarding, unloading. -- Created CARGO_UNIT test missions. + - Added Translate() method in POINT_VEC3, translating a 3D point over the horizontal plane with a Distance and Angle coordinate. + 2016-08-08 + - Added briefing to method ESCORT:New() -- If no EscortBriefing is given, the New() method will show the default briefing. 2016-08-06 + - Made PointVec3 and Vec3, PointVec2 and Vec2 terminology used in the code consistent. -- Replaced method PointVec3() to Vec3() where the code manages a Vec3. Replaced all references to the method. -- Replaced method PointVec2() to Vec2() where the code manages a Vec2. Replaced all references to the method. -- Replaced method RandomPointVec3() to RandomVec3() where the code manages a Vec3. Replaced all references to the method. 2016-08-03 + - Fixed error at SPAWN:RandomizeTemplate() -- Units started in wrong x, y position (at the template, instead of at the master template of the SPAWN). -- Adjusted x, y and alt to the start position of the first unit of the master template. @@ -21,9 +129,11 @@ -- Regenerated MOOSE.lua 2016-07-31 + - Added a SpawnHeight parameter to the SPAWN method RandomizeRoute(), based upon a bug report of David. Air units will now also have optionally the route height randomized. 2016-07-29 + - Documentation of methods GetFirstAliveGroup, GetNextAliveGroup, GetLastAliveGroup + code snippets. 2016-07-23 diff --git a/Moose Mission Setup/Moose Mission Template/Moose_Mission_Template.miz b/Moose Mission Setup/Moose Mission Template/Moose_Mission_Template.miz index d1b77c07b..af6c3b818 100644 Binary files a/Moose Mission Setup/Moose Mission Template/Moose_Mission_Template.miz and b/Moose Mission Setup/Moose Mission Template/Moose_Mission_Template.miz differ diff --git a/Moose Mission Setup/Moose Mission Update/7-zip.chm b/Moose Mission Setup/Moose Mission Update/7-zip.chm new file mode 100644 index 000000000..68f152ccb Binary files /dev/null and b/Moose Mission Setup/Moose Mission Update/7-zip.chm differ diff --git a/Moose Mission Setup/Moose Mission Update/7-zip.dll b/Moose Mission Setup/Moose Mission Update/7-zip.dll new file mode 100644 index 000000000..6f0bd373f Binary files /dev/null and b/Moose Mission Setup/Moose Mission Update/7-zip.dll differ diff --git a/Moose Mission Setup/Moose Mission Update/7-zip32.dll b/Moose Mission Setup/Moose Mission Update/7-zip32.dll new file mode 100644 index 000000000..1ea1da918 Binary files /dev/null and b/Moose Mission Setup/Moose Mission Update/7-zip32.dll differ diff --git a/Moose Mission Setup/Moose Mission Update/7z.dll b/Moose Mission Setup/Moose Mission Update/7z.dll new file mode 100644 index 000000000..450acaf6f Binary files /dev/null and b/Moose Mission Setup/Moose Mission Update/7z.dll differ diff --git a/Moose Mission Setup/Moose Mission Update/7z.exe b/Moose Mission Setup/Moose Mission Update/7z.exe new file mode 100644 index 000000000..e444ddf5e Binary files /dev/null and b/Moose Mission Setup/Moose Mission Update/7z.exe differ diff --git a/Moose Mission Setup/Moose Mission Update/7z.sfx b/Moose Mission Setup/Moose Mission Update/7z.sfx new file mode 100644 index 000000000..acf79e62e Binary files /dev/null and b/Moose Mission Setup/Moose Mission Update/7z.sfx differ diff --git a/Moose Mission Setup/Moose Mission Update/7zCon.sfx b/Moose Mission Setup/Moose Mission Update/7zCon.sfx new file mode 100644 index 000000000..531e1778a Binary files /dev/null and b/Moose Mission Setup/Moose Mission Update/7zCon.sfx differ diff --git a/Moose Mission Setup/Moose Mission Update/7zFM.exe b/Moose Mission Setup/Moose Mission Update/7zFM.exe new file mode 100644 index 000000000..89e81f9c0 Binary files /dev/null and b/Moose Mission Setup/Moose Mission Update/7zFM.exe differ diff --git a/Moose Mission Setup/Moose Mission Update/7zG.exe b/Moose Mission Setup/Moose Mission Update/7zG.exe new file mode 100644 index 000000000..4ceeb0f68 Binary files /dev/null and b/Moose Mission Setup/Moose Mission Update/7zG.exe differ diff --git a/Moose Mission Setup/Moose Mission Update/History.txt b/Moose Mission Setup/Moose Mission Update/History.txt new file mode 100644 index 000000000..677d35b55 --- /dev/null +++ b/Moose Mission Setup/Moose Mission Update/History.txt @@ -0,0 +1,1304 @@ +HISTORY of the 7-Zip +-------------------- + +16.02 2016-05-21 +------------------------- +- 7-Zip now can extract multivolume ZIP archives (z01, z02, ... , zip). +- Some bugs were fixed. + + +15.14 2015-12-31 +------------------------- +- 7-Zip File Manager: + - The code for "Open file from archive" operation was improved. + - The code for "Tools/Options" window was improved. + - The BUG was fixed: there was incorrect mouse cursor capture for + drag-and-drop operations from open archive to Explorer window. +- Some bugs were fixed. +- New localization: Yoruba. + + +15.12 2015-11-19 +------------------------- +- The release version. + + +15.11 beta 2015-11-14 +------------------------- +- Some bugs were fixed. + + +15.10 beta 2015-11-01 +------------------------- +- The BUG in 9.21 - 15.09 was fixed: + 7-Zip could ignore some parameters, specified for archive creation operation + for gzip and bzip2 formats in "Add to Archive" window and in command line + version (-m switch). +- Some bugs were fixed. + + +15.09 beta 2015-10-16 +------------------------- +- 7-Zip now can extract ext2 and multivolume VMDK images. +- Some bugs were fixed. + + +15.08 beta 2015-10-01 +------------------------- +- 7-Zip now can extract ext3 and ext4 (Linux file system) images. +- Some bugs were fixed. + + +15.07 beta 2015-09-17 +------------------------- +- 7-Zip now can extract GPT images and single file QCOW2, VMDK, VDI images. +- 7-Zip now can extract solid WIM archives with LZMS compression. +- Some bugs were fixed. + + +15.06 beta 2015-08-09 +------------------------- +- 7-Zip now can extract RAR5 archives. +- 7-Zip now doesn't sort files by type while adding to solid 7z archive. +- new -mqs switch to sort files by type while adding to solid 7z archive. +- The BUG in 7-Zip File Manager was fixed: + The "Move" operation to open 7z archive didn't delete empty files. +- The BUG in 15.05 was fixed: + console version added some text to the end of stdout stream, is -so switch was used. +- The BUG in 9.30 - 15.05 was fixed: + 7-Zip could not open multivolume sfx RAR archive. +- Some bugs were fixed. + + +15.05 beta 2015-06-14 +------------------------- +- 7-Zip now uses new installer. +- 7-Zip now can create 7z, xz and zip archives with 1536 MB dictionary for LZMA/LZMA2. +- 7-Zip File Manager now can operate with alternate file streams at NTFS + volumes via "File / Alternate Streams" menu command. +- 7-Zip now can extract .zipx (WinZip) archives that use xz compression. +- new optional "section size" parameter for BCJ2 filter for compression ratio improving. + Example: -mf=BCJ2:d9M, if largest executable section in files is smaller than 9 MB. +- Speed optimizations for BCJ2 filter and SHA-1 and SHA-256 calculation. +- Console version now uses stderr stream for error messages. +- Console version now shows names of processed files only in progress line by default. +- new -bb[0-3] switch to set output log level. -bb1 shows names of processed files in log. +- new -bs[o|e|p][0|1|2] switch to set stream for output messages; + o: output, e: error, p: progress line; 0: disable, 1: stdout, 2: stderr. +- new -bt switch to show execution time statistics. +- new -myx[0-9] switch to set level of file analysis. +- new -mmtf- switch to set single thread mode for filters. +- The BUG was fixed: + 7-Zip didn't restore NTFS permissions for folders during extracting from WIM archives. +- The BUG was fixed: + The command line version: if the command "rn" (Rename) was called with more + than one pair of paths, 7-Zip used only first rename pair. +- The BUG was fixed: + 7-Zip crashed for ZIP/LZMA/AES/AES-NI. +- The BUG in 15.01-15.02 was fixed: + 7-Zip created incorrect ZIP archives, if ZipCrypto encryption was used. + 7-Zip 9.20 can extract such incorrect ZIP archives. +- Some bugs were fixed. + + +9.38 beta 2015-01-03 +------------------------- +- Some bugs were fixed. + + +9.36 beta 2014-12-26 +------------------------- +- The BUG in command line version was fixed: + 7-Zip created temporary archive in current folder during update archive + operation, if -w{Path} switch was not specified. + The fixed 7-Zip creates temporary archive in folder that contains updated archive. +- The BUG in 9.33-9.35 was fixed: + 7-Zip silently ignored file reading errors during 7z or gz archive creation, + and the created archive contained only part of file that was read before error. + The fixed 7-Zip stops archive creation and it reports about error. +- Some bugs were fixed. + + +9.35 beta 2014-12-07 +------------------------- +- The BUG was fixed: + 7-Zip crashed during ZIP archive creation, if the number of CPU threads was more than 64. +- The BUG in 9.31-9.34 was fixed: + 7-Zip could not correctly extract ISO archives that are larger than 4 GiB. +- The BUG in 9.33-9.34 was fixed: + The option "Compress shared files" and -ssw switch didn't work. +- The BUG in 9.26-9.34 was fixed: + 7-Zip File Manager could crash for some archives open in "Flat View" mode. +- Some bugs were fixed. + + +9.34 alpha 2014-06-22 +------------------------- +- The BUG in 9.33 was fixed: + Command line version of 7-Zip could work incorrectly, if there is relative + path in exclude filename optiton (-x) and absolute path as include filename. +- The BUG in 9.26-9.33 was fixed: + 7-Zip could not open some unusual 7z archives that were created by another + software (not by 7-Zip). +- The BUG in 9.31-9.33 was fixed: + 7-Zip could crash with switch -tcab. + + +9.33 alpha 2014-06-15 +------------------------- +- 7-Zip now can show icons for 7-Zip items in Explorer's context menu. +- "Add to archive" dialog box: + - new options in "Path Mode" + - new option "Delete files after compression" + - new "NTFS" options for WIM and TAR formats: + - Store symbolic links + - Store hard links + - Store alternate data streams + - Store file security +- "Extract" dialog box: + - new optional field to set output folder name + - new option "Eliminate duplication of root folder" + - new option "Absolute pathnames" in "Path Mode". + - new option "Restore file security" (that works for WIM archives only) +- 7-Zip File Manager: + - new "File / Link" dialog box in to create symbolic links and hard links. +- Command line version: + - new -spd switch to Disable wildcard matching for file names + - new -spe switch to Eliminate duplication of root folder for extract archive command + - new -snh switch to store hard links as links (WIM and TAR formats only) + - new -snl switch to store symbolic links as links (WIM and TAR formats only) +- NSIS support was improved. +- The problem was fixed: + The command "extract to \*" with multiple archives could use same + output folder, if archives are placed inside PE (EXE) file. +- The BUG of 9.31-9.32 was fixed: + Command line version for test and extract commands returned the + value 0 as exit code, if it couldn't open archive. +- The BUG was fixed: + 7-Zip could not create archives with anti-items for any archive type, + except of 7z type +- Some bugs were fixed. +- New localization: Mongolian (script). + + +9.32 alpha 2013-12-01 +------------------------- +- 7-Zip now can create multivolume SFX archives in 7z format. + Standalone sfx module now can unpack external 7z archive with name that is + matched to name of sfx module. For example, sfx module renamed to archive.exe + can unpack archive.7z or archive.7z.001 . +- ZIP, NSIS, HFS, AR support was improved. +- 7-Zip now supports files larger than 4 GiB in ISO archives. +- Improved compression ratio in 7z format with maximum or ultra level for + executable files (EXE and DLL) that are larger than 16 MB (improved BCJ2 filter). +- Improved support for file pathnames longer than 260 characters. +- CRC and SHA checksum calculation for files can be called via Explorer's context menu. +- 7-Zip File Manager now also takes into account the numbers in filenames for sorting order. +- 7-Zip File Manager now can use RAM buffers instead of temp files to open + nested archives, if temp file is smaller than 1/4 of RAM size. +- 7-Zip File Manager can open files in "Parser" mode via "Open Archive > #" context + menu command. It shows the list of archives inside file. +- Command line version: + - new -t# switch to open file in "Parser" mode and show the list of archives inside file. + - new -stx{Type} switch to exclude archive type from using. + - -scs switch now supports UTF-16 encoding. + - now it shows time and memory usage statistics at the end of execution. +- The BUGs were fixed: + - 7-Zip 9.30 and early versions created ZIP archives with minor errors + in extra field of headers for directory items, if AES (WinZip-AES) encryption was used. + - 7-Zip could work incorrectly in decompression of more than one + multi-volume archive in one command. + - 7-Zip 9.24 alpha - 9.30 alpha versions could not extract ZIP archives + encrypted with PKWARE-AES method. +- Minimum supported system now is Windows 2000. 7-Zip doesn't work on Windows 95/98/ME. +- New localization: Irish. + + +9.30 alpha 2012-10-26 +------------------------- +- LZMA2 now is default compression method for .7z format. +- 7-Zip now can update WIM archives. +- 7-Zip File Manager now can move files to archives. +- The default encoding for TAR format now is UTF-8. You can use -mcp=1 switch for OEM encoding. +- Command line version: + - new "rn" command to rename files in archive. + - new -sdel switch to delete files after including to archive. + - new -sns switch to store NTFS alternate streams (for WIM format only). + - new -sni switch to store NT security information for files (for WIM format only). + - new -stl switch to set archive timestamp from the most recently modified file. +- Speed optimizations for opening big archives and big disk folders. +- 7-Zip now writes special padding blocks to headers of 7z archives for + faster archive opening. Note that 7-Zip 4.50 - 4.58 contain BUG, + so these old versions can't correctly work with such new 7z archives. +- DMG support was improved +- Some bugs were fixed. +- The BUG in 7-Zip 9.26 alpha - 9.29 alpha versions was fixed. + These alpha versions could not open non-solid 7z archive, if + some files were skipped during creation of that archive. + That problem is also related to 7z archives created in solid mode, + if each solid block contains no more than one file. + Note: 7-Zip skips files that were open for writing by another + application and shows warning in that case. +- New localization: Aragonese. + + +9.25 alpha 2011-09-16 +------------------------- +- LZMA decompression speed was improved. +- "compress and send to email" code was improved to support more email clients. +- New command "h" to calculate hash values CRC-32, CRC-64, SHA-256 or SHA-1 for files on disk. +- New -spf switch to store full file paths including drive letter to archive. + If you use that switch with extract command, please check that file names in archive are correct. +- Some bugs were fixed. + + +9.23 alpha 2011-06-07 +------------------------- +- The format of language files was changed. +- Some bugs were fixed. +- New localization: Karakalpak. + + +9.22 beta 2011-04-18 +------------------------- + +- 7-Zip now uses progress indicator displayed on a taskbar button under Windows 7. +- The BUG in 7-Zip 9.21 beta was fixed: + 7-Zip could ignore some options when you created ZIP archives. + For example, it could use ZipCrypto cipher instead of AES-256. + + +9.21 beta 2011-04-11 +------------------------- +- 7-Zip now can unpack UEFI BIOS files. +- 64-bit version of 7-Zip now includes additional 32-bit shell extension DLL. + So other 32-bit programs can call 64-bit 7-Zip via context menu. +- Now it's possible to associate 7-Zip with file types without Administrator rights. +- New -mf=FilterID switch to specify compression filter. Examples: + 7z a -mf=bcj2 a.7z a.tar + 7z a -mf=delta:4 a.7z a.wav + 7z a -mf=bcj a.tar.xz a.tar +- 32-bit 7-Zip running under 64-bit Windows now can use up to 4 GB of RAM. +- Some bugs were fixed. +- New localizations: Corsican, Kyrgyz, Ligurian. + + +9.20 2010-11-18 +------------------------- +- Some bugs were fixed. + + +9.19 beta 2010-11-11 +------------------------- +- The console version now doesn't show entered password. +- Some bugs were fixed. + + +9.18 beta 2010-11-02 +------------------------- +- 7-Zip now can unpack SquashFS and CramFS filesystem images. +- 7-Zip now can unpack some TAR and ISO archives with incorrect headers. +- New small SFX module for installers (in Extra package). +- Some bugs were fixed. + + +9.17 beta 2010-10-04 +------------------------- +- Disk fragmentation problem for ZIP archives created by 7-Zip was fixed. + + +9.16 beta 2010-09-08 +------------------------- +- 7-Zip now supports files that are larger than 8 GB in TAR archives. +- NSIS support was improved. +- Some bugs were fixed. +- New localizations: Hindi, Gujarati, Sanskrit. + + +9.15 beta 2010-06-20 +------------------------- +- Some bugs were fixed. +- New localization: Tatar. + + +9.14 beta 2010-06-04 +------------------------- +- WIM support was improved. + + +9.13 beta 2010-04-15 +------------------------- +- 7-Zip now stores NTFS file timestamps to ZIP archives. +- New additional "Open archive >" item in context menu allows to select + archive type for some files. +- Some bugs were fixed. +- New localization: Uyghur. + + +9.12 beta 2010-03-24 +------------------------- +- ZIP / PPMd compression ratio was improved in Maximum and Ultra modes. +- The BUG in 7-Zip 9.* beta was fixed: LZMA2 codec didn't work, + if more than 10 threads were used (or more than 20 threads in some modes). + + +9.11 beta 2010-03-15 +------------------------- +- 7-Zip now supports PPMd compression in ZIP archives. +- Speed optimizations in PPMd codec. +- The support for archives in installers was improved. +- Some bugs were fixed. +- New localization: Kazakh. + + +9.10 beta 2009-12-22 +------------------------- +- The BUG in 7-Zip 9.09 beta was fixed: + 7-Zip created incorrect ZIP archives, if ZipCrypto encryption was used. + + +9.09 beta 2009-12-12 +------------------------- +- 7-Zip now can unpack Apple Partition Map (APM) disk images. +- Speed optimizations in AES code for Intel's 32nm CPUs. +- Speed optimizations in CRC calculation code for Intel's Atom CPUs. +- Some bugs were fixed. + + +9.07 beta 2009-08-27 +------------------------- +- It's possible to specify Diff program in options (7-Zip File Manager). +- Some bugs were fixed. + + +9.06 beta 2009-08-17 +------------------------- +- 7-Zip now can unpack MSLZ archives. +- Partial parsing for EXE resources, SWF and FLV. +- Some bugs were fixed. + + +9.04 beta 2009-05-30 +------------------------- +- 7-Zip now can update solid .7z archives. +- 7-Zip now supports LZMA2 compression method. +- 7-Zip now supports XZ archives. +- 7-Zip now can unpack NTFS, FAT, VHD and MBR archives. +- 7-Zip now can unpack GZip, BZip2, LZMA, XZ and TAR archives from stdin. +- 7-Zip now can open/copy/compress disk images (like \\.\c:) from \\.\ folder. +- 7-Zip File Manager now doesn't use temp files to open nested archives + stored without compression. +- New -scrc switch to calculate total CRC-32 during extracting / testing. +- New -scc{WIN|DOS|UTF-8} switch to specify charset for console input/output (default = DOS). +- Some bugs were fixed. + + +4.65 2009-02-03 +------------------------- +- 7-Zip File Manager now can calculate SHA-256 checksum. +- Some bugs were fixed. + + +4.64 2009-01-03 +------------------------- +- The bug in 7-Zip 4.63 was fixed: 7-Zip could not decrypt .ZIP archives + encrypted with WinZip-AES method. + + +4.63 2008-12-31 +------------------------- +- 7-Zip now can unpack ZIP archives encrypted with PKWARE-AES. +- Some bugs were fixed. + + +4.62 2008-12-02 +------------------------- +- Some bugs were fixed. + + +4.61 beta 2008-11-23 +------------------------- +- 7-Zip now supports LZMA compression for .ZIP archives. +- Some bugs were fixed. +- New localization: Sinhala. + + +4.60 beta 2008-08-19 +------------------------- +- Some bugs were fixed. + + +4.59 beta 2008-08-13 +------------------------- +- 7-Zip now can unpack UDF, XAR and DMG/HFS archives. +- 7-Zip File Manager now keeps encryption when you edit encrypted file inside archive. +- 7-Zip File Manager now allows to change current folder from the address bar drop-down list. +- It's allowed to use -t switch for "list" and "extract" commands. +- Some bugs were fixed. +- New localizations: Icelandic, Kurdish Sorani. + + +4.58 beta 2008-05-05 +------------------------- +- Some speed optimizations. +- 7-Zip now can unpack .lzma archives. +- Unicode (UTF-8) support for filenames in .ZIP archives. Now there are 3 modes: + 1) Default mode: 7-Zip uses UTF-8, if the local code page doesn't contain required symbols. + 2) -mcu switch: 7-Zip uses UTF-8, if there are non-ASCII symbols. + 3) -mcl switch: 7-Zip uses local code page. +- Now it's possible to store file creation time in 7z and ZIP archives (-mtc switch). +- 7-Zip now can unpack multivolume RAR archives created with + "old style volume names" scheme and names *.001, *.002, ... +- Now it's possible to use -mSW- and -mSW+ switches instead of -mSW=off and -mSW=on +- Some bugs were fixed. +- New localizations: Punjabi (Indian), Pashto. + + +4.57 2007-12-06 +------------------------- +- The BUG in command line version was fixed: -up3 switch + could work incorrectly. + + +4.56 beta 2007-10-24 +------------------------- +- Some bugs were fixed. + + +4.55 beta 2007-09-05 +------------------------- +- Some bugs were fixed. + + +4.54 beta 2007-09-04 +------------------------- +- Decompression speed was increased. + + +4.53 beta 2007-08-27 +------------------------- +- "Test" and "Info" buttons now work for open archives. +- The bug in 7-Zip 4.48 - 4.52 beta was fixed: + 7-Zip could create .ZIP archives with broken files. +- Some bugs were fixed. + + +4.52 beta 2007-08-03 +------------------------- +- 7-Zip now can unpack Compound files (msi, doc, ...). +- Some bugs were fixed. + + +4.51 beta 2007-07-25 +------------------------- +- Bug was fixed: 7-Zip 4.50 beta could not open some .7z archives. + + +4.50 beta 2007-07-24 +------------------------- +- New switch for command line version: + -ssc[-] enables/disables case-sensitive mode for file names. +- Speed optimizations for AES encryption. +- Some bugs were fixed. + + +4.49 beta 2007-07-11 +------------------------- +- 7-Zip now can unpack WIM archives. +- 7-Zip now replaces incorrect characters in filenames during extracting. + + +4.48 beta 2007-06-26 +------------------------- +- Encryption strength for .7z format was increased. + Now it uses random initialization vectors. +- Some bugs were fixed. + + +4.47 beta 2007-05-27 +------------------------- +- Bugs of 7-Zip 4.46 beta were fixed: BZip2 could work incorrectly. + + +4.46 beta 2007-05-25 +------------------------- +- New fast compression mode for Deflate method in Zip and GZip. +- New "Compress shared files" option in GUI and -ssw switch. +- Some bugs were fixed. +- New localization: Norwegian Nynorsk. + + +4.45 beta 2007-04-17 +------------------------- +- Now it's possible to specify the size of solid block and the number + of CPU threads in "Add to archive" dialog box. +- Default dictionary size was increased: Normal: 16 MB, Max: 32 MB. +- Speed optimizations. +- Benchmark was improved (new "b" command in command line version). +- The number of DLL files was reduced. +- Now it's possible to associate 7-zip with combined types like .tbz2 +- switch -mhcf=off is not supported now. +- If -t{Type} switch is not specified, 7-Zip now uses extension of archive to + detect the type of archive. +- Some bugs were fixed. +- New localization: Welsh. + + +4.44 beta 2007-01-20 +------------------------- +- Speed optimizations for LZMA, Deflate, BZip2 and unRAR. +- 7-Zip now supports file pathnames longer than 260 characters. +- Some bugs were fixed. +- New localizations: Bangla, Bashkir, Nepali. + + +4.43 beta 2006-09-15 +------------------------- +- 7-Zip now can use multi-threading mode for compressing to .ZIP archives. +- ZIP format supporting was improved. +- 7-Zip now supports WinZip-compatible AES-256 encryption for .ZIP archives. +- New context menu items for .ZIP archives creating. +- 7-Zip now uses order list (list of extensions) for files sorting for compressing + to .7z archives. It can slightly increase compression ratio in some cases. +- 7-Zip now restores modification time of folders during .7z archives extracting. +- Some bugs were fixed. +- New localizations: Armenian, Marathi. + + + +4.42 2006-05-14 +------------------------- +- Compressing speed and Memory requirements were increased. + Default dictionary size was increased: Fastest: 64 KB, Fast: 1 MB, + Normal: 4 MB, Max: 16 MB, Ultra: 64 MB. +- BZip2 compressing / decompressing now can work in multi-threading mode +- Multi-threading mode now is default for multi-processor systems +- 64-bit version now supports 1 GB dictionary +- 7z/LZMA now can use only these match finders: HC4, BT2, BT3, BT4 +- Compression ratio in Zip/GZip/Deflate in Ultra mode was increased +- 7-Zip now can unpack ISO archives and some installers created by NSIS +- Optional "Flat View" mode in 7-Zip File Manager +- 7-Zip File Manager now can calculate CRC checksums for files +- -x switch with relative paths now affects files specified with absolute paths +- New switch for 7za.exe (console version): -slt. + "l" (list) command with -slt shows technical information for archive. +- New switch: -scs{WIN|DOS|UTF-8} specifies charset for list files. + Default charset for list files is UTF-8 now. +- Some bugs were fixed +- New localizations: Albanian, Kurdish + + +4.32 2005-12-09 +------------------------- +- Bug was fixed: 7-Zip 4.31 didn't work in Windows 95 + + +4.31 2005-12-04 +------------------------- +- Small changes +- New localization: Basque + + +4.30 beta 2005-11-18 +------------------------- +- Files 7zFMn.exe, 7zGn.exe, 7-zipn, 7za.exe, 7zC.sfx were removed from 7-zip package +- 7-Zip now uses uncompressed SFX: 7z.sfx +- Sfx modules 7z.sfx and 7zCon.sfx now use msvcrt.dll +- Speed optimizations in LZMA maximum/ultra compressing. +- LZMA now supports word size up to 273 +- 7-Zip now reduces dictionary size for LZMA, if you compress files + smaller than specified dictionary size. +- 7-Zip now can use large memory pages: + GUI: 7-Zip File Manager / Options / Settings / Use large memory pages. + Command line version: -slp switch. + This feature allows to increase speed of compressing. + But 7-Zip can make some pause at starting of compressing for allocating large pages. + Also Task Manager doesn't show real memory usage of program, if 7-Zip uses large pages. + This feature works only on Windows 2003 / XP x64 / Vista. + Also you must have administrator's rights for your system. + Recommended size of RAM: 1 GB or more. + To install this feature you must run 7-Zip File Manager at least once, + close it and reboot system. +- Some bugs were fixed + + +4.29 beta 2005-09-28 +------------------------- +- Bug was fixed: 7-Zip 4.28 beta worked incorrectly in Windows 95/98/Me + + +4.28 beta 2005-09-27 +------------------------- +- Bug was fixed: 7-Zip 4.27 beta created incorrect multivolume archives. +- "Duplicate filename" collision problem between names with ligatures was fixed. + + +4.27 beta 2005-09-21 +------------------------- +- 7-Zip can unpack CHM/HXS (MS HTML HELP) archives +- 7-Zip can unpack multivolume CAB archives +- Now 7-Zip deletes files to the Recycle Bin by default. + Shift+Delete deletes files permanently. +- Some bugs were fixed +- New localization: Tatarish + + +4.26 beta 2005-08-05 +------------------------- +- LZH format support (extracting only) +- Some bugs were fixed +- New localization: Ido + + +4.25 beta 2005-07-31 +------------------------- +- 7-Zip now doesn't interrupt the compressing when it can not + find specified file as in version 4.24 beta. It just shows warning. +- 7-Zip now supports standard selection mode in the file list +- Some bugs were fixed + + +4.24 beta 2005-07-06 +------------------------- +- 7-Zip now supports right-click Drag and Drop in Explorer +- Command line version now supports short file names (like FILENA~1.TXT) +- If there are no wildcard names and there is no -r switch in command line, + 7-Zip now checks that specified files exist on disk before compressing. +- Some bugs were fixed + + +4.23 2005-06-29 +------------------------- +- Drag and Drop support +- 7-Zip File Manager now can copy files from one archive to another +- Some bugs were fixed +- New localizations: Extremaduran, Malay + + +4.20 2005-05-30 +------------------------- +- No changes + + +4.19 beta 2005-05-21 +------------------------- +- BZip2 code was rewritten. Now it supports 3 modes: Normal, Maximum and + Ultra. In Normal mode it compresses almost as original BZip2 compressor. + Compression ratio in Maximum and Ultra modes is 1-3% better for some files, + but Maximum Mode is about 3 times slower and Ultra Mode is about 8 times + slower than Normal mode. +- Console version now prints all messages to stdout by default, + and if -so switch is specified, 7-Zip prints messages to stderr. +- Some bugs were fixed +- New localizations: Azeri, Georgian + + +4.18 beta 2005-04-19 +------------------------- +- Bug in v4.17 beta was fixed: 7-Zip File Manager could crash + after some operations with archives + + +4.17 beta 2005-04-18 +------------------------- +- To increase protection from viruses, 7-Zip now does not open + files with more than 4 continuous spaces in the name. + And 7-Zip changes such long spaces in name to " ... " in the file list. +- Code size optimization +- Some files were moved from main package to extra package: + - Plugin for FAR Manager + - SFX modules for installers (7zS.sfx and 7zSD.sfx) +- New localizations: Asturian, Indonesian + + +4.16 beta 2005-03-29 +------------------------- +- Speed optimization (5%) for 7z / LZMA +- 7za.exe now supports .Z archives +- -r- switch in command line now is default for all commands +- Some bugs were fixed +- New localization: Uzbek + + +4.15 beta 2005-01-25 +------------------------- +- Z format supporting (extracting only) +- 7-Zip now can extract ZIP archives compressed with "Shrink" method +- 7-Zip now doesn't interrupt the compressing when it can not open file. + 7-Zip just skips that file and shows warning. +- Some bugs were fixed +- New localization: Frisian + + +4.14 beta 2005-01-11 +------------------------- +- 7-Zip installer was created with NSIS. + Now it installs 7-Zip for all users (under Windows 2000/XP). +- Now 7-Zip can create multivolume archives + (switch -v for command line) +- Some bugs were fixed +- New localizations: Breton, Farsi + + +4.13 beta 2004-12-14 +------------------------- +- Switch "--" stops switches parsing +- Some bugs were fixed + + +4.12 beta 2004-11-18 +------------------------- +- Bug in v4.11 beta was fixed: + 7-Zip created incorrect ZIP archives if file size was + from 3.75 GB to 4 GB. + + +4.11 beta 2004-11-16 +------------------------- +- 7-Zip now shows file names during compressing/decompressing +- 7-Zip now supports Zip64 extension of ZIP format. So now it's + possible to compress files bigger than 4 GB to ZIP archives. +- Some bugs were fixed +- New localization: Galician + + +4.10 beta 2004-10-21 +------------------------- +- Bugs in v4.0* were fixed: + - Some commands in command line with "-r" switch worked incorrectly, + so 7-zip could skip some files during compressing + - Some other bugs were fixed +- Small internal changes + + +4.09 beta 2004-10-05 +------------------------- +- Bugs in v4.0* were fixed: + - Renaming inside archives didn't work or worked incorrectly + - GUI SFX didn't show extracting dialog at start +- Small fixes in 7-Zip GUI (7zG.exe) + + +4.08 beta 2004-10-04 +------------------------- +- Bug in installer for v4.07 was fixed: when rebooting + is required, it rebooted without asking user +- Small fixes in 7-Zip GUI (7zG.exe) + + +4.07 beta 2004-10-03 +------------------------- +- Big amount of code was changed in this beta version. + So don't use it for important data compressing. + And test archive after compressing. + +- Unified command line interface to GUI and console versions +- 7-Zip now can extract or test several archives in one command +- 7-Zip now doesn't interrupt the compressing when file is locked by + other application. 7-Zip just skips that file and shows warning. + Note: previous versions of 7-Zip had bug, so they can not unpack + non-solid and some solid 7z archives with such skipped files. +- Command line interface was changed: + - now it's possible to use absolute pathnames + - syntax simplification: + was: 7z a a Folder1\* Folder2\* -r + now: 7z a a Folder1 Folder2 + - now it's possible to use complex wildcard commands, like *\*111*\* +- More smart detection of archive type for files with unusual + file name extensions +- Supporting for RAR archives with encrypted headers +- CPIO format supporting was improved +- For GZip and BZip2 formats you can: + - Compress from stdin (-si switch) + - Compress to stdout (-so switch) + - Extract to stdout (-so switch) +- 7-Zip File Manager: + - Split and Combine commands + - new list view options: Full row select, Show grid lines +- Internal reconstruction +- Some bugs were fixed +- New localizations: Friulian, Macedonian, Mongolian, Tamil, Thai + + +3.13 2003-12-11 +------------------------- +- Some small bugs were fixed + + +3.12 2003-12-10 +------------------------- +- Now you can select compression method, dictionary size + and word size in "Add to archive" dialog box. Also it + shows memory usage. +- 7-Zip File Manager now contains toolbars. +- New "Benchmark" command in 7-Zip File Manager. + It measures compressing and decompressing speeds and + shows rating values. +- Some bugs were fixed. + + +3.11 2003-10-06 +------------------------- +- 7-zip now use limitations for solid block size + for increasing the speed of random file decompressing: + - in Store mode: 0 B + - in Fast mode: 16 MB + - in Normal mode: 256 MB + - in Maximum mode: 1 GB + - in Ultra mode: 4 GB +- 7z.exe, 7za.exe and SFX modules now support Unicode + file names under Windows NT/2000/XP/2003. + 7zn.exe and 7zan.exe were removed from package. +- Some bugs were fixed +- New localization: Afrikaans + + +3.10 2003-09-27 +------------------------- +- Drag-and-Drop from external application +- GUI version (7zG.exe) can compress files with absolute paths +- Compression dialog doesn't suggest bzip2 and gzip2 types when + there are more than one selected file +- Optional auto renaming for existing files during extraction + in command line version (-aot switch). +- Some bugs were fixed + + +3.09.02 2003-09-20 +------------------------- +- Optional limitation for solid block size for increasing + the speed of random file decompressing (-ms switch) + + +3.09.01 beta 2003-09-06 +------------------------- +- Automatic compression filter for executable files: + dll, exe, ocx, sfx, sys, (-mf switch) +- Compression levels in 7z now are: + - Fast: 32 KB dictionary, BCJ filter + - Normal: 2 MB dictionary, BCJ filter + - Maximum: 8 MB dictionary, BCJ filter, max settings + - Ultra: 32 MB dictionary, BCJ2 filter, max settings +- Updating solid 7z archives now is supported, if it doesn't + require repacking solid blocks +- -mhcf switch for 7z format now is default +- Some bugs were fixed + + +3.08.04 beta 2003-08-24 +------------------------- +- Favorites menu in 7-Zip File Manager +- Some bugs were fixed + + +3.08.03 beta 2003-08-21 +------------------------- +- Automatic adding of extension to archive name in Compress Dialog +- Some bugs in previous 3.08.* versions were fixed: + - Storing columns width inside archives in File Manager + - Opening archive inside archive + - Quotes in list files in console version + + +3.08.02 beta 2003-08-20 +------------------------- +- Some bugs were fixed + + +3.08 beta 2003-08-19 +------------------------- +- Compress dialog: + - Supporting fast compressing mode (-mx=1 switch) + - Multi-threading option for Multi-Processor systems + or Pentium 4 with Hyper-Threading + - Encrypt file names option +- New context menu items: + - Extract here + - Extract to + - Compress and email +- Internal reconstruction, registry using was reduced +- New localization: Esperanto + + +2.30 Beta 32 2003-05-15 +------------------------- +- New features in compressing / decompressing window. +- "Show password" option. +- Some other small changes. +- New localization: Valencian. + + +2.30 Beta 31 2003-04-29 +------------------------- +- Some bugs were fixed. + + +2.30 Beta 30 2003-04-19 +------------------------- +- 7-Zip File Manager: + - Showing .. item. + - 1/2 Panels mode switching (F9). +- Supporting Bzip2 compression in ZIP archives. +- Some bugs were fixed. +- Some optimization recompiling for reducing code size. + + +2.30 Beta 29 2003-04-07 +------------------------- +- 7-Zip File Manager: + - "7-Zip" and "System" submenus in "Files" menu. + - Path history and "Browse" button in "Copy" dialog. +- RAR supporting was improved. +- Some bugs were fixed. +- Small changes in LZMA code. +- New localizations: Hebrew, Vietnamese. + + +2.30 Beta 28 2003-02-16 +------------------------- +- Some bugs were fixed: + - Updating 7z archives that are larger than 4 GB. + - Using anti-items in 7z format. + - Compressing empty files with password to zip format. +- In max mode 7z now uses 8 MB dictionary instead of 4 MB. +- 7-Zip File Manager: + - Supporting file comments: Ctrl-Z. + - New key alias for folder bookmarks: [Shift]+Alt+Number. + + +2.30 Beta 27 2003-01-24 +------------------------- +- Two BUGs in two previous beta versions (Beta 25 and Beta 26) + were fixed: + 1. Incorrect compressing to non-solid 7z archive + when files have some very big sizes: + 4 GB, 8 GB, 12 GB, 16 GB, ... + 2. Incorrect percent showing in 7z compressing + when files are bigger than 4 GB. +- Supporting multivolume RAR and SPLIT archives. +- Supporting DEB archives. +- Supporting old version of CPIO format. +- Some bugs were fixed. +- New localizations: Korean, Swedish. + + +2.30 Beta 26 2003-01-12 +------------------------- +- Supporting Deflate64 method in Zip archives. +- Supporting Rar 1.50 archives. +- Some bugs were fixed. + + +2.30 Beta 25 2003-01-02 +------------------------- +- Encryption feature for 7z format (AES-256). +- New optional archive header compressing mode (-mhcf). +- Archive headers now always are compressed with LZMA method. +- Updating non-solid 7z archives without -ms=off now is allowed. +- Folder creating and item renaming inside archive now is supported. +- Supporting encrypted Rar3 archives. +- Supporting Unicode names in Rar3 archives. +- Some bugs were fixed. +- New localizations: Lithuanian, Voro. + + +2.30 Beta 24 2002-11-01 +------------------------- +- Some internal reconstructions. +- -m switch syntax was slightly changed. +- Some bugs were fixed. +- New localizations: Catalan, Norwegian, Romanian. + + +2.30 Beta 23 2002-09-07 +------------------------- +- Encryption feature for zip format. +- Percent indicating for some operations. +- Some bugs were fixed. + + +2.30 Beta 22 2002-08-31 +------------------------- +- New program: 7-Zip File Manager. +- Command line version now doesn't allow absolute paths + for compressing files. +- New localizations: Belarusian, Greek. +- Bug in FAR plugin was fixed: + Incorrect updating when archive has no explicit + directory items for file items. +- Some bugs were fixed. + + +2.30 Beta 21 2002-07-08 +------------------------- +- RAM requirements for LZMA (7z) compression were reduced. +- Small bug in FAR plugin was fixed. + + +2.30 Beta 20 2002-07-01 +------------------------- +- RAM requirements for LZMA (7z) decompression were reduced. +- New localization: Turkish. +- Some bugs were fixed. + + +2.30 Beta 19 2002-04-11 +------------------------- +- Supporting RAR 3.0 archives. +- New localizations: Danish, Ukrainian. + + +2.30 Beta 18 2002-03-25 +------------------------- +- Compressing speed in 7z format was slightly increased. +- New localizations: Estonian, Finnish. +- Some bugs were fixed. + + +2.30 Beta 17 2002-03-03 +------------------------- +- Supporting ARJ archives. +- New localization: Chinese Simplified. + + +2.30 Beta 16 2002-02-24 +------------------------- +- Supporting RPM and CPIO archives. +- New fast compression mode for LZMA: -m0a=0. +- New match finders for LZMA: bt4b, hc3, hc4. +- Some bugs were fixed. + + +2.30 Beta 15 2002-02-17 +------------------------- +- Compression ratio in 7z was slightly improved. +- New localization: Dutch. + + +2.30 Beta 14 2002-02-10 +------------------------- +- Speed optimization for multiprocessor computers (-mmt switch). +- New localizations: Czech, Japanese, Polish. +- Some bugs were fixed. + + +2.30 Beta 13 2002-01-31 +------------------------- +- New SFX module for installers. +- New match finder for LZMA: bt3. +- New localizations: Portuguese, Portuguese Brazil, Serbo-Croatian. +- Some bugs were fixed. + + +2.30 Beta 12 2002-01-16 +------------------------- +- Bug was fixed: memory leak in Beta 11. +- New localization: Hungarian. + + +2.30 Beta 11 2002-01-15 +------------------------- +- Archive testing feature for GUI version. +- Now 7-Zip can use more than 256 MB of RAM in all Windows versions. +- New localizations: Bulgarian, Chinese Traditional, Latvian, Slovak. +- Some bugs were fixed. + + +2.30 Beta 10 2002-01-11 +------------------------- +- Bugs were fixed: + - Updating 7z archives in Beta 8 and 9 didn't work. + - Unicode version in Beta 9 didn't work in Windows NT4. + - Some other bugs were fixed. +- New localizations: Arabic, French, Italian, Slovenian, Spanish. + + +2.30 Beta 9 2002-01-08 +------------------------- +- Program localization: English, German, Russian. +- Additional optimized versions of programs + for Windows NT4/2000/XP. +- Two new match finders for LZMA: pat3h and pat4h. +- Some bugs were fixed. + + +2.30 Beta 8 2001-12-21 +------------------------- +- 7-Zip now supports some zip archives that were not + supported by previous versions. +- 7-Zip now supports new state (-uw switch) for cases + when 7-Zip can not detect whether file is newer or the same. +- Supporting anti-items in 7z format for incremental + update (-u with action #3). +- Some bugs were fixed. + + +2.30 Beta 7 2001-11-04 +------------------------- +- BCJ2: new converter for x86 code. +- Supporting tar archives with very long file names + (GNU extension to 'tar' format). +- Supporting multistream coders in 7z (-mb switch). +- More compressing parameters for zip and gzip + in console version (-m switch). +- Solid compressing option in Windows version. +- Compressing parameters option in Windows version. +- Auto renaming existing files feature for + extracting files. +- Overwrite mode switch for extracting (-ao). +- Some bugs were fixed. + + +2.30 Beta 6 2001-10-13 +------------------------- +- Supporting 7z format in MultiArc plugin for FAR Manager. +- Some bugs were fixed. + + +2.30 Beta 5 2001-10-02 +------------------------- +- Creating SFX archives from explorer. +- 7zWin.sfx: Windows version of SFX module. +- Auto adding .exe extension to SFX archive name. +- 7za.exe now supports 7z, bzip2, gzip, tar, zip. +- Some bugs were fixed. + + +2.30 Beta 4 2001-09-15 +------------------------- +- Self extract capability for 7z format. +- 7z archive format is default for 7z.exe and 7za.exe. +- 7z in default mode now uses bt234 match finder + and solid compression. +- 7z in maximum mode (-mx) now uses 4MB dictionary. + + +2.30 Beta 3 2001-09-10 +------------------------- +- Bug was fixed: decompressing .7z solid archives + containing empty files. +- new 7za.exe: standalone command line version + (only for 7z format). +- Speed of compressing to Deflate format (zip, gzip) + was slightly increased. + + +2.30 Beta 2 2001-08-30 +------------------------- +- Supporting the new 7z format with high compression ratio. +- -bd (Disable percentage indicator) switch in + console version. +- Bug in console version was fixed: + previous versions incorrectly execute compression + commands with non-recursive wildcards in combination + with subfolders. +- Some other bugs were fixed. + + +2.30 Beta 1 2001-05-07 +------------------------- +- Speed of reading of archive contents was increased. +- Bug was fixed: incorrect showing file names with + national charsets in some zip archives. +- Now it is possible to compress files larger than 4GB + to GZip archives. + + +2.24 2001-03-21 +------------------------- +- Bugs in GZip and Cab decoders were fixed. + + +2.23 2001-03-04 +------------------------- +- Opening archive items in Explorer. +- Context menu for archive items in Explorer. +- Automatic adding extension to archive name in console version. +- Some bugs were fixed. + + +2.22 2001-01-21 +------------------------- +- Supporting Zip archives containing more than 65535 files. +- Speed of Plugin for Explorer was increased. +- Searching start position of archive now is limited by + first 1MB part of file. +- Some bugs were fixed. +- Packet now doesn't contain 7zip.exe, far7zip.reg and + far7zip2.reg files. There is new far7z.reg file. + + +2.21 2000-12-21 +------------------------- +- FAR Plugin was improved: + + - Showing process box during opening archives. + - Viewing properties of file by Ctrl-A. + - Alt-F6 in archive now immediately extracts selected files + to current directory. + +- Some bugs were fixed: + + - Entering to archive's subfolders in Explorer by clicking + items in main window didn't work under Windows ME/2000. + - Decompressing solid Rar archives sometimes gave error. + - Console version 7z.exe during list operation incorrectly + showed file names with national (non-english) charsets. + - FAR Plugin didn't execute some operations. + - Showing percents during extracting ZIP archives sometimes + was incorrect. + + +2.20 2000-11-20 +------------------------- +- Supporting BZip2 and Cab. +- New program architecture with external + compression and cryptographic modules. +- Decryption support (Rar and Zip). +- New console client. +- Some bugs were fixed. + + +2.11 2000-06-15 +------------------------- +- Bugs were fixed: + + - FAR Plugin incorrectly processed + names of subdirectories that use national + (non-english) charsets. + - gzip plugin could not compress empty files. + + +2.10 2000-05-16 +------------------------- +- First level 7-Zip Plugin for FAR Manager. +- GUI version with integration to Windows Shell. +- Compression and decompressing GZip and TAR formats. +- Decompression RAR. +- Install & Uninstall support. +- Some bugs were fixed. + + +2.01 1999-09-19 +------------------------- +- Small bug was fixed. +- Compression ratio was improved for some files. + + +2.00 1999-07-18 +------------------------- +- Release. +- Big bug was fixed: previous versions incorrectly worked + during compressing with files that were referred by + direct(without wildcards) paths, containing subdirs parts. +- Compression and decompression speed were improved. +- -mx switch (maXimize compression) was added. +- Small bugs were fixed. + + +2.00 Beta 1 1999-01-02 +------------------------- +- Original beta version. + + +End of document diff --git a/Moose Mission Setup/Moose Mission Update/License.txt b/Moose Mission Setup/Moose Mission Update/License.txt new file mode 100644 index 000000000..f1e7690ee --- /dev/null +++ b/Moose Mission Setup/Moose Mission Update/License.txt @@ -0,0 +1,56 @@ + 7-Zip + ~~~~~ + License for use and distribution + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + 7-Zip Copyright (C) 1999-2016 Igor Pavlov. + + Licenses for files are: + + 1) 7z.dll: GNU LGPL + unRAR restriction + 2) All other files: GNU LGPL + + The GNU LGPL + unRAR restriction means that you must follow both + GNU LGPL rules and unRAR restriction rules. + + + Note: + You can use 7-Zip on any computer, including a computer in a commercial + organization. You don't need to register or pay for 7-Zip. + + + GNU LGPL information + -------------------- + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You can receive a copy of the GNU Lesser General Public License from + http://www.gnu.org/ + + + unRAR restriction + ----------------- + + The decompression engine for RAR archives was developed using source + code of unRAR program. + All copyrights to original unRAR code are owned by Alexander Roshal. + + The license for original unRAR code has the following restriction: + + The unRAR sources cannot be used to re-create the RAR compression algorithm, + which is proprietary. Distribution of modified unRAR sources in separate form + or as a part of other software is permitted, provided that it is clearly + stated in the documentation and source comments that the code may + not be used to develop a RAR (WinRAR) compatible archiver. + + + -- + Igor Pavlov diff --git a/Moose Mission Setup/Moose Mission Update/Moose_Update_Missions.bat b/Moose Mission Setup/Moose Mission Update/Moose_Update_Missions.bat index 3403b852c..7db429fef 100644 --- a/Moose Mission Setup/Moose Mission Update/Moose_Update_Missions.bat +++ b/Moose Mission Setup/Moose Mission Update/Moose_Update_Missions.bat @@ -1,7 +1,7 @@ echo off rem Update Missions with a new version of Moose.lua -rem Run this batch file with the following command arguments in Eclipse: "${resource_loc:/Moose/Moose Development/Moose}" "${current_date}" +rem Provide as the only parameter the path to the .miz files, which can be embedded in directories. echo Path to Mission Files: %1 diff --git a/Moose Mission Setup/Moose Mission Update/Uninstall.exe b/Moose Mission Setup/Moose Mission Update/Uninstall.exe new file mode 100644 index 000000000..e214183c8 Binary files /dev/null and b/Moose Mission Setup/Moose Mission Update/Uninstall.exe differ diff --git a/Moose Mission Setup/Moose Mission Update/descript.ion b/Moose Mission Setup/Moose Mission Update/descript.ion new file mode 100644 index 000000000..914077b90 --- /dev/null +++ b/Moose Mission Setup/Moose Mission Update/descript.ion @@ -0,0 +1,14 @@ +7-zip.chm 7-Zip Help +7-Zip.dll 7-Zip Plugin +7-Zip32.dll 7-Zip Plugin 32-bit +7z.dll 7-Zip Engine +7z.exe 7-Zip Console +7z.sfx 7-Zip GUI SFX +7zCon.sfx 7-Zip Console SFX +7zFM.exe 7-Zip File Manager +7zg.exe 7-Zip GUI +descript.ion 7-Zip File Descriptions +history.txt 7-Zip History +Lang 7-Zip Translations +license.txt 7-Zip License +readme.txt 7-Zip Overview diff --git a/Moose Mission Setup/Moose Mission Update/l10n/DEFAULT/Moose.lua b/Moose Mission Setup/Moose Mission Update/l10n/DEFAULT/Moose.lua index 8db5aea17..ac46b458e 100644 --- a/Moose Mission Setup/Moose Mission Update/l10n/DEFAULT/Moose.lua +++ b/Moose Mission Setup/Moose Mission Update/l10n/DEFAULT/Moose.lua @@ -1,5 +1,5 @@ env.info( '*** MOOSE STATIC INCLUDE START *** ' ) -env.info( 'Moose Generation Timestamp: 20161205_1227' ) +env.info( 'Moose Generation Timestamp: 20161216_1640' ) local base = _G Include = {} @@ -2495,6 +2495,19 @@ end env.info(( 'Init: Scripts Loaded v1.1' )) +--- This module contains derived utilities taken from the MIST framework, +-- which are excellent tools to be reused in an OO environment!. +-- +-- ### Authors: +-- +-- * Grimes : Design & Programming of the MIST framework. +-- +-- ### Contributions: +-- +-- * FlightControl : Rework to OO framework +-- +-- @module Utils + --- @type SMOKECOLOR -- @field Green @@ -2597,11 +2610,11 @@ UTILS.OneLineSerialize = function( tbl ) -- serialization of a table all on a s tbl_str[#tbl_str + 1] = table.concat(val_str) end elseif type(val) == 'function' then - -- tbl_str[#tbl_str + 1] = "function " .. tostring(ind) - -- tbl_str[#tbl_str + 1] = ',' --I think this is right, I just added it + tbl_str[#tbl_str + 1] = "f() " .. tostring(ind) + tbl_str[#tbl_str + 1] = ',' --I think this is right, I just added it else --- env.info('unable to serialize value type ' .. routines.utils.basicSerialize(type(val)) .. ' at index ' .. tostring(ind)) --- env.info( debug.traceback() ) + env.info('unable to serialize value type ' .. routines.utils.basicSerialize(type(val)) .. ' at index ' .. tostring(ind)) + env.info( debug.traceback() ) end end @@ -2792,8 +2805,8 @@ end -- -- 1.1) BASE constructor -- --------------------- --- Any class derived from BASE, must use the @{Base#BASE.New) constructor within the @{Base#BASE.Inherit) method. --- See an example at the @{Base#BASE.New} method how this is done. +-- Any class derived from BASE, must use the @{Core.Base#BASE.New) constructor within the @{Core.Base#BASE.Inherit) method. +-- See an example at the @{Core.Base#BASE.New} method how this is done. -- -- 1.2) BASE Trace functionality -- ----------------------------- @@ -2869,24 +2882,6 @@ FORMATION = { ---- The base constructor. This is the top top class of all classed defined within the MOOSE. --- Any new class needs to be derived from this class for proper inheritance. --- @param #BASE self --- @return #BASE The new instance of the BASE class. --- @usage --- -- This declares the constructor of the class TASK, inheriting from BASE. --- --- TASK constructor --- -- @param #TASK self --- -- @param Parameter The parameter of the New constructor. --- -- @return #TASK self --- function TASK:New( Parameter ) --- --- local self = BASE:Inherit( self, BASE:New() ) --- --- self.Variable = Parameter --- --- return self --- end -- @todo need to investigate if the deepCopy is really needed... Don't think so. function BASE:New() local self = routines.utils.deepCopy( self ) -- Create a new self instance @@ -2895,9 +2890,40 @@ function BASE:New() self.__index = self _ClassID = _ClassID + 1 self.ClassID = _ClassID + + return self end +function BASE:_Destructor() + --self:E("_Destructor") + + --self:EventRemoveAll() +end + +function BASE:_SetDestructor() + + -- TODO: Okay, this is really technical... + -- When you set a proxy to a table to catch __gc, weak tables don't behave like weak... + -- Therefore, I am parking this logic until I've properly discussed all this with the community. + --[[ + local proxy = newproxy(true) + local proxyMeta = getmetatable(proxy) + + proxyMeta.__gc = function () + env.info("In __gc for " .. self:GetClassNameAndID() ) + if self._Destructor then + self:_Destructor() + end + end + + -- keep the userdata from newproxy reachable until the object + -- table is about to be garbage-collected - then the __gc hook + -- will be invoked and the destructor called + rawset( self, '__proxy', proxy ) + --]] +end + --- This is the worker method to inherit from a parent class. -- @param #BASE self -- @param Child is the Child class that inherits. @@ -2910,6 +2936,8 @@ function BASE:Inherit( Child, Parent ) if Child ~= nil then setmetatable( Child, Parent ) Child.__index = Child + + Child:_SetDestructor() end --self:T( 'Inherited from ' .. Parent.ClassName ) return Child @@ -2949,7 +2977,7 @@ end --- Set a new listener for the class. -- @param self --- @param DCSTypes#Event Event +-- @param Dcs.DCSTypes#Event Event -- @param #function EventFunction -- @return #BASE function BASE:AddEvent( Event, EventFunction ) @@ -2965,12 +2993,278 @@ end --- Returns the event dispatcher -- @param #BASE self --- @return Event#EVENT +-- @return Core.Event#EVENT function BASE:Event() return _EVENTDISPATCHER end +--- Remove all subscribed events +-- @param #BASE self +-- @return #BASE +function BASE:EventRemoveAll() + + _EVENTDISPATCHER:RemoveAll( self ) + + return self +end + +--- Subscribe to a S_EVENT_SHOT event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnShot( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_SHOT ) + + return self +end + +--- Subscribe to a S_EVENT_HIT event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnHit( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_HIT ) + + return self +end + +--- Subscribe to a S_EVENT_TAKEOFF event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnTakeOff( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_TAKEOFF ) + + return self +end + +--- Subscribe to a S_EVENT_LAND event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnLand( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_LAND ) + + return self +end + +--- Subscribe to a S_EVENT_CRASH event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnCrash( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_CRASH ) + + return self +end + +--- Subscribe to a S_EVENT_EJECTION event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnEjection( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_EJECTION ) + + return self +end + + +--- Subscribe to a S_EVENT_REFUELING event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnRefueling( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_REFUELING ) + + return self +end + +--- Subscribe to a S_EVENT_DEAD event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnDead( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_DEAD ) + + return self +end + +--- Subscribe to a S_EVENT_PILOT_DEAD event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnPilotDead( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_PILOT_DEAD ) + + return self +end + +--- Subscribe to a S_EVENT_BASE_CAPTURED event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnBaseCaptured( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_BASE_CAPTURED ) + + return self +end + +--- Subscribe to a S_EVENT_MISSION_START event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnMissionStart( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_MISSION_START ) + + return self +end + +--- Subscribe to a S_EVENT_MISSION_END event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnPlayerMissionEnd( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_MISSION_END ) + + return self +end + +--- Subscribe to a S_EVENT_TOOK_CONTROL event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnTookControl( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_TOOK_CONTROL ) + + return self +end + +--- Subscribe to a S_EVENT_REFUELING_STOP event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnRefuelingStop( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_REFUELING_STOP ) + + return self +end + +--- Subscribe to a S_EVENT_BIRTH event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnBirth( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_BIRTH ) + + return self +end + +--- Subscribe to a S_EVENT_HUMAN_FAILURE event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnHumanFailure( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_HUMAN_FAILURE ) + + return self +end + +--- Subscribe to a S_EVENT_ENGINE_STARTUP event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnEngineStartup( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_ENGINE_STARTUP ) + + return self +end + +--- Subscribe to a S_EVENT_ENGINE_SHUTDOWN event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnEngineShutdown( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_ENGINE_SHUTDOWN ) + + return self +end + +--- Subscribe to a S_EVENT_PLAYER_ENTER_UNIT event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnPlayerEnterUnit( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_PLAYER_ENTER_UNIT ) + + return self +end + +--- Subscribe to a S_EVENT_PLAYER_LEAVE_UNIT event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnPlayerLeaveUnit( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_PLAYER_LEAVE_UNIT ) + + return self +end + +--- Subscribe to a S_EVENT_PLAYER_COMMENT event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnPlayerComment( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_PLAYER_COMMENT ) + + return self +end + +--- Subscribe to a S_EVENT_SHOOTING_START event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnShootingStart( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_SHOOTING_START ) + + return self +end + +--- Subscribe to a S_EVENT_SHOOTING_END event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnShootingEnd( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_SHOOTING_END ) + + return self +end + + + @@ -3047,8 +3341,8 @@ local BaseEventCodes = { --- Creation of a Birth Event. -- @param #BASE self --- @param DCSTypes#Time EventTime The time stamp of the event. --- @param DCSObject#Object Initiator The initiating object of the event. +-- @param Dcs.DCSTypes#Time EventTime The time stamp of the event. +-- @param Dcs.DCSWrapper.Object#Object Initiator The initiating object of the event. -- @param #string IniUnitName The initiating unit name. -- @param place -- @param subplace @@ -3069,8 +3363,8 @@ end --- Creation of a Crash Event. -- @param #BASE self --- @param DCSTypes#Time EventTime The time stamp of the event. --- @param DCSObject#Object Initiator The initiating object of the event. +-- @param Dcs.DCSTypes#Time EventTime The time stamp of the event. +-- @param Dcs.DCSWrapper.Object#Object Initiator The initiating object of the event. function BASE:CreateEventCrash( EventTime, Initiator ) self:F( { EventTime, Initiator } ) @@ -3083,10 +3377,10 @@ function BASE:CreateEventCrash( EventTime, Initiator ) world.onEvent( Event ) end --- TODO: Complete DCSTypes#Event structure. +-- TODO: Complete Dcs.DCSTypes#Event structure. --- The main event handling function... This function captures all events generated for the class. -- @param #BASE self --- @param DCSTypes#Event event +-- @param Dcs.DCSTypes#Event event function BASE:onEvent(event) --self:F( { BaseEventCodes[event.id], event } ) @@ -3129,7 +3423,7 @@ function BASE:GetState( Object, StateName ) local ClassNameAndID = Object:GetClassNameAndID() if self.States[ClassNameAndID] then - local State = self.States[ClassNameAndID][StateName] + local State = self.States[ClassNameAndID][StateName] or false self:T2( { ClassNameAndID, StateName, State } ) return State end @@ -3401,2947 +3695,46 @@ end ---- This module contains the OBJECT class. --- --- 1) @{Object#OBJECT} class, extends @{Base#BASE} --- =========================================================== --- The @{Object#OBJECT} class is a wrapper class to handle the DCS Object objects: --- --- * Support all DCS Object APIs. --- * Enhance with Object specific APIs not in the DCS Object API set. --- * Manage the "state" of the DCS Object. --- --- 1.1) OBJECT constructor: --- ------------------------------ --- The OBJECT class provides the following functions to construct a OBJECT instance: --- --- * @{Object#OBJECT.New}(): Create a OBJECT instance. --- --- 1.2) OBJECT methods: --- -------------------------- --- The following methods can be used to identify an Object object: --- --- * @{Object#OBJECT.GetID}(): Returns the ID of the Object object. --- --- === --- --- @module Object --- @author FlightControl - ---- The OBJECT class --- @type OBJECT --- @extends Base#BASE --- @field #string ObjectName The name of the Object. -OBJECT = { - ClassName = "OBJECT", - ObjectName = "", -} - - ---- A DCSObject --- @type DCSObject --- @field id_ The ID of the controllable in DCS - ---- Create a new OBJECT from a DCSObject --- @param #OBJECT self --- @param DCSObject#Object ObjectName The Object name --- @return #OBJECT self -function OBJECT:New( ObjectName ) - local self = BASE:Inherit( self, BASE:New() ) - self:F2( ObjectName ) - self.ObjectName = ObjectName - return self -end - - ---- Returns the unit's unique identifier. --- @param Object#OBJECT self --- @return DCSObject#Object.ID ObjectID --- @return #nil The DCS Object is not existing or alive. -function OBJECT:GetID() - self:F2( self.ObjectName ) - - local DCSObject = self:GetDCSObject() - - if DCSObject then - local ObjectID = DCSObject:getID() - return ObjectID - end - - return nil -end - ---- Destroys the OBJECT. --- @param #OBJECT self --- @return #nil The DCS Unit is not existing or alive. -function OBJECT:Destroy() - self:F2( self.ObjectName ) - - local DCSObject = self:GetDCSObject() - - if DCSObject then - - DCSObject:destroy() - end - - return nil -end - - - - ---- This module contains the IDENTIFIABLE class. --- --- 1) @{Identifiable#IDENTIFIABLE} class, extends @{Object#OBJECT} --- =============================================================== --- The @{Identifiable#IDENTIFIABLE} class is a wrapper class to handle the DCS Identifiable objects: --- --- * Support all DCS Identifiable APIs. --- * Enhance with Identifiable specific APIs not in the DCS Identifiable API set. --- * Manage the "state" of the DCS Identifiable. --- --- 1.1) IDENTIFIABLE constructor: --- ------------------------------ --- The IDENTIFIABLE class provides the following functions to construct a IDENTIFIABLE instance: --- --- * @{Identifiable#IDENTIFIABLE.New}(): Create a IDENTIFIABLE instance. --- --- 1.2) IDENTIFIABLE methods: --- -------------------------- --- The following methods can be used to identify an identifiable object: --- --- * @{Identifiable#IDENTIFIABLE.GetName}(): Returns the name of the Identifiable. --- * @{Identifiable#IDENTIFIABLE.IsAlive}(): Returns if the Identifiable is alive. --- * @{Identifiable#IDENTIFIABLE.GetTypeName}(): Returns the type name of the Identifiable. --- * @{Identifiable#IDENTIFIABLE.GetCoalition}(): Returns the coalition of the Identifiable. --- * @{Identifiable#IDENTIFIABLE.GetCountry}(): Returns the country of the Identifiable. --- * @{Identifiable#IDENTIFIABLE.GetDesc}(): Returns the descriptor structure of the Identifiable. --- --- --- === --- --- @module Identifiable --- @author FlightControl - ---- The IDENTIFIABLE class --- @type IDENTIFIABLE --- @extends Object#OBJECT --- @field #string IdentifiableName The name of the identifiable. -IDENTIFIABLE = { - ClassName = "IDENTIFIABLE", - IdentifiableName = "", -} - -local _CategoryName = { - [Unit.Category.AIRPLANE] = "Airplane", - [Unit.Category.HELICOPTER] = "Helicoper", - [Unit.Category.GROUND_UNIT] = "Ground Identifiable", - [Unit.Category.SHIP] = "Ship", - [Unit.Category.STRUCTURE] = "Structure", - } - ---- Create a new IDENTIFIABLE from a DCSIdentifiable --- @param #IDENTIFIABLE self --- @param DCSIdentifiable#Identifiable IdentifiableName The DCS Identifiable name --- @return #IDENTIFIABLE self -function IDENTIFIABLE:New( IdentifiableName ) - local self = BASE:Inherit( self, OBJECT:New( IdentifiableName ) ) - self:F2( IdentifiableName ) - self.IdentifiableName = IdentifiableName - return self -end - ---- Returns if the Identifiable is alive. --- @param Identifiable#IDENTIFIABLE self --- @return #boolean true if Identifiable is alive. --- @return #nil The DCS Identifiable is not existing or alive. -function IDENTIFIABLE:IsAlive() - self:F2( self.IdentifiableName ) - - local DCSIdentifiable = self:GetDCSObject() - - if DCSIdentifiable then - local IdentifiableIsAlive = DCSIdentifiable:isExist() - return IdentifiableIsAlive - end - - return false -end - - - - ---- Returns DCS Identifiable object name. --- The function provides access to non-activated objects too. --- @param Identifiable#IDENTIFIABLE self --- @return #string The name of the DCS Identifiable. --- @return #nil The DCS Identifiable is not existing or alive. -function IDENTIFIABLE:GetName() - self:F2( self.IdentifiableName ) - - local DCSIdentifiable = self:GetDCSObject() - - if DCSIdentifiable then - local IdentifiableName = self.IdentifiableName - return IdentifiableName - end - - self:E( self.ClassName .. " " .. self.IdentifiableName .. " not found!" ) - return nil -end - - ---- Returns the type name of the DCS Identifiable. --- @param Identifiable#IDENTIFIABLE self --- @return #string The type name of the DCS Identifiable. --- @return #nil The DCS Identifiable is not existing or alive. -function IDENTIFIABLE:GetTypeName() - self:F2( self.IdentifiableName ) - - local DCSIdentifiable = self:GetDCSObject() - - if DCSIdentifiable then - local IdentifiableTypeName = DCSIdentifiable:getTypeName() - self:T3( IdentifiableTypeName ) - return IdentifiableTypeName - end - - self:E( self.ClassName .. " " .. self.IdentifiableName .. " not found!" ) - return nil -end - - ---- Returns category of the DCS Identifiable. --- @param #IDENTIFIABLE self --- @return DCSObject#Object.Category The category ID -function IDENTIFIABLE:GetCategory() - self:F2( self.ObjectName ) - - local DCSObject = self:GetDCSObject() - if DCSObject then - local ObjectCategory = DCSObject:getCategory() - self:T3( ObjectCategory ) - return ObjectCategory - end - - return nil -end - - ---- Returns the DCS Identifiable category name as defined within the DCS Identifiable Descriptor. --- @param Identifiable#IDENTIFIABLE self --- @return #string The DCS Identifiable Category Name -function IDENTIFIABLE:GetCategoryName() - local DCSIdentifiable = self:GetDCSObject() - - if DCSIdentifiable then - local IdentifiableCategoryName = _CategoryName[ self:GetDesc().category ] - return IdentifiableCategoryName - end - - self:E( self.ClassName .. " " .. self.IdentifiableName .. " not found!" ) - return nil -end - ---- Returns coalition of the Identifiable. --- @param Identifiable#IDENTIFIABLE self --- @return DCSCoalitionObject#coalition.side The side of the coalition. --- @return #nil The DCS Identifiable is not existing or alive. -function IDENTIFIABLE:GetCoalition() - self:F2( self.IdentifiableName ) - - local DCSIdentifiable = self:GetDCSObject() - - if DCSIdentifiable then - local IdentifiableCoalition = DCSIdentifiable:getCoalition() - self:T3( IdentifiableCoalition ) - return IdentifiableCoalition - end - - self:E( self.ClassName .. " " .. self.IdentifiableName .. " not found!" ) - return nil -end - ---- Returns country of the Identifiable. --- @param Identifiable#IDENTIFIABLE self --- @return DCScountry#country.id The country identifier. --- @return #nil The DCS Identifiable is not existing or alive. -function IDENTIFIABLE:GetCountry() - self:F2( self.IdentifiableName ) - - local DCSIdentifiable = self:GetDCSObject() - - if DCSIdentifiable then - local IdentifiableCountry = DCSIdentifiable:getCountry() - self:T3( IdentifiableCountry ) - return IdentifiableCountry - end - - self:E( self.ClassName .. " " .. self.IdentifiableName .. " not found!" ) - return nil -end - - - ---- Returns Identifiable descriptor. Descriptor type depends on Identifiable category. --- @param Identifiable#IDENTIFIABLE self --- @return DCSIdentifiable#Identifiable.Desc The Identifiable descriptor. --- @return #nil The DCS Identifiable is not existing or alive. -function IDENTIFIABLE:GetDesc() - self:F2( self.IdentifiableName ) - - local DCSIdentifiable = self:GetDCSObject() - - if DCSIdentifiable then - local IdentifiableDesc = DCSIdentifiable:getDesc() - self:T2( IdentifiableDesc ) - return IdentifiableDesc - end - - self:E( self.ClassName .. " " .. self.IdentifiableName .. " not found!" ) - return nil -end - - - - - - - - - ---- This module contains the POSITIONABLE class. --- --- 1) @{Positionable#POSITIONABLE} class, extends @{Identifiable#IDENTIFIABLE} --- =========================================================== --- The @{Positionable#POSITIONABLE} class is a wrapper class to handle the POSITIONABLE objects: --- --- * Support all DCS APIs. --- * Enhance with POSITIONABLE specific APIs not in the DCS API set. --- * Manage the "state" of the POSITIONABLE. --- --- 1.1) POSITIONABLE constructor: --- ------------------------------ --- The POSITIONABLE class provides the following functions to construct a POSITIONABLE instance: --- --- * @{Positionable#POSITIONABLE.New}(): Create a POSITIONABLE instance. --- --- 1.2) POSITIONABLE methods: --- -------------------------- --- The following methods can be used to identify an measurable object: --- --- * @{Positionable#POSITIONABLE.GetID}(): Returns the ID of the measurable object. --- * @{Positionable#POSITIONABLE.GetName}(): Returns the name of the measurable object. --- --- === --- --- @module Positionable --- @author FlightControl - ---- The POSITIONABLE class --- @type POSITIONABLE --- @extends Identifiable#IDENTIFIABLE --- @field #string PositionableName The name of the measurable. -POSITIONABLE = { - ClassName = "POSITIONABLE", - PositionableName = "", -} - ---- A DCSPositionable --- @type DCSPositionable --- @field id_ The ID of the controllable in DCS - ---- Create a new POSITIONABLE from a DCSPositionable --- @param #POSITIONABLE self --- @param DCSPositionable#Positionable PositionableName The POSITIONABLE name --- @return #POSITIONABLE self -function POSITIONABLE:New( PositionableName ) - local self = BASE:Inherit( self, IDENTIFIABLE:New( PositionableName ) ) - - return self -end - ---- Returns the @{DCSTypes#Position3} position vectors indicating the point and direction vectors in 3D of the POSITIONABLE within the mission. --- @param Positionable#POSITIONABLE self --- @return DCSTypes#Position The 3D position vectors of the POSITIONABLE. --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:GetPositionVec3() - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local PositionablePosition = DCSPositionable:getPosition() - self:T3( PositionablePosition ) - return PositionablePosition - end - - return nil -end - ---- Returns the @{DCSTypes#Vec2} vector indicating the point in 2D of the POSITIONABLE within the mission. --- @param Positionable#POSITIONABLE self --- @return DCSTypes#Vec2 The 2D point vector of the POSITIONABLE. --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:GetVec2() - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local PositionableVec3 = DCSPositionable:getPosition().p - - local PositionableVec2 = {} - PositionableVec2.x = PositionableVec3.x - PositionableVec2.y = PositionableVec3.z - - self:T2( PositionableVec2 ) - return PositionableVec2 - end - - return nil -end - ---- Returns a POINT_VEC2 object indicating the point in 2D of the POSITIONABLE within the mission. --- @param Positionable#POSITIONABLE self --- @return Point#POINT_VEC2 The 2D point vector of the POSITIONABLE. --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:GetPointVec2() - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local PositionableVec3 = DCSPositionable:getPosition().p - - local PositionablePointVec2 = POINT_VEC2:NewFromVec3( PositionableVec3 ) - - self:T2( PositionablePointVec2 ) - return PositionablePointVec2 - end - - return nil -end - - ---- Returns a random @{DCSTypes#Vec3} vector within a range, indicating the point in 3D of the POSITIONABLE within the mission. --- @param Positionable#POSITIONABLE self --- @return DCSTypes#Vec3 The 3D point vector of the POSITIONABLE. --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:GetRandomVec3( Radius ) - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local PositionablePointVec3 = DCSPositionable:getPosition().p - local PositionableRandomVec3 = {} - local angle = math.random() * math.pi*2; - PositionableRandomVec3.x = PositionablePointVec3.x + math.cos( angle ) * math.random() * Radius; - PositionableRandomVec3.y = PositionablePointVec3.y - PositionableRandomVec3.z = PositionablePointVec3.z + math.sin( angle ) * math.random() * Radius; - - self:T3( PositionableRandomVec3 ) - return PositionableRandomVec3 - end - - return nil -end - ---- Returns the @{DCSTypes#Vec3} vector indicating the 3D vector of the POSITIONABLE within the mission. --- @param Positionable#POSITIONABLE self --- @return DCSTypes#Vec3 The 3D point vector of the POSITIONABLE. --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:GetVec3() - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local PositionableVec3 = DCSPositionable:getPosition().p - self:T3( PositionableVec3 ) - return PositionableVec3 - end - - return nil -end - ---- Returns the altitude of the POSITIONABLE. --- @param Positionable#POSITIONABLE self --- @return DCSTypes#Distance The altitude of the POSITIONABLE. --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:GetAltitude() - self:F2() - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local PositionablePointVec3 = DCSPositionable:getPoint() --DCSTypes#Vec3 - return PositionablePointVec3.y - end - - return nil -end - ---- Returns if the Positionable is located above a runway. --- @param Positionable#POSITIONABLE self --- @return #boolean true if Positionable is above a runway. --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:IsAboveRunway() - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - - local Vec2 = self:GetVec2() - local SurfaceType = land.getSurfaceType( Vec2 ) - local IsAboveRunway = SurfaceType == land.SurfaceType.RUNWAY - - self:T2( IsAboveRunway ) - return IsAboveRunway - end - - return nil -end - - - ---- Returns the POSITIONABLE heading in degrees. --- @param Positionable#POSITIONABLE self --- @return #number The POSTIONABLE heading -function POSITIONABLE:GetHeading() - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - - local PositionablePosition = DCSPositionable:getPosition() - if PositionablePosition then - local PositionableHeading = math.atan2( PositionablePosition.x.z, PositionablePosition.x.x ) - if PositionableHeading < 0 then - PositionableHeading = PositionableHeading + 2 * math.pi - end - PositionableHeading = PositionableHeading * 180 / math.pi - self:T2( PositionableHeading ) - return PositionableHeading - end - end - - return nil -end - - ---- Returns true if the POSITIONABLE is in the air. --- @param Positionable#POSITIONABLE self --- @return #boolean true if in the air. --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:InAir() - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local PositionableInAir = DCSPositionable:inAir() - self:T3( PositionableInAir ) - return PositionableInAir - end - - return nil -end - ---- Returns the POSITIONABLE velocity vector. --- @param Positionable#POSITIONABLE self --- @return DCSTypes#Vec3 The velocity vector --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:GetVelocity() - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local PositionableVelocityVec3 = DCSPositionable:getVelocity() - self:T3( PositionableVelocityVec3 ) - return PositionableVelocityVec3 - end - - return nil -end - ---- Returns the POSITIONABLE velocity in km/h. --- @param Positionable#POSITIONABLE self --- @return #number The velocity in km/h --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:GetVelocityKMH() - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local VelocityVec3 = self:GetVelocity() - local Velocity = ( VelocityVec3.x ^ 2 + VelocityVec3.y ^ 2 + VelocityVec3.z ^ 2 ) ^ 0.5 -- in meters / sec - local Velocity = Velocity * 3.6 -- now it is in km/h. - self:T3( Velocity ) - return Velocity - end - - return nil -end - - - - ---- This module contains the CONTROLLABLE class. --- --- 1) @{Controllable#CONTROLLABLE} class, extends @{Positionable#POSITIONABLE} --- =========================================================== --- The @{Controllable#CONTROLLABLE} class is a wrapper class to handle the DCS Controllable objects: --- --- * Support all DCS Controllable APIs. --- * Enhance with Controllable specific APIs not in the DCS Controllable API set. --- * Handle local Controllable Controller. --- * Manage the "state" of the DCS Controllable. --- --- 1.1) CONTROLLABLE constructor --- ----------------------------- --- The CONTROLLABLE class provides the following functions to construct a CONTROLLABLE instance: --- --- * @{#CONTROLLABLE.New}(): Create a CONTROLLABLE instance. --- --- 1.2) CONTROLLABLE task methods --- ------------------------------ --- Several controllable task methods are available that help you to prepare tasks. --- These methods return a string consisting of the task description, which can then be given to either a @{Controllable#CONTROLLABLE.PushTask} or @{Controllable#SetTask} method to assign the task to the CONTROLLABLE. --- Tasks are specific for the category of the CONTROLLABLE, more specific, for AIR, GROUND or AIR and GROUND. --- Each task description where applicable indicates for which controllable category the task is valid. --- There are 2 main subdivisions of tasks: Assigned tasks and EnRoute tasks. --- --- ### 1.2.1) Assigned task methods --- --- Assigned task methods make the controllable execute the task where the location of the (possible) targets of the task are known before being detected. --- This is different from the EnRoute tasks, where the targets of the task need to be detected before the task can be executed. --- --- Find below a list of the **assigned task** methods: --- --- * @{#CONTROLLABLE.TaskAttackControllable}: (AIR) Attack a Controllable. --- * @{#CONTROLLABLE.TaskAttackMapObject}: (AIR) Attacking the map object (building, structure, e.t.c). --- * @{#CONTROLLABLE.TaskAttackUnit}: (AIR) Attack the Unit. --- * @{#CONTROLLABLE.TaskBombing}: (AIR) Delivering weapon at the point on the ground. --- * @{#CONTROLLABLE.TaskBombingRunway}: (AIR) Delivering weapon on the runway. --- * @{#CONTROLLABLE.TaskEmbarking}: (AIR) Move the controllable to a Vec2 Point, wait for a defined duration and embark a controllable. --- * @{#CONTROLLABLE.TaskEmbarkToTransport}: (GROUND) Embark to a Transport landed at a location. --- * @{#CONTROLLABLE.TaskEscort}: (AIR) Escort another airborne controllable. --- * @{#CONTROLLABLE.TaskFAC_AttackControllable}: (AIR + GROUND) The task makes the controllable/unit a FAC and orders the FAC to control the target (enemy ground controllable) destruction. --- * @{#CONTROLLABLE.TaskFireAtPoint}: (GROUND) Fire at a VEC2 point until ammunition is finished. --- * @{#CONTROLLABLE.TaskFollow}: (AIR) Following another airborne controllable. --- * @{#CONTROLLABLE.TaskHold}: (GROUND) Hold ground controllable from moving. --- * @{#CONTROLLABLE.TaskHoldPosition}: (AIR) Hold position at the current position of the first unit of the controllable. --- * @{#CONTROLLABLE.TaskLand}: (AIR HELICOPTER) Landing at the ground. For helicopters only. --- * @{#CONTROLLABLE.TaskLandAtZone}: (AIR) Land the controllable at a @{Zone#ZONE_RADIUS). --- * @{#CONTROLLABLE.TaskOrbitCircle}: (AIR) Orbit at the current position of the first unit of the controllable at a specified alititude. --- * @{#CONTROLLABLE.TaskOrbitCircleAtVec2}: (AIR) Orbit at a specified position at a specified alititude during a specified duration with a specified speed. --- * @{#CONTROLLABLE.TaskRefueling}: (AIR) Refueling from the nearest tanker. No parameters. --- * @{#CONTROLLABLE.TaskRoute}: (AIR + GROUND) Return a Misson task to follow a given route defined by Points. --- * @{#CONTROLLABLE.TaskRouteToVec2}: (AIR + GROUND) Make the Controllable move to a given point. --- * @{#CONTROLLABLE.TaskRouteToVec3}: (AIR + GROUND) Make the Controllable move to a given point. --- * @{#CONTROLLABLE.TaskRouteToZone}: (AIR + GROUND) Route the controllable to a given zone. --- * @{#CONTROLLABLE.TaskReturnToBase}: (AIR) Route the controllable to an airbase. --- --- ### 1.2.2) EnRoute task methods --- --- EnRoute tasks require the targets of the task need to be detected by the controllable (using its sensors) before the task can be executed: --- --- * @{#CONTROLLABLE.EnRouteTaskAWACS}: (AIR) Aircraft will act as an AWACS for friendly units (will provide them with information about contacts). No parameters. --- * @{#CONTROLLABLE.EnRouteTaskEngageControllable}: (AIR) Engaging a controllable. The task does not assign the target controllable to the unit/controllable to attack now; it just allows the unit/controllable to engage the target controllable as well as other assigned targets. --- * @{#CONTROLLABLE.EnRouteTaskEngageTargets}: (AIR) Engaging targets of defined types. --- * @{#CONTROLLABLE.EnRouteTaskEWR}: (AIR) Attack the Unit. --- * @{#CONTROLLABLE.EnRouteTaskFAC}: (AIR + GROUND) The task makes the controllable/unit a FAC and lets the FAC to choose a targets (enemy ground controllable) around as well as other assigned targets. --- * @{#CONTROLLABLE.EnRouteTaskFAC_EngageControllable}: (AIR + GROUND) The task makes the controllable/unit a FAC and lets the FAC to choose the target (enemy ground controllable) as well as other assigned targets. --- * @{#CONTROLLABLE.EnRouteTaskTanker}: (AIR) Aircraft will act as a tanker for friendly units. No parameters. --- --- ### 1.2.3) Preparation task methods --- --- There are certain task methods that allow to tailor the task behaviour: --- --- * @{#CONTROLLABLE.TaskWrappedAction}: Return a WrappedAction Task taking a Command. --- * @{#CONTROLLABLE.TaskCombo}: Return a Combo Task taking an array of Tasks. --- * @{#CONTROLLABLE.TaskCondition}: Return a condition section for a controlled task. --- * @{#CONTROLLABLE.TaskControlled}: Return a Controlled Task taking a Task and a TaskCondition. --- --- ### 1.2.4) Obtain the mission from controllable templates --- --- Controllable templates contain complete mission descriptions. Sometimes you want to copy a complete mission from a controllable and assign it to another: --- --- * @{#CONTROLLABLE.TaskMission}: (AIR + GROUND) Return a mission task from a mission template. --- --- 1.3) CONTROLLABLE Command methods --- -------------------------- --- Controllable **command methods** prepare the execution of commands using the @{#CONTROLLABLE.SetCommand} method: --- --- * @{#CONTROLLABLE.CommandDoScript}: Do Script command. --- * @{#CONTROLLABLE.CommandSwitchWayPoint}: Perform a switch waypoint command. --- --- 1.4) CONTROLLABLE Option methods --- ------------------------- --- Controllable **Option methods** change the behaviour of the Controllable while being alive. --- --- ### 1.4.1) Rule of Engagement: --- --- * @{#CONTROLLABLE.OptionROEWeaponFree} --- * @{#CONTROLLABLE.OptionROEOpenFire} --- * @{#CONTROLLABLE.OptionROEReturnFire} --- * @{#CONTROLLABLE.OptionROEEvadeFire} --- --- To check whether an ROE option is valid for a specific controllable, use: --- --- * @{#CONTROLLABLE.OptionROEWeaponFreePossible} --- * @{#CONTROLLABLE.OptionROEOpenFirePossible} --- * @{#CONTROLLABLE.OptionROEReturnFirePossible} --- * @{#CONTROLLABLE.OptionROEEvadeFirePossible} --- --- ### 1.4.2) Rule on thread: --- --- * @{#CONTROLLABLE.OptionROTNoReaction} --- * @{#CONTROLLABLE.OptionROTPassiveDefense} --- * @{#CONTROLLABLE.OptionROTEvadeFire} --- * @{#CONTROLLABLE.OptionROTVertical} --- --- To test whether an ROT option is valid for a specific controllable, use: --- --- * @{#CONTROLLABLE.OptionROTNoReactionPossible} --- * @{#CONTROLLABLE.OptionROTPassiveDefensePossible} --- * @{#CONTROLLABLE.OptionROTEvadeFirePossible} --- * @{#CONTROLLABLE.OptionROTVerticalPossible} --- --- === --- --- @module Controllable --- @author FlightControl - ---- The CONTROLLABLE class --- @type CONTROLLABLE --- @extends Positionable#POSITIONABLE --- @field DCSControllable#Controllable DCSControllable The DCS controllable class. --- @field #string ControllableName The name of the controllable. -CONTROLLABLE = { - ClassName = "CONTROLLABLE", - ControllableName = "", - WayPointFunctions = {}, -} - ---- Create a new CONTROLLABLE from a DCSControllable --- @param #CONTROLLABLE self --- @param DCSControllable#Controllable ControllableName The DCS Controllable name --- @return #CONTROLLABLE self -function CONTROLLABLE:New( ControllableName ) - local self = BASE:Inherit( self, POSITIONABLE:New( ControllableName ) ) - self:F2( ControllableName ) - self.ControllableName = ControllableName - return self -end - --- DCS Controllable methods support. - ---- Get the controller for the CONTROLLABLE. --- @param #CONTROLLABLE self --- @return DCSController#Controller -function CONTROLLABLE:_GetController() - self:F2( { self.ControllableName } ) - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - local ControllableController = DCSControllable:getController() - self:T3( ControllableController ) - return ControllableController - end - - return nil -end - - - --- Tasks - ---- Popping current Task from the controllable. --- @param #CONTROLLABLE self --- @return Controllable#CONTROLLABLE self -function CONTROLLABLE:PopCurrentTask() - self:F2() - - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - local Controller = self:_GetController() - Controller:popTask() - return self - end - - return nil -end - ---- Pushing Task on the queue from the controllable. --- @param #CONTROLLABLE self --- @return Controllable#CONTROLLABLE self -function CONTROLLABLE:PushTask( DCSTask, WaitTime ) - self:F2() - - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - local Controller = self:_GetController() - - -- When a controllable SPAWNs, it takes about a second to get the controllable in the simulator. Setting tasks to unspawned controllables provides unexpected results. - -- Therefore we schedule the functions to set the mission and options for the Controllable. - -- Controller:pushTask( DCSTask ) - - if WaitTime then - SCHEDULER:New( Controller, Controller.pushTask, { DCSTask }, WaitTime ) - else - Controller:pushTask( DCSTask ) - end - - return self - end - - return nil -end - ---- Clearing the Task Queue and Setting the Task on the queue from the controllable. --- @param #CONTROLLABLE self --- @return Controllable#CONTROLLABLE self -function CONTROLLABLE:SetTask( DCSTask, WaitTime ) - self:F2( { DCSTask } ) - - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - - local Controller = self:_GetController() - self:E(Controller) - - -- When a controllable SPAWNs, it takes about a second to get the controllable in the simulator. Setting tasks to unspawned controllables provides unexpected results. - -- Therefore we schedule the functions to set the mission and options for the Controllable. - -- Controller.setTask( Controller, DCSTask ) - - if not WaitTime then - WaitTime = 1 - end - SCHEDULER:New( Controller, Controller.setTask, { DCSTask }, WaitTime ) - - return self - end - - return nil -end - - ---- Return a condition section for a controlled task. --- @param #CONTROLLABLE self --- @param DCSTime#Time time --- @param #string userFlag --- @param #boolean userFlagValue --- @param #string condition --- @param DCSTime#Time duration --- @param #number lastWayPoint --- return DCSTask#Task -function CONTROLLABLE:TaskCondition( time, userFlag, userFlagValue, condition, duration, lastWayPoint ) - self:F2( { time, userFlag, userFlagValue, condition, duration, lastWayPoint } ) - - local DCSStopCondition = {} - DCSStopCondition.time = time - DCSStopCondition.userFlag = userFlag - DCSStopCondition.userFlagValue = userFlagValue - DCSStopCondition.condition = condition - DCSStopCondition.duration = duration - DCSStopCondition.lastWayPoint = lastWayPoint - - self:T3( { DCSStopCondition } ) - return DCSStopCondition -end - ---- Return a Controlled Task taking a Task and a TaskCondition. --- @param #CONTROLLABLE self --- @param DCSTask#Task DCSTask --- @param #DCSStopCondition DCSStopCondition --- @return DCSTask#Task -function CONTROLLABLE:TaskControlled( DCSTask, DCSStopCondition ) - self:F2( { DCSTask, DCSStopCondition } ) - - local DCSTaskControlled - - DCSTaskControlled = { - id = 'ControlledTask', - params = { - task = DCSTask, - stopCondition = DCSStopCondition - } - } - - self:T3( { DCSTaskControlled } ) - return DCSTaskControlled -end - ---- Return a Combo Task taking an array of Tasks. --- @param #CONTROLLABLE self --- @param DCSTask#TaskArray DCSTasks Array of @{DCSTask#Task} --- @return DCSTask#Task -function CONTROLLABLE:TaskCombo( DCSTasks ) - self:F2( { DCSTasks } ) - - local DCSTaskCombo - - DCSTaskCombo = { - id = 'ComboTask', - params = { - tasks = DCSTasks - } - } - - self:T3( { DCSTaskCombo } ) - return DCSTaskCombo -end - ---- Return a WrappedAction Task taking a Command. --- @param #CONTROLLABLE self --- @param DCSCommand#Command DCSCommand --- @return DCSTask#Task -function CONTROLLABLE:TaskWrappedAction( DCSCommand, Index ) - self:F2( { DCSCommand } ) - - local DCSTaskWrappedAction - - DCSTaskWrappedAction = { - id = "WrappedAction", - enabled = true, - number = Index, - auto = false, - params = { - action = DCSCommand, - }, - } - - self:T3( { DCSTaskWrappedAction } ) - return DCSTaskWrappedAction -end - ---- Executes a command action --- @param #CONTROLLABLE self --- @param DCSCommand#Command DCSCommand --- @return #CONTROLLABLE self -function CONTROLLABLE:SetCommand( DCSCommand ) - self:F2( DCSCommand ) - - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - local Controller = self:_GetController() - Controller:setCommand( DCSCommand ) - return self - end - - return nil -end - ---- Perform a switch waypoint command --- @param #CONTROLLABLE self --- @param #number FromWayPoint --- @param #number ToWayPoint --- @return DCSTask#Task --- @usage --- --- This test demonstrates the use(s) of the SwitchWayPoint method of the GROUP class. --- HeliGroup = GROUP:FindByName( "Helicopter" ) --- --- --- Route the helicopter back to the FARP after 60 seconds. --- -- We use the SCHEDULER class to do this. --- SCHEDULER:New( nil, --- function( HeliGroup ) --- local CommandRTB = HeliGroup:CommandSwitchWayPoint( 2, 8 ) --- HeliGroup:SetCommand( CommandRTB ) --- end, { HeliGroup }, 90 --- ) -function CONTROLLABLE:CommandSwitchWayPoint( FromWayPoint, ToWayPoint ) - self:F2( { FromWayPoint, ToWayPoint } ) - - local CommandSwitchWayPoint = { - id = 'SwitchWaypoint', - params = { - fromWaypointIndex = FromWayPoint, - goToWaypointIndex = ToWayPoint, - }, - } - - self:T3( { CommandSwitchWayPoint } ) - return CommandSwitchWayPoint -end - ---- Perform stop route command --- @param #CONTROLLABLE self --- @param #boolean StopRoute --- @return DCSTask#Task -function CONTROLLABLE:CommandStopRoute( StopRoute, Index ) - self:F2( { StopRoute, Index } ) - - local CommandStopRoute = { - id = 'StopRoute', - params = { - value = StopRoute, - }, - } - - self:T3( { CommandStopRoute } ) - return CommandStopRoute -end - - --- TASKS FOR AIR CONTROLLABLES - - ---- (AIR) Attack a Controllable. --- @param #CONTROLLABLE self --- @param Controllable#CONTROLLABLE AttackGroup The Controllable to be attacked. --- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. --- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. --- @param DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. --- @param DCSTypes#Distance Altitude (optional) Desired attack start altitude. Controllable/aircraft will make its attacks from the altitude. If the altitude is too low or too high to use weapon aircraft/controllable will choose closest altitude to the desired attack start altitude. If the desired altitude is defined controllable/aircraft will not attack from safe altitude. --- @param #boolean AttackQtyLimit (optional) The flag determines how to interpret attackQty parameter. If the flag is true then attackQty is a limit on maximal attack quantity for "AttackControllable" and "AttackUnit" tasks. If the flag is false then attackQty is a desired attack quantity for "Bombing" and "BombingRunway" tasks. --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:TaskAttackGroup( AttackGroup, WeaponType, WeaponExpend, AttackQty, Direction, Altitude, AttackQtyLimit ) - self:F2( { self.ControllableName, AttackGroup, WeaponType, WeaponExpend, AttackQty, Direction, Altitude, AttackQtyLimit } ) - - -- AttackControllable = { - -- id = 'AttackControllable', - -- params = { - -- groupId = Group.ID, - -- weaponType = number, - -- expend = enum AI.Task.WeaponExpend, - -- attackQty = number, - -- directionEnabled = boolean, - -- direction = Azimuth, - -- altitudeEnabled = boolean, - -- altitude = Distance, - -- attackQtyLimit = boolean, - -- } - -- } - - local DirectionEnabled = nil - if Direction then - DirectionEnabled = true - end - - local AltitudeEnabled = nil - if Altitude then - AltitudeEnabled = true - end - - local DCSTask - DCSTask = { id = 'AttackControllable', - params = { - groupId = AttackGroup:GetID(), - weaponType = WeaponType, - expend = WeaponExpend, - attackQty = AttackQty, - directionEnabled = DirectionEnabled, - direction = Direction, - altitudeEnabled = AltitudeEnabled, - altitude = Altitude, - attackQtyLimit = AttackQtyLimit, - }, - }, - - self:T3( { DCSTask } ) - return DCSTask -end - - ---- (AIR) Attack the Unit. --- @param #CONTROLLABLE self --- @param Unit#UNIT AttackUnit The unit. --- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. --- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. --- @param DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. --- @param #boolean AttackQtyLimit (optional) The flag determines how to interpret attackQty parameter. If the flag is true then attackQty is a limit on maximal attack quantity for "AttackControllable" and "AttackUnit" tasks. If the flag is false then attackQty is a desired attack quantity for "Bombing" and "BombingRunway" tasks. --- @param #boolean ControllableAttack (optional) Flag indicates that the target must be engaged by all aircrafts of the controllable. Has effect only if the task is assigned to a controllable, not to a single aircraft. --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:TaskAttackUnit( AttackUnit, WeaponType, WeaponExpend, AttackQty, Direction, AttackQtyLimit, ControllableAttack ) - self:F2( { self.ControllableName, AttackUnit, WeaponType, WeaponExpend, AttackQty, Direction, AttackQtyLimit, ControllableAttack } ) - - -- AttackUnit = { - -- id = 'AttackUnit', - -- params = { - -- unitId = Unit.ID, - -- weaponType = number, - -- expend = enum AI.Task.WeaponExpend - -- attackQty = number, - -- direction = Azimuth, - -- attackQtyLimit = boolean, - -- controllableAttack = boolean, - -- } - -- } - - local DCSTask - DCSTask = { id = 'AttackUnit', - params = { - unitId = AttackUnit:GetID(), - weaponType = WeaponType, - expend = WeaponExpend, - attackQty = AttackQty, - direction = Direction, - attackQtyLimit = AttackQtyLimit, - controllableAttack = ControllableAttack, - }, - }, - - self:T3( { DCSTask } ) - return DCSTask -end - - ---- (AIR) Delivering weapon at the point on the ground. --- @param #CONTROLLABLE self --- @param DCSTypes#Vec2 Vec2 2D-coordinates of the point to deliver weapon at. --- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. --- @param #number AttackQty (optional) Desired quantity of passes. The parameter is not the same in AttackControllable and AttackUnit tasks. --- @param DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. --- @param #boolean ControllableAttack (optional) Flag indicates that the target must be engaged by all aircrafts of the controllable. Has effect only if the task is assigned to a controllable, not to a single aircraft. --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:TaskBombing( Vec2, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack ) - self:F2( { self.ControllableName, Vec2, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack } ) - --- Bombing = { --- id = 'Bombing', --- params = { --- point = Vec2, --- weaponType = number, --- expend = enum AI.Task.WeaponExpend, --- attackQty = number, --- direction = Azimuth, --- controllableAttack = boolean, --- } --- } - - local DCSTask - DCSTask = { id = 'Bombing', - params = { - point = Vec2, - weaponType = WeaponType, - expend = WeaponExpend, - attackQty = AttackQty, - direction = Direction, - controllableAttack = ControllableAttack, - }, - }, - - self:T3( { DCSTask } ) - return DCSTask -end - ---- (AIR) Orbit at a specified position at a specified alititude during a specified duration with a specified speed. --- @param #CONTROLLABLE self --- @param DCSTypes#Vec2 Point The point to hold the position. --- @param #number Altitude The altitude to hold the position. --- @param #number Speed The speed flying when holding the position. --- @return #CONTROLLABLE self -function CONTROLLABLE:TaskOrbitCircleAtVec2( Point, Altitude, Speed ) - self:F2( { self.ControllableName, Point, Altitude, Speed } ) - - -- pattern = enum AI.Task.OribtPattern, - -- point = Vec2, - -- point2 = Vec2, - -- speed = Distance, - -- altitude = Distance - - local LandHeight = land.getHeight( Point ) - - self:T3( { LandHeight } ) - - local DCSTask = { id = 'Orbit', - params = { pattern = AI.Task.OrbitPattern.CIRCLE, - point = Point, - speed = Speed, - altitude = Altitude + LandHeight - } - } - - - -- local AITask = { id = 'ControlledTask', - -- params = { task = { id = 'Orbit', - -- params = { pattern = AI.Task.OrbitPattern.CIRCLE, - -- point = Point, - -- speed = Speed, - -- altitude = Altitude + LandHeight - -- } - -- }, - -- stopCondition = { duration = Duration - -- } - -- } - -- } - -- ) - - return DCSTask -end - ---- (AIR) Orbit at the current position of the first unit of the controllable at a specified alititude. --- @param #CONTROLLABLE self --- @param #number Altitude The altitude to hold the position. --- @param #number Speed The speed flying when holding the position. --- @return #CONTROLLABLE self -function CONTROLLABLE:TaskOrbitCircle( Altitude, Speed ) - self:F2( { self.ControllableName, Altitude, Speed } ) - - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - local ControllablePoint = self:GetVec2() - return self:TaskOrbitCircleAtVec2( ControllablePoint, Altitude, Speed ) - end - - return nil -end - - - ---- (AIR) Hold position at the current position of the first unit of the controllable. --- @param #CONTROLLABLE self --- @param #number Duration The maximum duration in seconds to hold the position. --- @return #CONTROLLABLE self -function CONTROLLABLE:TaskHoldPosition() - self:F2( { self.ControllableName } ) - - return self:TaskOrbitCircle( 30, 10 ) -end - - - - ---- (AIR) Attacking the map object (building, structure, e.t.c). --- @param #CONTROLLABLE self --- @param DCSTypes#Vec2 Vec2 2D-coordinates of the point the map object is closest to. The distance between the point and the map object must not be greater than 2000 meters. Object id is not used here because Mission Editor doesn't support map object identificators. --- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. --- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. --- @param DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. --- @param #boolean ControllableAttack (optional) Flag indicates that the target must be engaged by all aircrafts of the controllable. Has effect only if the task is assigned to a controllable, not to a single aircraft. --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:TaskAttackMapObject( Vec2, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack ) - self:F2( { self.ControllableName, Vec2, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack } ) - --- AttackMapObject = { --- id = 'AttackMapObject', --- params = { --- point = Vec2, --- weaponType = number, --- expend = enum AI.Task.WeaponExpend, --- attackQty = number, --- direction = Azimuth, --- controllableAttack = boolean, --- } --- } - - local DCSTask - DCSTask = { id = 'AttackMapObject', - params = { - point = Vec2, - weaponType = WeaponType, - expend = WeaponExpend, - attackQty = AttackQty, - direction = Direction, - controllableAttack = ControllableAttack, - }, - }, - - self:T3( { DCSTask } ) - return DCSTask -end - - ---- (AIR) Delivering weapon on the runway. --- @param #CONTROLLABLE self --- @param Airbase#AIRBASE Airbase Airbase to attack. --- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. --- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. --- @param DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. --- @param #boolean ControllableAttack (optional) Flag indicates that the target must be engaged by all aircrafts of the controllable. Has effect only if the task is assigned to a controllable, not to a single aircraft. --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:TaskBombingRunway( Airbase, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack ) - self:F2( { self.ControllableName, Airbase, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack } ) - --- BombingRunway = { --- id = 'BombingRunway', --- params = { --- runwayId = AirdromeId, --- weaponType = number, --- expend = enum AI.Task.WeaponExpend, --- attackQty = number, --- direction = Azimuth, --- controllableAttack = boolean, --- } --- } - - local DCSTask - DCSTask = { id = 'BombingRunway', - params = { - point = Airbase:GetID(), - weaponType = WeaponType, - expend = WeaponExpend, - attackQty = AttackQty, - direction = Direction, - controllableAttack = ControllableAttack, - }, - }, - - self:T3( { DCSTask } ) - return DCSTask -end - - ---- (AIR) Refueling from the nearest tanker. No parameters. --- @param #CONTROLLABLE self --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:TaskRefueling() - self:F2( { self.ControllableName } ) - --- Refueling = { --- id = 'Refueling', --- params = {} --- } - - local DCSTask - DCSTask = { id = 'Refueling', - params = { - }, - }, - - self:T3( { DCSTask } ) - return DCSTask -end - - ---- (AIR HELICOPTER) Landing at the ground. For helicopters only. --- @param #CONTROLLABLE self --- @param DCSTypes#Vec2 Point The point where to land. --- @param #number Duration The duration in seconds to stay on the ground. --- @return #CONTROLLABLE self -function CONTROLLABLE:TaskLandAtVec2( Point, Duration ) - self:F2( { self.ControllableName, Point, Duration } ) - --- Land = { --- id= 'Land', --- params = { --- point = Vec2, --- durationFlag = boolean, --- duration = Time --- } --- } - - local DCSTask - if Duration and Duration > 0 then - DCSTask = { id = 'Land', - params = { - point = Point, - durationFlag = true, - duration = Duration, - }, - } - else - DCSTask = { id = 'Land', - params = { - point = Point, - durationFlag = false, - }, - } - end - - self:T3( DCSTask ) - return DCSTask -end - ---- (AIR) Land the controllable at a @{Zone#ZONE_RADIUS). --- @param #CONTROLLABLE self --- @param Zone#ZONE Zone The zone where to land. --- @param #number Duration The duration in seconds to stay on the ground. --- @return #CONTROLLABLE self -function CONTROLLABLE:TaskLandAtZone( Zone, Duration, RandomPoint ) - self:F2( { self.ControllableName, Zone, Duration, RandomPoint } ) - - local Point - if RandomPoint then - Point = Zone:GetRandomVec2() - else - Point = Zone:GetVec2() - end - - local DCSTask = self:TaskLandAtVec2( Point, Duration ) - - self:T3( DCSTask ) - return DCSTask -end - - - ---- (AIR) Following another airborne controllable. --- The unit / controllable will follow lead unit of another controllable, wingmens of both controllables will continue following their leaders. --- If another controllable is on land the unit / controllable will orbit around. --- @param #CONTROLLABLE self --- @param Controllable#CONTROLLABLE FollowControllable The controllable to be followed. --- @param DCSTypes#Vec3 Vec3 Position of the unit / lead unit of the controllable relative lead unit of another controllable in frame reference oriented by course of lead unit of another controllable. If another controllable is on land the unit / controllable will orbit around. --- @param #number LastWaypointIndex Detach waypoint of another controllable. Once reached the unit / controllable Follow task is finished. --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:TaskFollow( FollowControllable, Vec3, LastWaypointIndex ) - self:F2( { self.ControllableName, FollowControllable, Vec3, LastWaypointIndex } ) - --- Follow = { --- id = 'Follow', --- params = { --- groupId = Group.ID, --- pos = Vec3, --- lastWptIndexFlag = boolean, --- lastWptIndex = number --- } --- } - - local LastWaypointIndexFlag = false - if LastWaypointIndex then - LastWaypointIndexFlag = true - end - - local DCSTask - DCSTask = { - id = 'Follow', - params = { - groupId = FollowControllable:GetID(), - pos = Vec3, - lastWptIndexFlag = LastWaypointIndexFlag, - lastWptIndex = LastWaypointIndex - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - - ---- (AIR) Escort another airborne controllable. --- The unit / controllable will follow lead unit of another controllable, wingmens of both controllables will continue following their leaders. --- The unit / controllable will also protect that controllable from threats of specified types. --- @param #CONTROLLABLE self --- @param Controllable#CONTROLLABLE EscortControllable The controllable to be escorted. --- @param DCSTypes#Vec3 Vec3 Position of the unit / lead unit of the controllable relative lead unit of another controllable in frame reference oriented by course of lead unit of another controllable. If another controllable is on land the unit / controllable will orbit around. --- @param #number LastWaypointIndex Detach waypoint of another controllable. Once reached the unit / controllable Follow task is finished. --- @param #number EngagementDistanceMax Maximal distance from escorted controllable to threat. If the threat is already engaged by escort escort will disengage if the distance becomes greater than 1.5 * engagementDistMax. --- @param DCSTypes#AttributeNameArray TargetTypes Array of AttributeName that is contains threat categories allowed to engage. --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:TaskEscort( FollowControllable, Vec3, LastWaypointIndex, EngagementDistance, TargetTypes ) - self:F2( { self.ControllableName, FollowControllable, Vec3, LastWaypointIndex, EngagementDistance, TargetTypes } ) - --- Escort = { --- id = 'Escort', --- params = { --- groupId = Group.ID, --- pos = Vec3, --- lastWptIndexFlag = boolean, --- lastWptIndex = number, --- engagementDistMax = Distance, --- targetTypes = array of AttributeName, --- } --- } - - local LastWaypointIndexFlag = false - if LastWaypointIndex then - LastWaypointIndexFlag = true - end - - local DCSTask - DCSTask = { id = 'Escort', - params = { - groupId = FollowControllable:GetID(), - pos = Vec3, - lastWptIndexFlag = LastWaypointIndexFlag, - lastWptIndex = LastWaypointIndex, - engagementDistMax = EngagementDistance, - targetTypes = TargetTypes, - }, - }, - - self:T3( { DCSTask } ) - return DCSTask -end - - --- GROUND TASKS - ---- (GROUND) Fire at a VEC2 point until ammunition is finished. --- @param #CONTROLLABLE self --- @param DCSTypes#Vec2 Vec2 The point to fire at. --- @param DCSTypes#Distance Radius The radius of the zone to deploy the fire at. --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius ) - self:F2( { self.ControllableName, Vec2, Radius } ) - - -- FireAtPoint = { - -- id = 'FireAtPoint', - -- params = { - -- point = Vec2, - -- radius = Distance, - -- } - -- } - - local DCSTask - DCSTask = { id = 'FireAtPoint', - params = { - point = Vec2, - radius = Radius, - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - ---- (GROUND) Hold ground controllable from moving. --- @param #CONTROLLABLE self --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:TaskHold() - self:F2( { self.ControllableName } ) - --- Hold = { --- id = 'Hold', --- params = { --- } --- } - - local DCSTask - DCSTask = { id = 'Hold', - params = { - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - - --- TASKS FOR AIRBORNE AND GROUND UNITS/CONTROLLABLES - ---- (AIR + GROUND) The task makes the controllable/unit a FAC and orders the FAC to control the target (enemy ground controllable) destruction. --- The killer is player-controlled allied CAS-aircraft that is in contact with the FAC. --- If the task is assigned to the controllable lead unit will be a FAC. --- @param #CONTROLLABLE self --- @param Controllable#CONTROLLABLE AttackGroup Target CONTROLLABLE. --- @param #number WeaponType Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param DCSTypes#AI.Task.Designation Designation (optional) Designation type. --- @param #boolean Datalink (optional) Allows to use datalink to send the target information to attack aircraft. Enabled by default. --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:TaskFAC_AttackGroup( AttackGroup, WeaponType, Designation, Datalink ) - self:F2( { self.ControllableName, AttackGroup, WeaponType, Designation, Datalink } ) - --- FAC_AttackControllable = { --- id = 'FAC_AttackControllable', --- params = { --- groupId = Group.ID, --- weaponType = number, --- designation = enum AI.Task.Designation, --- datalink = boolean --- } --- } - - local DCSTask - DCSTask = { id = 'FAC_AttackControllable', - params = { - groupId = AttackGroup:GetID(), - weaponType = WeaponType, - designation = Designation, - datalink = Datalink, - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - --- EN-ROUTE TASKS FOR AIRBORNE CONTROLLABLES - ---- (AIR) Engaging targets of defined types. --- @param #CONTROLLABLE self --- @param DCSTypes#Distance Distance Maximal distance from the target to a route leg. If the target is on a greater distance it will be ignored. --- @param DCSTypes#AttributeNameArray TargetTypes Array of target categories allowed to engage. --- @param #number Priority All enroute tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:EnRouteTaskEngageTargets( Distance, TargetTypes, Priority ) - self:F2( { self.ControllableName, Distance, TargetTypes, Priority } ) - --- EngageTargets ={ --- id = 'EngageTargets', --- params = { --- maxDist = Distance, --- targetTypes = array of AttributeName, --- priority = number --- } --- } - - local DCSTask - DCSTask = { id = 'EngageTargets', - params = { - maxDist = Distance, - targetTypes = TargetTypes, - priority = Priority - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - - - ---- (AIR) Engaging a targets of defined types at circle-shaped zone. --- @param #CONTROLLABLE self --- @param DCSTypes#Vec2 Vec2 2D-coordinates of the zone. --- @param DCSTypes#Distance Radius Radius of the zone. --- @param DCSTypes#AttributeNameArray TargetTypes Array of target categories allowed to engage. --- @param #number Priority All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:EnRouteTaskEngageTargets( Vec2, Radius, TargetTypes, Priority ) - self:F2( { self.ControllableName, Vec2, Radius, TargetTypes, Priority } ) - --- EngageTargetsInZone = { --- id = 'EngageTargetsInZone', --- params = { --- point = Vec2, --- zoneRadius = Distance, --- targetTypes = array of AttributeName, --- priority = number --- } --- } - - local DCSTask - DCSTask = { id = 'EngageTargetsInZone', - params = { - point = Vec2, - zoneRadius = Radius, - targetTypes = TargetTypes, - priority = Priority - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - - ---- (AIR) Engaging a controllable. The task does not assign the target controllable to the unit/controllable to attack now; it just allows the unit/controllable to engage the target controllable as well as other assigned targets. --- @param #CONTROLLABLE self --- @param Controllable#CONTROLLABLE AttackGroup The Controllable to be attacked. --- @param #number Priority All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. --- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. --- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. --- @param DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. --- @param DCSTypes#Distance Altitude (optional) Desired attack start altitude. Controllable/aircraft will make its attacks from the altitude. If the altitude is too low or too high to use weapon aircraft/controllable will choose closest altitude to the desired attack start altitude. If the desired altitude is defined controllable/aircraft will not attack from safe altitude. --- @param #boolean AttackQtyLimit (optional) The flag determines how to interpret attackQty parameter. If the flag is true then attackQty is a limit on maximal attack quantity for "AttackControllable" and "AttackUnit" tasks. If the flag is false then attackQty is a desired attack quantity for "Bombing" and "BombingRunway" tasks. --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:EnRouteTaskEngageGroup( AttackGroup, Priority, WeaponType, WeaponExpend, AttackQty, Direction, Altitude, AttackQtyLimit ) - self:F2( { self.ControllableName, AttackGroup, Priority, WeaponType, WeaponExpend, AttackQty, Direction, Altitude, AttackQtyLimit } ) - - -- EngageControllable = { - -- id = 'EngageControllable ', - -- params = { - -- groupId = Group.ID, - -- weaponType = number, - -- expend = enum AI.Task.WeaponExpend, - -- attackQty = number, - -- directionEnabled = boolean, - -- direction = Azimuth, - -- altitudeEnabled = boolean, - -- altitude = Distance, - -- attackQtyLimit = boolean, - -- priority = number, - -- } - -- } - - local DirectionEnabled = nil - if Direction then - DirectionEnabled = true - end - - local AltitudeEnabled = nil - if Altitude then - AltitudeEnabled = true - end - - local DCSTask - DCSTask = { id = 'EngageControllable', - params = { - groupId = AttackGroup:GetID(), - weaponType = WeaponType, - expend = WeaponExpend, - attackQty = AttackQty, - directionEnabled = DirectionEnabled, - direction = Direction, - altitudeEnabled = AltitudeEnabled, - altitude = Altitude, - attackQtyLimit = AttackQtyLimit, - priority = Priority, - }, - }, - - self:T3( { DCSTask } ) - return DCSTask -end - - ---- (AIR) Attack the Unit. --- @param #CONTROLLABLE self --- @param Unit#UNIT AttackUnit The UNIT. --- @param #number Priority All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. --- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. --- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. --- @param DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. --- @param #boolean AttackQtyLimit (optional) The flag determines how to interpret attackQty parameter. If the flag is true then attackQty is a limit on maximal attack quantity for "AttackControllable" and "AttackUnit" tasks. If the flag is false then attackQty is a desired attack quantity for "Bombing" and "BombingRunway" tasks. --- @param #boolean ControllableAttack (optional) Flag indicates that the target must be engaged by all aircrafts of the controllable. Has effect only if the task is assigned to a controllable, not to a single aircraft. --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:EnRouteTaskEngageUnit( AttackUnit, Priority, WeaponType, WeaponExpend, AttackQty, Direction, AttackQtyLimit, ControllableAttack ) - self:F2( { self.ControllableName, AttackUnit, Priority, WeaponType, WeaponExpend, AttackQty, Direction, AttackQtyLimit, ControllableAttack } ) - - -- EngageUnit = { - -- id = 'EngageUnit', - -- params = { - -- unitId = Unit.ID, - -- weaponType = number, - -- expend = enum AI.Task.WeaponExpend - -- attackQty = number, - -- direction = Azimuth, - -- attackQtyLimit = boolean, - -- controllableAttack = boolean, - -- priority = number, - -- } - -- } - - local DCSTask - DCSTask = { id = 'EngageUnit', - params = { - unitId = AttackUnit:GetID(), - weaponType = WeaponType, - expend = WeaponExpend, - attackQty = AttackQty, - direction = Direction, - attackQtyLimit = AttackQtyLimit, - controllableAttack = ControllableAttack, - priority = Priority, - }, - }, - - self:T3( { DCSTask } ) - return DCSTask -end - - - ---- (AIR) Aircraft will act as an AWACS for friendly units (will provide them with information about contacts). No parameters. --- @param #CONTROLLABLE self --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:EnRouteTaskAWACS( ) - self:F2( { self.ControllableName } ) - --- AWACS = { --- id = 'AWACS', --- params = { --- } --- } - - local DCSTask - DCSTask = { id = 'AWACS', - params = { - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - - ---- (AIR) Aircraft will act as a tanker for friendly units. No parameters. --- @param #CONTROLLABLE self --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:EnRouteTaskTanker( ) - self:F2( { self.ControllableName } ) - --- Tanker = { --- id = 'Tanker', --- params = { --- } --- } - - local DCSTask - DCSTask = { id = 'Tanker', - params = { - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - - --- En-route tasks for ground units/controllables - ---- (GROUND) Ground unit (EW-radar) will act as an EWR for friendly units (will provide them with information about contacts). No parameters. --- @param #CONTROLLABLE self --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:EnRouteTaskEWR( ) - self:F2( { self.ControllableName } ) - --- EWR = { --- id = 'EWR', --- params = { --- } --- } - - local DCSTask - DCSTask = { id = 'EWR', - params = { - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - - --- En-route tasks for airborne and ground units/controllables - ---- (AIR + GROUND) The task makes the controllable/unit a FAC and lets the FAC to choose the target (enemy ground controllable) as well as other assigned targets. --- The killer is player-controlled allied CAS-aircraft that is in contact with the FAC. --- If the task is assigned to the controllable lead unit will be a FAC. --- @param #CONTROLLABLE self --- @param Controllable#CONTROLLABLE AttackGroup Target CONTROLLABLE. --- @param #number Priority All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. --- @param #number WeaponType Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param DCSTypes#AI.Task.Designation Designation (optional) Designation type. --- @param #boolean Datalink (optional) Allows to use datalink to send the target information to attack aircraft. Enabled by default. --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:EnRouteTaskFAC_EngageGroup( AttackGroup, Priority, WeaponType, Designation, Datalink ) - self:F2( { self.ControllableName, AttackGroup, WeaponType, Priority, Designation, Datalink } ) - --- FAC_EngageControllable = { --- id = 'FAC_EngageControllable', --- params = { --- groupId = Group.ID, --- weaponType = number, --- designation = enum AI.Task.Designation, --- datalink = boolean, --- priority = number, --- } --- } - - local DCSTask - DCSTask = { id = 'FAC_EngageControllable', - params = { - groupId = AttackGroup:GetID(), - weaponType = WeaponType, - designation = Designation, - datalink = Datalink, - priority = Priority, - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - - ---- (AIR + GROUND) The task makes the controllable/unit a FAC and lets the FAC to choose a targets (enemy ground controllable) around as well as other assigned targets. --- The killer is player-controlled allied CAS-aircraft that is in contact with the FAC. --- If the task is assigned to the controllable lead unit will be a FAC. --- @param #CONTROLLABLE self --- @param DCSTypes#Distance Radius The maximal distance from the FAC to a target. --- @param #number Priority All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:EnRouteTaskFAC( Radius, Priority ) - self:F2( { self.ControllableName, Radius, Priority } ) - --- FAC = { --- id = 'FAC', --- params = { --- radius = Distance, --- priority = number --- } --- } - - local DCSTask - DCSTask = { id = 'FAC', - params = { - radius = Radius, - priority = Priority - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - - - - ---- (AIR) Move the controllable to a Vec2 Point, wait for a defined duration and embark a controllable. --- @param #CONTROLLABLE self --- @param DCSTypes#Vec2 Point The point where to wait. --- @param #number Duration The duration in seconds to wait. --- @param #CONTROLLABLE EmbarkingControllable The controllable to be embarked. --- @return DCSTask#Task The DCS task structure -function CONTROLLABLE:TaskEmbarking( Point, Duration, EmbarkingControllable ) - self:F2( { self.ControllableName, Point, Duration, EmbarkingControllable.DCSControllable } ) - - local DCSTask - DCSTask = { id = 'Embarking', - params = { x = Point.x, - y = Point.y, - duration = Duration, - controllablesForEmbarking = { EmbarkingControllable.ControllableID }, - durationFlag = true, - distributionFlag = false, - distribution = {}, - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - ---- (GROUND) Embark to a Transport landed at a location. - ---- Move to a defined Vec2 Point, and embark to a controllable when arrived within a defined Radius. --- @param #CONTROLLABLE self --- @param DCSTypes#Vec2 Point The point where to wait. --- @param #number Radius The radius of the embarking zone around the Point. --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:TaskEmbarkToTransport( Point, Radius ) - self:F2( { self.ControllableName, Point, Radius } ) - - local DCSTask --DCSTask#Task - DCSTask = { id = 'EmbarkToTransport', - params = { x = Point.x, - y = Point.y, - zoneRadius = Radius, - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - - - ---- (AIR + GROUND) Return a mission task from a mission template. --- @param #CONTROLLABLE self --- @param #table TaskMission A table containing the mission task. --- @return DCSTask#Task -function CONTROLLABLE:TaskMission( TaskMission ) - self:F2( Points ) - - local DCSTask - DCSTask = { id = 'Mission', params = { TaskMission, }, } - - self:T3( { DCSTask } ) - return DCSTask -end - ---- Return a Misson task to follow a given route defined by Points. --- @param #CONTROLLABLE self --- @param #table Points A table of route points. --- @return DCSTask#Task -function CONTROLLABLE:TaskRoute( Points ) - self:F2( Points ) - - local DCSTask - DCSTask = { id = 'Mission', params = { route = { points = Points, }, }, } - - self:T3( { DCSTask } ) - return DCSTask -end - ---- (AIR + GROUND) Make the Controllable move to fly to a given point. --- @param #CONTROLLABLE self --- @param DCSTypes#Vec3 Point The destination point in Vec3 format. --- @param #number Speed The speed to travel. --- @return #CONTROLLABLE self -function CONTROLLABLE:TaskRouteToVec2( Point, Speed ) - self:F2( { Point, Speed } ) - - local ControllablePoint = self:GetUnit( 1 ):GetVec2() - - local PointFrom = {} - PointFrom.x = ControllablePoint.x - PointFrom.y = ControllablePoint.y - PointFrom.type = "Turning Point" - PointFrom.action = "Turning Point" - PointFrom.speed = Speed - PointFrom.speed_locked = true - PointFrom.properties = { - ["vnav"] = 1, - ["scale"] = 0, - ["angle"] = 0, - ["vangle"] = 0, - ["steer"] = 2, - } - - - local PointTo = {} - PointTo.x = Point.x - PointTo.y = Point.y - PointTo.type = "Turning Point" - PointTo.action = "Fly Over Point" - PointTo.speed = Speed - PointTo.speed_locked = true - PointTo.properties = { - ["vnav"] = 1, - ["scale"] = 0, - ["angle"] = 0, - ["vangle"] = 0, - ["steer"] = 2, - } - - - local Points = { PointFrom, PointTo } - - self:T3( Points ) - - self:Route( Points ) - - return self -end - ---- (AIR + GROUND) Make the Controllable move to a given point. --- @param #CONTROLLABLE self --- @param DCSTypes#Vec3 Point The destination point in Vec3 format. --- @param #number Speed The speed to travel. --- @return #CONTROLLABLE self -function CONTROLLABLE:TaskRouteToVec3( Point, Speed ) - self:F2( { Point, Speed } ) - - local ControllableVec3 = self:GetUnit( 1 ):GetVec3() - - local PointFrom = {} - PointFrom.x = ControllableVec3.x - PointFrom.y = ControllableVec3.z - PointFrom.alt = ControllableVec3.y - PointFrom.alt_type = "BARO" - PointFrom.type = "Turning Point" - PointFrom.action = "Turning Point" - PointFrom.speed = Speed - PointFrom.speed_locked = true - PointFrom.properties = { - ["vnav"] = 1, - ["scale"] = 0, - ["angle"] = 0, - ["vangle"] = 0, - ["steer"] = 2, - } - - - local PointTo = {} - PointTo.x = Point.x - PointTo.y = Point.z - PointTo.alt = Point.y - PointTo.alt_type = "BARO" - PointTo.type = "Turning Point" - PointTo.action = "Fly Over Point" - PointTo.speed = Speed - PointTo.speed_locked = true - PointTo.properties = { - ["vnav"] = 1, - ["scale"] = 0, - ["angle"] = 0, - ["vangle"] = 0, - ["steer"] = 2, - } - - - local Points = { PointFrom, PointTo } - - self:T3( Points ) - - self:Route( Points ) - - return self -end - - - ---- Make the controllable to follow a given route. --- @param #CONTROLLABLE self --- @param #table GoPoints A table of Route Points. --- @return #CONTROLLABLE self -function CONTROLLABLE:Route( GoPoints ) - self:F2( GoPoints ) - - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - local Points = routines.utils.deepCopy( GoPoints ) - local MissionTask = { id = 'Mission', params = { route = { points = Points, }, }, } - local Controller = self:_GetController() - --Controller.setTask( Controller, MissionTask ) - SCHEDULER:New( Controller, Controller.setTask, { MissionTask }, 1 ) - return self - end - - return nil -end - - - ---- (AIR + GROUND) Route the controllable to a given zone. --- The controllable final destination point can be randomized. --- A speed can be given in km/h. --- A given formation can be given. --- @param #CONTROLLABLE self --- @param Zone#ZONE Zone The zone where to route to. --- @param #boolean Randomize Defines whether to target point gets randomized within the Zone. --- @param #number Speed The speed. --- @param Base#FORMATION Formation The formation string. -function CONTROLLABLE:TaskRouteToZone( Zone, Randomize, Speed, Formation ) - self:F2( Zone ) - - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - - local ControllablePoint = self:GetVec2() - - local PointFrom = {} - PointFrom.x = ControllablePoint.x - PointFrom.y = ControllablePoint.y - PointFrom.type = "Turning Point" - PointFrom.action = "Cone" - PointFrom.speed = 20 / 1.6 - - - local PointTo = {} - local ZonePoint - - if Randomize then - ZonePoint = Zone:GetRandomVec2() - else - ZonePoint = Zone:GetVec2() - end - - PointTo.x = ZonePoint.x - PointTo.y = ZonePoint.y - PointTo.type = "Turning Point" - - if Formation then - PointTo.action = Formation - else - PointTo.action = "Cone" - end - - if Speed then - PointTo.speed = Speed - else - PointTo.speed = 20 / 1.6 - end - - local Points = { PointFrom, PointTo } - - self:T3( Points ) - - self:Route( Points ) - - return self - end - - return nil -end - ---- (AIR) Return the Controllable to an @{Airbase#AIRBASE} --- A speed can be given in km/h. --- A given formation can be given. --- @param #CONTROLLABLE self --- @param Airbase#AIRBASE ReturnAirbase The @{Airbase#AIRBASE} to return to. --- @param #number Speed (optional) The speed. --- @return #string The route -function CONTROLLABLE:RouteReturnToAirbase( ReturnAirbase, Speed ) - self:F2( { ReturnAirbase, Speed } ) - --- Example --- [4] = --- { --- ["alt"] = 45, --- ["type"] = "Land", --- ["action"] = "Landing", --- ["alt_type"] = "BARO", --- ["formation_template"] = "", --- ["properties"] = --- { --- ["vnav"] = 1, --- ["scale"] = 0, --- ["angle"] = 0, --- ["vangle"] = 0, --- ["steer"] = 2, --- }, -- end of ["properties"] --- ["ETA"] = 527.81058817743, --- ["airdromeId"] = 12, --- ["y"] = 243127.2973737, --- ["x"] = -5406.2803440839, --- ["name"] = "DictKey_WptName_53", --- ["speed"] = 138.88888888889, --- ["ETA_locked"] = false, --- ["task"] = --- { --- ["id"] = "ComboTask", --- ["params"] = --- { --- ["tasks"] = --- { --- }, -- end of ["tasks"] --- }, -- end of ["params"] --- }, -- end of ["task"] --- ["speed_locked"] = true, --- }, -- end of [4] - - - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - - local ControllablePoint = self:GetVec2() - local ControllableVelocity = self:GetMaxVelocity() - - local PointFrom = {} - PointFrom.x = ControllablePoint.x - PointFrom.y = ControllablePoint.y - PointFrom.type = "Turning Point" - PointFrom.action = "Turning Point" - PointFrom.speed = ControllableVelocity - - - local PointTo = {} - local AirbasePoint = ReturnAirbase:GetVec2() - - PointTo.x = AirbasePoint.x - PointTo.y = AirbasePoint.y - PointTo.type = "Land" - PointTo.action = "Landing" - PointTo.airdromeId = ReturnAirbase:GetID()-- Airdrome ID - self:T(PointTo.airdromeId) - --PointTo.alt = 0 - - local Points = { PointFrom, PointTo } - - self:T3( Points ) - - local Route = { points = Points, } - - return Route - end - - return nil -end - --- Commands - ---- Do Script command --- @param #CONTROLLABLE self --- @param #string DoScript --- @return #DCSCommand -function CONTROLLABLE:CommandDoScript( DoScript ) - - local DCSDoScript = { - id = "Script", - params = { - command = DoScript, - }, - } - - self:T3( DCSDoScript ) - return DCSDoScript -end - - ---- Return the mission template of the controllable. --- @param #CONTROLLABLE self --- @return #table The MissionTemplate --- TODO: Rework the method how to retrieve a template ... -function CONTROLLABLE:GetTaskMission() - self:F2( self.ControllableName ) - - return routines.utils.deepCopy( _DATABASE.Templates.Controllables[self.ControllableName].Template ) -end - ---- Return the mission route of the controllable. --- @param #CONTROLLABLE self --- @return #table The mission route defined by points. -function CONTROLLABLE:GetTaskRoute() - self:F2( self.ControllableName ) - - return routines.utils.deepCopy( _DATABASE.Templates.Controllables[self.ControllableName].Template.route.points ) -end - ---- Return the route of a controllable by using the @{Database#DATABASE} class. --- @param #CONTROLLABLE self --- @param #number Begin The route point from where the copy will start. The base route point is 0. --- @param #number End The route point where the copy will end. The End point is the last point - the End point. The last point has base 0. --- @param #boolean Randomize Randomization of the route, when true. --- @param #number Radius When randomization is on, the randomization is within the radius. -function CONTROLLABLE:CopyRoute( Begin, End, Randomize, Radius ) - self:F2( { Begin, End } ) - - local Points = {} - - -- Could be a Spawned Controllable - local ControllableName = string.match( self:GetName(), ".*#" ) - if ControllableName then - ControllableName = ControllableName:sub( 1, -2 ) - else - ControllableName = self:GetName() - end - - self:T3( { ControllableName } ) - - local Template = _DATABASE.Templates.Controllables[ControllableName].Template - - if Template then - if not Begin then - Begin = 0 - end - if not End then - End = 0 - end - - for TPointID = Begin + 1, #Template.route.points - End do - if Template.route.points[TPointID] then - Points[#Points+1] = routines.utils.deepCopy( Template.route.points[TPointID] ) - if Randomize then - if not Radius then - Radius = 500 - end - Points[#Points].x = Points[#Points].x + math.random( Radius * -1, Radius ) - Points[#Points].y = Points[#Points].y + math.random( Radius * -1, Radius ) - end - end - end - return Points - else - error( "Template not found for Controllable : " .. ControllableName ) - end - - return nil -end - - ---- Return the detected targets of the controllable. --- The optional parametes specify the detection methods that can be applied. --- If no detection method is given, the detection will use all the available methods by default. --- @param Controllable#CONTROLLABLE self --- @param #boolean DetectVisual (optional) --- @param #boolean DetectOptical (optional) --- @param #boolean DetectRadar (optional) --- @param #boolean DetectIRST (optional) --- @param #boolean DetectRWR (optional) --- @param #boolean DetectDLINK (optional) --- @return #table DetectedTargets -function CONTROLLABLE:GetDetectedTargets( DetectVisual, DetectOptical, DetectRadar, DetectIRST, DetectRWR, DetectDLINK ) - self:F2( self.ControllableName ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - local DetectionVisual = ( DetectVisual and DetectVisual == true ) and Controller.Detection.VISUAL or nil - local DetectionOptical = ( DetectOptical and DetectOptical == true ) and Controller.Detection.OPTICAL or nil - local DetectionRadar = ( DetectRadar and DetectRadar == true ) and Controller.Detection.RADAR or nil - local DetectionIRST = ( DetectIRST and DetectIRST == true ) and Controller.Detection.IRST or nil - local DetectionRWR = ( DetectRWR and DetectRWR == true ) and Controller.Detection.RWR or nil - local DetectionDLINK = ( DetectDLINK and DetectDLINK == true ) and Controller.Detection.DLINK or nil - - - return self:_GetController():getDetectedTargets( DetectionVisual, DetectionOptical, DetectionRadar, DetectionIRST, DetectionRWR, DetectionDLINK ) - end - - return nil -end - -function CONTROLLABLE:IsTargetDetected( DCSObject ) - self:F2( self.ControllableName ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - - local TargetIsDetected, TargetIsVisible, TargetLastTime, TargetKnowType, TargetKnowDistance, TargetLastPos, TargetLastVelocity - = self:_GetController().isTargetDetected( self:_GetController(), DCSObject, - Controller.Detection.VISUAL, - Controller.Detection.OPTIC, - Controller.Detection.RADAR, - Controller.Detection.IRST, - Controller.Detection.RWR, - Controller.Detection.DLINK - ) - return TargetIsDetected, TargetIsVisible, TargetLastTime, TargetKnowType, TargetKnowDistance, TargetLastPos, TargetLastVelocity - end - - return nil -end - --- Options - ---- Can the CONTROLLABLE hold their weapons? --- @param #CONTROLLABLE self --- @return #boolean -function CONTROLLABLE:OptionROEHoldFirePossible() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - if self:IsAir() or self:IsGround() or self:IsShip() then - return true - end - - return false - end - - return nil -end - ---- Holding weapons. --- @param Controllable#CONTROLLABLE self --- @return Controllable#CONTROLLABLE self -function CONTROLLABLE:OptionROEHoldFire() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - local Controller = self:_GetController() - - if self:IsAir() then - Controller:setOption( AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.WEAPON_HOLD ) - elseif self:IsGround() then - Controller:setOption( AI.Option.Ground.id.ROE, AI.Option.Ground.val.ROE.WEAPON_HOLD ) - elseif self:IsShip() then - Controller:setOption( AI.Option.Naval.id.ROE, AI.Option.Naval.val.ROE.WEAPON_HOLD ) - end - - return self - end - - return nil -end - ---- Can the CONTROLLABLE attack returning on enemy fire? --- @param #CONTROLLABLE self --- @return #boolean -function CONTROLLABLE:OptionROEReturnFirePossible() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - if self:IsAir() or self:IsGround() or self:IsShip() then - return true - end - - return false - end - - return nil -end - ---- Return fire. --- @param #CONTROLLABLE self --- @return #CONTROLLABLE self -function CONTROLLABLE:OptionROEReturnFire() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - local Controller = self:_GetController() - - if self:IsAir() then - Controller:setOption( AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.RETURN_FIRE ) - elseif self:IsGround() then - Controller:setOption( AI.Option.Ground.id.ROE, AI.Option.Ground.val.ROE.RETURN_FIRE ) - elseif self:IsShip() then - Controller:setOption( AI.Option.Naval.id.ROE, AI.Option.Naval.val.ROE.RETURN_FIRE ) - end - - return self - end - - return nil -end - ---- Can the CONTROLLABLE attack designated targets? --- @param #CONTROLLABLE self --- @return #boolean -function CONTROLLABLE:OptionROEOpenFirePossible() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - if self:IsAir() or self:IsGround() or self:IsShip() then - return true - end - - return false - end - - return nil -end - ---- Openfire. --- @param #CONTROLLABLE self --- @return #CONTROLLABLE self -function CONTROLLABLE:OptionROEOpenFire() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - local Controller = self:_GetController() - - if self:IsAir() then - Controller:setOption( AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.OPEN_FIRE ) - elseif self:IsGround() then - Controller:setOption( AI.Option.Ground.id.ROE, AI.Option.Ground.val.ROE.OPEN_FIRE ) - elseif self:IsShip() then - Controller:setOption( AI.Option.Naval.id.ROE, AI.Option.Naval.val.ROE.OPEN_FIRE ) - end - - return self - end - - return nil -end - ---- Can the CONTROLLABLE attack targets of opportunity? --- @param #CONTROLLABLE self --- @return #boolean -function CONTROLLABLE:OptionROEWeaponFreePossible() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - if self:IsAir() then - return true - end - - return false - end - - return nil -end - ---- Weapon free. --- @param #CONTROLLABLE self --- @return #CONTROLLABLE self -function CONTROLLABLE:OptionROEWeaponFree() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - local Controller = self:_GetController() - - if self:IsAir() then - Controller:setOption( AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.WEAPON_FREE ) - end - - return self - end - - return nil -end - ---- Can the CONTROLLABLE ignore enemy fire? --- @param #CONTROLLABLE self --- @return #boolean -function CONTROLLABLE:OptionROTNoReactionPossible() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - if self:IsAir() then - return true - end - - return false - end - - return nil -end - - ---- No evasion on enemy threats. --- @param #CONTROLLABLE self --- @return #CONTROLLABLE self -function CONTROLLABLE:OptionROTNoReaction() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - local Controller = self:_GetController() - - if self:IsAir() then - Controller:setOption( AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.NO_REACTION ) - end - - return self - end - - return nil -end - ---- Can the CONTROLLABLE evade using passive defenses? --- @param #CONTROLLABLE self --- @return #boolean -function CONTROLLABLE:OptionROTPassiveDefensePossible() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - if self:IsAir() then - return true - end - - return false - end - - return nil -end - ---- Evasion passive defense. --- @param #CONTROLLABLE self --- @return #CONTROLLABLE self -function CONTROLLABLE:OptionROTPassiveDefense() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - local Controller = self:_GetController() - - if self:IsAir() then - Controller:setOption( AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.PASSIVE_DEFENCE ) - end - - return self - end - - return nil -end - ---- Can the CONTROLLABLE evade on enemy fire? --- @param #CONTROLLABLE self --- @return #boolean -function CONTROLLABLE:OptionROTEvadeFirePossible() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - if self:IsAir() then - return true - end - - return false - end - - return nil -end - - ---- Evade on fire. --- @param #CONTROLLABLE self --- @return #CONTROLLABLE self -function CONTROLLABLE:OptionROTEvadeFire() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - local Controller = self:_GetController() - - if self:IsAir() then - Controller:setOption( AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.EVADE_FIRE ) - end - - return self - end - - return nil -end - ---- Can the CONTROLLABLE evade on fire using vertical manoeuvres? --- @param #CONTROLLABLE self --- @return #boolean -function CONTROLLABLE:OptionROTVerticalPossible() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - if self:IsAir() then - return true - end - - return false - end - - return nil -end - - ---- Evade on fire using vertical manoeuvres. --- @param #CONTROLLABLE self --- @return #CONTROLLABLE self -function CONTROLLABLE:OptionROTVertical() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - local Controller = self:_GetController() - - if self:IsAir() then - Controller:setOption( AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.BYPASS_AND_ESCAPE ) - end - - return self - end - - return nil -end - ---- Retrieve the controllable mission and allow to place function hooks within the mission waypoint plan. --- Use the method @{Controllable#CONTROLLABLE:WayPointFunction} to define the hook functions for specific waypoints. --- Use the method @{Controllable@CONTROLLABLE:WayPointExecute) to start the execution of the new mission plan. --- Note that when WayPointInitialize is called, the Mission of the controllable is RESTARTED! --- @param #CONTROLLABLE self --- @param #table WayPoints If WayPoints is given, then use the route. --- @return #CONTROLLABLE -function CONTROLLABLE:WayPointInitialize( WayPoints ) - self:F( { WayPoint, WayPointIndex, WayPointFunction } ) - - if WayPoints then - self.WayPoints = WayPoints - else - self.WayPoints = self:GetTaskRoute() - end - - return self -end - - ---- Registers a waypoint function that will be executed when the controllable moves over the WayPoint. --- @param #CONTROLLABLE self --- @param #number WayPoint The waypoint number. Note that the start waypoint on the route is WayPoint 1! --- @param #number WayPointIndex When defining multiple WayPoint functions for one WayPoint, use WayPointIndex to set the sequence of actions. --- @param #function WayPointFunction The waypoint function to be called when the controllable moves over the waypoint. The waypoint function takes variable parameters. --- @return #CONTROLLABLE -function CONTROLLABLE:WayPointFunction( WayPoint, WayPointIndex, WayPointFunction, ... ) - self:F2( { WayPoint, WayPointIndex, WayPointFunction } ) - - table.insert( self.WayPoints[WayPoint].task.params.tasks, WayPointIndex ) - self.WayPoints[WayPoint].task.params.tasks[WayPointIndex] = self:TaskFunction( WayPoint, WayPointIndex, WayPointFunction, arg ) - return self -end - - -function CONTROLLABLE:TaskFunction( WayPoint, WayPointIndex, FunctionString, FunctionArguments ) - self:F2( { WayPoint, WayPointIndex, FunctionString, FunctionArguments } ) - - local DCSTask - - local DCSScript = {} - DCSScript[#DCSScript+1] = "local MissionControllable = CONTROLLABLE:Find( ... ) " - - if FunctionArguments and #FunctionArguments > 0 then - DCSScript[#DCSScript+1] = FunctionString .. "( MissionControllable, " .. table.concat( FunctionArguments, "," ) .. ")" - else - DCSScript[#DCSScript+1] = FunctionString .. "( MissionControllable )" - end - - DCSTask = self:TaskWrappedAction( - self:CommandDoScript( - table.concat( DCSScript ) - ), WayPointIndex - ) - - self:T3( DCSTask ) - - return DCSTask - -end - ---- Executes the WayPoint plan. --- The function gets a WayPoint parameter, that you can use to restart the mission at a specific WayPoint. --- Note that when the WayPoint parameter is used, the new start mission waypoint of the controllable will be 1! --- @param #CONTROLLABLE self --- @param #number WayPoint The WayPoint from where to execute the mission. --- @param #number WaitTime The amount seconds to wait before initiating the mission. --- @return #CONTROLLABLE -function CONTROLLABLE:WayPointExecute( WayPoint, WaitTime ) - self:F( { WayPoint, WaitTime } ) - - if not WayPoint then - WayPoint = 1 - end - - -- When starting the mission from a certain point, the TaskPoints need to be deleted before the given WayPoint. - for TaskPointID = 1, WayPoint - 1 do - table.remove( self.WayPoints, 1 ) - end - - self:T3( self.WayPoints ) - - self:SetTask( self:TaskRoute( self.WayPoints ), WaitTime ) - - return self -end - --- Message APIs - ---- Returns a message with the callsign embedded (if there is one). --- @param #CONTROLLABLE self --- @param #string Message The message text --- @param DCSTypes#Duration Duration The duration of the message. --- @return Message#MESSAGE -function CONTROLLABLE:GetMessage( Message, Duration ) - - local DCSObject = self:GetDCSObject() - if DCSObject then - return MESSAGE:New( Message, Duration, self:GetCallsign() .. " (" .. self:GetTypeName() .. ")" ) - end - - return nil -end - ---- Send a message to all coalitions. --- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. --- @param #CONTROLLABLE self --- @param #string Message The message text --- @param DCSTypes#Duration Duration The duration of the message. -function CONTROLLABLE:MessageToAll( Message, Duration ) - self:F2( { Message, Duration } ) - - local DCSObject = self:GetDCSObject() - if DCSObject then - self:GetMessage( Message, Duration ):ToAll() - end - - return nil -end - ---- Send a message to the red coalition. --- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. --- @param #CONTROLLABLE self --- @param #string Message The message text --- @param DCSTYpes#Duration Duration The duration of the message. -function CONTROLLABLE:MessageToRed( Message, Duration ) - self:F2( { Message, Duration } ) - - local DCSObject = self:GetDCSObject() - if DCSObject then - self:GetMessage( Message, Duration ):ToRed() - end - - return nil -end - ---- Send a message to the blue coalition. --- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. --- @param #CONTROLLABLE self --- @param #string Message The message text --- @param DCSTypes#Duration Duration The duration of the message. -function CONTROLLABLE:MessageToBlue( Message, Duration ) - self:F2( { Message, Duration } ) - - local DCSObject = self:GetDCSObject() - if DCSObject then - self:GetMessage( Message, Duration ):ToBlue() - end - - return nil -end - ---- Send a message to a client. --- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. --- @param #CONTROLLABLE self --- @param #string Message The message text --- @param DCSTypes#Duration Duration The duration of the message. --- @param Client#CLIENT Client The client object receiving the message. -function CONTROLLABLE:MessageToClient( Message, Duration, Client ) - self:F2( { Message, Duration } ) - - local DCSObject = self:GetDCSObject() - if DCSObject then - self:GetMessage( Message, Duration ):ToClient( Client ) - end - - return nil -end - ---- Send a message to a @{Group}. --- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. --- @param #CONTROLLABLE self --- @param #string Message The message text --- @param DCSTypes#Duration Duration The duration of the message. --- @param Group#GROUP MessageGroup The GROUP object receiving the message. -function CONTROLLABLE:MessageToGroup( Message, Duration, MessageGroup ) - self:F2( { Message, Duration } ) - - local DCSObject = self:GetDCSObject() - if DCSObject then - if DCSObject:isExist() then - self:GetMessage( Message, Duration ):ToGroup( MessageGroup ) - end - end - - return nil -end - ---- Send a message to the players in the @{Group}. --- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. --- @param #CONTROLLABLE self --- @param #string Message The message text --- @param DCSTypes#Duration Duration The duration of the message. -function CONTROLLABLE:Message( Message, Duration ) - self:F2( { Message, Duration } ) - - local DCSObject = self:GetDCSObject() - if DCSObject then - self:GetMessage( Message, Duration ):ToGroup( self ) - end - - return nil -end - --- This module contains the SCHEDULER class. -- --- 1) @{Scheduler#SCHEDULER} class, extends @{Base#BASE} --- ===================================================== --- The @{Scheduler#SCHEDULER} class models time events calling given event handling functions. +-- # 1) @{Core.Scheduler#SCHEDULER} class, extends @{Core.Base#BASE} +-- +-- The @{Core.Scheduler#SCHEDULER} class creates schedule. -- --- 1.1) SCHEDULER constructor --- -------------------------- --- The SCHEDULER class is quite easy to use: +-- ## 1.1) SCHEDULER constructor +-- +-- The SCHEDULER class is quite easy to use, but note that the New constructor has variable parameters: -- --- * @{Scheduler#SCHEDULER.New}: Setup a new scheduler and start it with the specified parameters. +-- * @{Core.Scheduler#SCHEDULER.New}( nil ): Setup a new SCHEDULER object, which is persistently executed after garbage collection. +-- * @{Core.Scheduler#SCHEDULER.New}( Object ): Setup a new SCHEDULER object, which is linked to the Object. When the Object is nillified or destroyed, the SCHEDULER object will also be destroyed and stopped after garbage collection. +-- * @{Core.Scheduler#SCHEDULER.New}( nil, Function, FunctionArguments, Start, ... ): Setup a new persistent SCHEDULER object, and start a new schedule for the Function with the defined FunctionArguments according the Start and sequent parameters. +-- * @{Core.Scheduler#SCHEDULER.New}( Object, Function, FunctionArguments, Start, ... ): Setup a new SCHEDULER object, linked to Object, and start a new schedule for the Function with the defined FunctionArguments according the Start and sequent parameters. +-- +-- ## 1.2) SCHEDULER timer stopping and (re-)starting. -- --- 1.2) SCHEDULER timer stop and start --- ----------------------------------- -- The SCHEDULER can be stopped and restarted with the following methods: -- --- * @{Scheduler#SCHEDULER.Start}: (Re-)Start the scheduler. --- * @{Scheduler#SCHEDULER.Stop}: Stop the scheduler. +-- * @{Core.Scheduler#SCHEDULER.Start}(): (Re-)Start the schedules within the SCHEDULER object. If a CallID is provided to :Start(), only the schedule referenced by CallID will be (re-)started. +-- * @{Core.Scheduler#SCHEDULER.Stop}(): Stop the schedules within the SCHEDULER object. If a CallID is provided to :Stop(), then only the schedule referenced by CallID will be stopped. -- --- 1.3) Reschedule new time event --- ------------------------------ --- With @{Scheduler#SCHEDULER.Schedule} a new time event can be scheduled. +-- ## 1.3) Create a new schedule +-- +-- With @{Core.Scheduler#SCHEDULER.Schedule}() a new time event can be scheduled. This function is used by the :New() constructor when a new schedule is planned. -- -- === -- -- ### Contributions: -- --- * Mechanist : Concept & Testing +-- * FlightControl : Concept & Testing -- -- ### Authors: -- -- * FlightControl : Design & Programming -- +-- ### Test Missions: +-- +-- * SCH - Scheduler +-- -- === -- -- @module Scheduler @@ -6350,170 +3743,341 @@ end --- The SCHEDULER class -- @type SCHEDULER -- @field #number ScheduleID the ID of the scheduler. --- @extends Base#BASE +-- @extends Core.Base#BASE SCHEDULER = { ClassName = "SCHEDULER", + Schedules = {}, } --- SCHEDULER constructor. -- @param #SCHEDULER self --- @param #table TimeEventObject Specified for which Moose object the timer is setup. If a value of nil is provided, a scheduler will be setup without an object reference. --- @param #function TimeEventFunction The event function to be called when a timer event occurs. The event function needs to accept the parameters specified in TimeEventFunctionArguments. --- @param #table TimeEventFunctionArguments Optional arguments that can be given as part of scheduler. The arguments need to be given as a table { param1, param 2, ... }. --- @param #number StartSeconds Specifies the amount of seconds that will be waited before the scheduling is started, and the event function is called. --- @param #number RepeatSecondsInterval Specifies the interval in seconds when the scheduler will call the event function. --- @param #number RandomizationFactor Specifies a randomization factor between 0 and 1 to randomize the RepeatSecondsInterval. --- @param #number StopSeconds Specifies the amount of seconds when the scheduler will be stopped. +-- @param #table SchedulerObject Specified for which Moose object the timer is setup. If a value of nil is provided, a scheduler will be setup without an object reference. +-- @param #function SchedulerFunction The event function to be called when a timer event occurs. The event function needs to accept the parameters specified in SchedulerArguments. +-- @param #table SchedulerArguments Optional arguments that can be given as part of scheduler. The arguments need to be given as a table { param1, param 2, ... }. +-- @param #number Start Specifies the amount of seconds that will be waited before the scheduling is started, and the event function is called. +-- @param #number Repeat Specifies the interval in seconds when the scheduler will call the event function. +-- @param #number RandomizeFactor Specifies a randomization factor between 0 and 1 to randomize the Repeat. +-- @param #number Stop Specifies the amount of seconds when the scheduler will be stopped. -- @return #SCHEDULER self -function SCHEDULER:New( TimeEventObject, TimeEventFunction, TimeEventFunctionArguments, StartSeconds, RepeatSecondsInterval, RandomizationFactor, StopSeconds ) +-- @return #number The ScheduleID of the planned schedule. +function SCHEDULER:New( SchedulerObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop ) local self = BASE:Inherit( self, BASE:New() ) - self:F2( { TimeEventObject, TimeEventFunction, TimeEventFunctionArguments, StartSeconds, RepeatSecondsInterval, RandomizationFactor, StopSeconds } ) + self:F2( { Start, Repeat, RandomizeFactor, Stop } ) + local ScheduleID = nil + + if SchedulerFunction then + ScheduleID = self:Schedule( SchedulerObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop ) + end - self:Schedule( TimeEventObject, TimeEventFunction, TimeEventFunctionArguments, StartSeconds, RepeatSecondsInterval, RandomizationFactor, StopSeconds ) - - return self + return self, ScheduleID end +--function SCHEDULER:_Destructor() +-- --self:E("_Destructor") +-- +-- _SCHEDULEDISPATCHER:RemoveSchedule( self.CallID ) +--end + --- Schedule a new time event. Note that the schedule will only take place if the scheduler is *started*. Even for a single schedule event, the scheduler needs to be started also. -- @param #SCHEDULER self --- @param #table TimeEventObject Specified for which Moose object the timer is setup. If a value of nil is provided, a scheduler will be setup without an object reference. --- @param #function TimeEventFunction The event function to be called when a timer event occurs. The event function needs to accept the parameters specified in TimeEventFunctionArguments. --- @param #table TimeEventFunctionArguments Optional arguments that can be given as part of scheduler. The arguments need to be given as a table { param1, param 2, ... }. --- @param #number StartSeconds Specifies the amount of seconds that will be waited before the scheduling is started, and the event function is called. --- @param #number RepeatSecondsInterval Specifies the interval in seconds when the scheduler will call the event function. --- @param #number RandomizationFactor Specifies a randomization factor between 0 and 1 to randomize the RepeatSecondsInterval. --- @param #number StopSeconds Specifies the amount of seconds when the scheduler will be stopped. --- @return #SCHEDULER self -function SCHEDULER:Schedule( TimeEventObject, TimeEventFunction, TimeEventFunctionArguments, StartSeconds, RepeatSecondsInterval, RandomizationFactor, StopSeconds ) - self:F2( { TimeEventFunctionArguments, StartSeconds, RepeatSecondsInterval, RandomizationFactor, StopSeconds } ) +-- @param #table SchedulerObject Specified for which Moose object the timer is setup. If a value of nil is provided, a scheduler will be setup without an object reference. +-- @param #function SchedulerFunction The event function to be called when a timer event occurs. The event function needs to accept the parameters specified in SchedulerArguments. +-- @param #table SchedulerArguments Optional arguments that can be given as part of scheduler. The arguments need to be given as a table { param1, param 2, ... }. +-- @param #number Start Specifies the amount of seconds that will be waited before the scheduling is started, and the event function is called. +-- @param #number Repeat Specifies the interval in seconds when the scheduler will call the event function. +-- @param #number RandomizeFactor Specifies a randomization factor between 0 and 1 to randomize the Repeat. +-- @param #number Stop Specifies the amount of seconds when the scheduler will be stopped. +-- @return #number The ScheduleID of the planned schedule. +function SCHEDULER:Schedule( SchedulerObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop ) + self:F2( { Start, Repeat, RandomizeFactor, Stop } ) + self:T3( { SchedulerArguments } ) - self.TimeEventObject = TimeEventObject - self.TimeEventFunction = TimeEventFunction - self.TimeEventFunctionArguments = TimeEventFunctionArguments - self.StartSeconds = StartSeconds - self.Repeat = false - self.RepeatSecondsInterval = RepeatSecondsInterval or 0 - self.RandomizationFactor = RandomizationFactor or 0 - self.StopSeconds = StopSeconds - self.StartTime = timer.getTime() + self.SchedulerObject = SchedulerObject + + local ScheduleID = _SCHEDULEDISPATCHER:AddSchedule( + self, + SchedulerFunction, + SchedulerArguments, + Start, + Repeat, + RandomizeFactor, + Stop + ) + + self.Schedules[#self.Schedules+1] = ScheduleID - self:Start() + return ScheduleID +end +--- (Re-)Starts the schedules or a specific schedule if a valid ScheduleID is provided. +-- @param #SCHEDULER self +-- @param #number ScheduleID (optional) The ScheduleID of the planned (repeating) schedule. +function SCHEDULER:Start( ScheduleID ) + self:F3( { ScheduleID } ) + + _SCHEDULEDISPATCHER:Start( self, ScheduleID ) +end + +--- Stops the schedules or a specific schedule if a valid ScheduleID is provided. +-- @param #SCHEDULER self +-- @param #number ScheduleID (optional) The ScheduleID of the planned (repeating) schedule. +function SCHEDULER:Stop( ScheduleID ) + self:F3( { ScheduleID } ) + + _SCHEDULEDISPATCHER:Stop( self, ScheduleID ) +end + +--- Removes a specific schedule if a valid ScheduleID is provided. +-- @param #SCHEDULER self +-- @param #number ScheduleID (optional) The ScheduleID of the planned (repeating) schedule. +function SCHEDULER:Remove( ScheduleID ) + self:F3( { ScheduleID } ) + + _SCHEDULEDISPATCHER:Remove( self, ScheduleID ) +end + + + + + + + + + + + + + + + +--- This module defines the SCHEDULEDISPATCHER class, which is used by a central object called _SCHEDULEDISPATCHER. +-- +-- === +-- +-- Takes care of the creation and dispatching of scheduled functions for SCHEDULER objects. +-- +-- This class is tricky and needs some thorought explanation. +-- SCHEDULE classes are used to schedule functions for objects, or as persistent objects. +-- The SCHEDULEDISPATCHER class ensures that: +-- +-- - Scheduled functions are planned according the SCHEDULER object parameters. +-- - Scheduled functions are repeated when requested, according the SCHEDULER object parameters. +-- - Scheduled functions are automatically removed when the schedule is finished, according the SCHEDULER object parameters. +-- +-- The SCHEDULEDISPATCHER class will manage SCHEDULER object in memory during garbage collection: +-- - When a SCHEDULER object is not attached to another object (that is, it's first :Schedule() parameter is nil), then the SCHEDULER +-- object is _persistent_ within memory. +-- - When a SCHEDULER object *is* attached to another object, then the SCHEDULER object is _not persistent_ within memory after a garbage collection! +-- The none persistency of SCHEDULERS attached to objects is required to allow SCHEDULER objects to be garbage collectged, when the parent object is also desroyed or nillified and garbage collected. +-- Even when there are pending timer scheduled functions to be executed for the SCHEDULER object, +-- these will not be executed anymore when the SCHEDULER object has been destroyed. +-- +-- The SCHEDULEDISPATCHER allows multiple scheduled functions to be planned and executed for one SCHEDULER object. +-- The SCHEDULER object therefore keeps a table of "CallID's", which are returned after each planning of a new scheduled function by the SCHEDULEDISPATCHER. +-- The SCHEDULER object plans new scheduled functions through the @{Core.Scheduler#SCHEDULER.Schedule}() method. +-- The Schedule() method returns the CallID that is the reference ID for each planned schedule. +-- +-- === +-- +-- === +-- +-- ### Contributions: - +-- ### Authors: FlightControl : Design & Programming +-- +-- @module ScheduleDispatcher + +--- The SCHEDULEDISPATCHER structure +-- @type SCHEDULEDISPATCHER +SCHEDULEDISPATCHER = { + ClassName = "SCHEDULEDISPATCHER", + CallID = 0, +} + +function SCHEDULEDISPATCHER:New() + local self = BASE:Inherit( self, BASE:New() ) + self:F3() return self end ---- (Re-)Starts the scheduler. --- @param #SCHEDULER self --- @return #SCHEDULER self -function SCHEDULER:Start() - self:F2( self.TimeEventObject ) +--- Add a Schedule to the ScheduleDispatcher. +-- The development of this method was really tidy. +-- It is constructed as such that a garbage collection is executed on the weak tables, when the Scheduler is nillified. +-- Nothing of this code should be modified without testing it thoroughly. +-- @param #SCHEDULEDISPATCHER self +-- @param Core.Scheduler#SCHEDULER Scheduler +function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleArguments, Start, Repeat, Randomize, Stop ) + self:F2( { Scheduler, ScheduleFunction, ScheduleArguments, Start, Repeat, Randomize, Stop } ) - if self.RepeatSecondsInterval ~= 0 then - self.Repeat = true + self.CallID = self.CallID + 1 + + -- Initialize the ObjectSchedulers array, which is a weakly coupled table. + -- If the object used as the key is nil, then the garbage collector will remove the item from the Functions array. + self.PersistentSchedulers = self.PersistentSchedulers or {} + + -- Initialize the ObjectSchedulers array, which is a weakly coupled table. + -- If the object used as the key is nil, then the garbage collector will remove the item from the Functions array. + self.ObjectSchedulers = self.ObjectSchedulers or setmetatable( {}, { __mode = "v" } ) + + if Scheduler.SchedulerObject then + self.ObjectSchedulers[self.CallID] = Scheduler + self:T3( { self.CallID, self.ObjectSchedulers[self.CallID] } ) + else + self.PersistentSchedulers[self.CallID] = Scheduler + self:T3( { self.CallID, self.PersistentSchedulers[self.CallID] } ) end - if self.StartSeconds then - if self.ScheduleID then - timer.removeFunction( self.ScheduleID ) - end - self.ScheduleID = timer.scheduleFunction( self._Scheduler, self, timer.getTime() + self.StartSeconds + .01 ) - end - - return self -end + self.Schedule = self.Schedule or setmetatable( {}, { __mode = "k" } ) + self.Schedule[Scheduler] = {} + self.Schedule[Scheduler][self.CallID] = {} + self.Schedule[Scheduler][self.CallID].Function = ScheduleFunction + self.Schedule[Scheduler][self.CallID].Arguments = ScheduleArguments + self.Schedule[Scheduler][self.CallID].StartTime = timer.getTime() + ( Start or 0 ) + self.Schedule[Scheduler][self.CallID].Start = Start + .001 + self.Schedule[Scheduler][self.CallID].Repeat = Repeat + self.Schedule[Scheduler][self.CallID].Randomize = Randomize + self.Schedule[Scheduler][self.CallID].Stop = Stop ---- Stops the scheduler. --- @param #SCHEDULER self --- @return #SCHEDULER self -function SCHEDULER:Stop() - self:F2( self.TimeEventObject ) + self:T3( self.Schedule[Scheduler][self.CallID] ) - self.Repeat = false - if self.ScheduleID then - self:E( "Stop Schedule" ) - timer.removeFunction( self.ScheduleID ) - end - self.ScheduleID = nil + self.Schedule[Scheduler][self.CallID].CallHandler = function( CallID ) + self:F2( CallID ) - return self -end - --- Private Functions - ---- @param #SCHEDULER self -function SCHEDULER:_Scheduler() - self:F2( self.TimeEventFunctionArguments ) - - local ErrorHandler = function( errmsg ) - - env.info( "Error in SCHEDULER function:" .. errmsg ) - if debug ~= nil then - env.info( debug.traceback() ) + local ErrorHandler = function( errmsg ) + env.info( "Error in timer function: " .. errmsg ) + if debug ~= nil then + env.info( debug.traceback() ) + end + return errmsg end - return errmsg + local Scheduler = self.ObjectSchedulers[CallID] + if not Scheduler then + Scheduler = self.PersistentSchedulers[CallID] + end + + self:T3( { Scheduler = Scheduler } ) + + if Scheduler then + + local Schedule = self.Schedule[Scheduler][CallID] + + self:T3( { Schedule = Schedule } ) + + local ScheduleObject = Scheduler.SchedulerObject + --local ScheduleObjectName = Scheduler.SchedulerObject:GetNameAndClassID() + local ScheduleFunction = Schedule.Function + local ScheduleArguments = Schedule.Arguments + local Start = Schedule.Start + local Repeat = Schedule.Repeat or 0 + local Randomize = Schedule.Randomize or 0 + local Stop = Schedule.Stop or 0 + local ScheduleID = Schedule.ScheduleID + + local Status, Result + if ScheduleObject then + local function Timer() + return ScheduleFunction( ScheduleObject, unpack( ScheduleArguments ) ) + end + Status, Result = xpcall( Timer, ErrorHandler ) + else + local function Timer() + return ScheduleFunction( unpack( ScheduleArguments ) ) + end + Status, Result = xpcall( Timer, ErrorHandler ) + end + + local CurrentTime = timer.getTime() + local StartTime = CurrentTime + Start + + if Status and (( Result == nil ) or ( Result and Result ~= false ) ) then + if Repeat ~= 0 and ( Stop == 0 ) or ( Stop ~= 0 and CurrentTime <= StartTime + Stop ) then + local ScheduleTime = + CurrentTime + + Repeat + + math.random( + - ( Randomize * Repeat / 2 ), + ( Randomize * Repeat / 2 ) + ) + + 0.01 + self:T3( { Repeat = CallID, CurrentTime, ScheduleTime, ScheduleArguments } ) + return ScheduleTime -- returns the next time the function needs to be called. + else + self:Stop( Scheduler, CallID ) + end + else + self:Stop( Scheduler, CallID ) + end + else + --self:E( "Scheduled obscolete call for CallID: " .. CallID ) + end + + return nil end - local StartTime = self.StartTime - local StopSeconds = self.StopSeconds - local Repeat = self.Repeat - local RandomizationFactor = self.RandomizationFactor - local RepeatSecondsInterval = self.RepeatSecondsInterval - local ScheduleID = self.ScheduleID + self:Start( Scheduler, self.CallID ) + + return self.CallID +end - local Status, Result - if self.TimeEventObject then - Status, Result = xpcall( function() return self.TimeEventFunction( self.TimeEventObject, unpack( self.TimeEventFunctionArguments ) ) end, ErrorHandler ) - else - Status, Result = xpcall( function() return self.TimeEventFunction( unpack( self.TimeEventFunctionArguments ) ) end, ErrorHandler ) +function SCHEDULEDISPATCHER:RemoveSchedule( Scheduler, CallID ) + self:F2( { Remove = CallID, Scheduler = Scheduler } ) + + if CallID then + self:Stop( Scheduler, CallID ) + self.Schedule[Scheduler][CallID] = nil end +end - self:T( { "Timer Event2 .. " .. self.ScheduleID, Status, Result, StartTime, RepeatSecondsInterval, RandomizationFactor, StopSeconds } ) +function SCHEDULEDISPATCHER:Start( Scheduler, CallID ) + self:F2( { Start = CallID, Scheduler = Scheduler } ) - if Status and ( ( Result == nil ) or ( Result and Result ~= false ) ) then - if Repeat and ( not StopSeconds or ( StopSeconds and timer.getTime() <= StartTime + StopSeconds ) ) then - local ScheduleTime = - timer.getTime() + - self.RepeatSecondsInterval + - math.random( - - ( RandomizationFactor * RepeatSecondsInterval / 2 ), - ( RandomizationFactor * RepeatSecondsInterval / 2 ) - ) + - 0.01 - self:T( { self.TimeEventFunctionArguments, "Repeat:", timer.getTime(), ScheduleTime } ) - return ScheduleTime -- returns the next time the function needs to be called. - else - timer.removeFunction( ScheduleID ) - self.ScheduleID = nil + if CallID then + local Schedule = self.Schedule[Scheduler] + Schedule[CallID].ScheduleID = timer.scheduleFunction( + Schedule[CallID].CallHandler, + CallID, + timer.getTime() + Schedule[CallID].Start + ) + else + for CallID, Schedule in pairs( self.Schedule[Scheduler] ) do + self:Start( Scheduler, CallID ) -- Recursive end - else - timer.removeFunction( ScheduleID ) - self.ScheduleID = nil end +end - return nil +function SCHEDULEDISPATCHER:Stop( Scheduler, CallID ) + self:F2( { Stop = CallID, Scheduler = Scheduler } ) + + if CallID then + local Schedule = self.Schedule[Scheduler] + timer.removeFunction( Schedule[CallID].ScheduleID ) + else + for CallID, Schedule in pairs( self.Schedule[Scheduler] ) do + self:Stop( Scheduler, CallID ) -- Recursive + end + end end - - - - - - - - - - - - - ---- The EVENT class models an efficient event handling process between other classes and its units, weapons. +--- This module contains the EVENT class. +-- +-- === +-- +-- Takes care of EVENT dispatching between DCS events and event handling functions defined in MOOSE classes. +-- +-- === +-- +-- The above menus classes **are derived** from 2 main **abstract** classes defined within the MOOSE framework (so don't use these): +-- +-- === +-- +-- ### Contributions: - +-- ### Authors: FlightControl : Design & Programming +-- -- @module Event --- @author FlightControl --- The EVENT structure -- @type EVENT @@ -6558,13 +4122,13 @@ local _EVENTCODES = { -- @field weapon -- @field IniDCSUnit -- @field IniDCSUnitName --- @field Unit#UNIT IniUnit +-- @field Wrapper.Unit#UNIT IniUnit -- @field #string IniUnitName -- @field IniDCSGroup -- @field IniDCSGroupName -- @field TgtDCSUnit -- @field TgtDCSUnitName --- @field Unit#UNIT TgtUnit +-- @field Wrapper.Unit#UNIT TgtUnit -- @field #string TgtUnitName -- @field TgtDCSGroup -- @field TgtDCSGroupName @@ -6593,45 +4157,62 @@ end --- Initializes the Events structure for the event -- @param #EVENT self --- @param DCSWorld#world.event EventID --- @param #string EventClass +-- @param Dcs.DCSWorld#world.event EventID +-- @param Core.Base#BASE EventClass -- @return #EVENT.Events function EVENT:Init( EventID, EventClass ) self:F3( { _EVENTCODES[EventID], EventClass } ) - if not self.Events[EventID] then - self.Events[EventID] = {} + + if not self.Events[EventID] then + -- Create a WEAK table to ensure that the garbage collector is cleaning the event links when the object usage is cleaned. + self.Events[EventID] = setmetatable( {}, { __mode = "k" } ) + end + if not self.Events[EventID][EventClass] then - self.Events[EventID][EventClass] = {} + self.Events[EventID][EventClass] = setmetatable( {}, { __mode = "k" } ) end return self.Events[EventID][EventClass] end --- Removes an Events entry -- @param #EVENT self --- @param Base#BASE EventSelf The self instance of the class for which the event is. --- @param DCSWorld#world.event EventID +-- @param Core.Base#BASE EventClass The self instance of the class for which the event is. +-- @param Dcs.DCSWorld#world.event EventID -- @return #EVENT.Events -function EVENT:Remove( EventSelf, EventID ) - self:F3( { EventSelf, _EVENTCODES[EventID] } ) +function EVENT:Remove( EventClass, EventID ) + self:F3( { EventClass, _EVENTCODES[EventID] } ) - local EventClass = EventSelf:GetClassNameAndID() + local EventClass = EventClass self.Events[EventID][EventClass] = nil end +--- Clears all event subscriptions for a @{Core.Base#BASE} derived object. +-- @param #EVENT self +-- @param Core.Base#BASE EventObject +function EVENT:RemoveAll( EventObject ) + self:F3( { EventObject:GetClassNameAndID() } ) + + local EventClass = EventObject:GetClassNameAndID() + for EventID, EventData in pairs( self.Events ) do + self.Events[EventID][EventClass] = nil + end +end + + --- Create an OnDead event handler for a group -- @param #EVENT self -- @param #table EventTemplate -- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param EventSelf The self instance of the class for which the event is. +-- @param EventClass The instance of the class for which the event is. -- @param #function OnEventFunction -- @return #EVENT -function EVENT:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, OnEventFunction ) +function EVENT:OnEventForTemplate( EventTemplate, EventFunction, EventClass, OnEventFunction ) self:F2( EventTemplate.name ) for EventUnitID, EventUnit in pairs( EventTemplate.units ) do - OnEventFunction( self, EventUnit.name, EventFunction, EventSelf ) + OnEventFunction( self, EventUnit.name, EventFunction, EventClass ) end return self end @@ -6639,15 +4220,15 @@ end --- Set a new listener for an S_EVENT_X event independent from a unit or a weapon. -- @param #EVENT self -- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf The self instance of the class for which the event is. +-- @param Core.Base#BASE EventClass The self instance of the class for which the event is captured. When the event happens, the event process will be called in this class provided. -- @param EventID -- @return #EVENT -function EVENT:OnEventGeneric( EventFunction, EventSelf, EventID ) +function EVENT:OnEventGeneric( EventFunction, EventClass, EventID ) self:F2( { EventID } ) - local Event = self:Init( EventID, EventSelf:GetClassNameAndID() ) + local Event = self:Init( EventID, EventClass ) Event.EventFunction = EventFunction - Event.EventSelf = EventSelf + Event.EventClass = EventClass return self end @@ -6656,19 +4237,19 @@ end -- @param #EVENT self -- @param #string EventDCSUnitName -- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf The self instance of the class for which the event is. +-- @param Core.Base#BASE EventClass The self instance of the class for which the event is. -- @param EventID -- @return #EVENT -function EVENT:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, EventID ) +function EVENT:OnEventForUnit( EventDCSUnitName, EventFunction, EventClass, EventID ) self:F2( EventDCSUnitName ) - local Event = self:Init( EventID, EventSelf:GetClassNameAndID() ) + local Event = self:Init( EventID, EventClass ) if not Event.IniUnit then Event.IniUnit = {} end Event.IniUnit[EventDCSUnitName] = {} Event.IniUnit[EventDCSUnitName].EventFunction = EventFunction - Event.IniUnit[EventDCSUnitName].EventSelf = EventSelf + Event.IniUnit[EventDCSUnitName].EventClass = EventClass return self end @@ -6676,14 +4257,14 @@ do -- OnBirth --- Create an OnBirth event handler for a group -- @param #EVENT self - -- @param Group#GROUP EventGroup + -- @param Wrapper.Group#GROUP EventGroup -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param EventSelf The self instance of the class for which the event is. + -- @param EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnBirthForTemplate( EventTemplate, EventFunction, EventSelf ) + function EVENT:OnBirthForTemplate( EventTemplate, EventFunction, EventClass ) self:F2( EventTemplate.name ) - self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnBirthForUnit ) + self:OnEventForTemplate( EventTemplate, EventFunction, EventClass, self.OnBirthForUnit ) return self end @@ -6691,12 +4272,12 @@ do -- OnBirth --- Set a new listener for an S_EVENT_BIRTH event, and registers the unit born. -- @param #EVENT self -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf + -- @param Base#BASE EventClass -- @return #EVENT - function EVENT:OnBirth( EventFunction, EventSelf ) + function EVENT:OnBirth( EventFunction, EventClass ) self:F2() - self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_BIRTH ) + self:OnEventGeneric( EventFunction, EventClass, world.event.S_EVENT_BIRTH ) return self end @@ -6705,24 +4286,24 @@ do -- OnBirth -- @param #EVENT self -- @param #string EventDCSUnitName The id of the unit for the event to be handled. -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf + -- @param Base#BASE EventClass -- @return #EVENT - function EVENT:OnBirthForUnit( EventDCSUnitName, EventFunction, EventSelf ) + function EVENT:OnBirthForUnit( EventDCSUnitName, EventFunction, EventClass ) self:F2( EventDCSUnitName ) - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_BIRTH ) + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventClass, world.event.S_EVENT_BIRTH ) return self end --- Stop listening to S_EVENT_BIRTH event. -- @param #EVENT self - -- @param Base#BASE EventSelf + -- @param Base#BASE EventClass -- @return #EVENT - function EVENT:OnBirthRemove( EventSelf ) + function EVENT:OnBirthRemove( EventClass ) self:F2() - self:Remove( EventSelf, world.event.S_EVENT_BIRTH ) + self:Remove( EventClass, world.event.S_EVENT_BIRTH ) return self end @@ -6734,14 +4315,14 @@ do -- OnCrash --- Create an OnCrash event handler for a group -- @param #EVENT self - -- @param Group#GROUP EventGroup + -- @param Wrapper.Group#GROUP EventGroup -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param EventSelf The self instance of the class for which the event is. + -- @param EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnCrashForTemplate( EventTemplate, EventFunction, EventSelf ) + function EVENT:OnCrashForTemplate( EventTemplate, EventFunction, EventClass ) self:F2( EventTemplate.name ) - self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnCrashForUnit ) + self:OnEventForTemplate( EventTemplate, EventFunction, EventClass, self.OnCrashForUnit ) return self end @@ -6749,12 +4330,12 @@ do -- OnCrash --- Set a new listener for an S_EVENT_CRASH event. -- @param #EVENT self -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf + -- @param Base#BASE EventClass -- @return #EVENT - function EVENT:OnCrash( EventFunction, EventSelf ) + function EVENT:OnCrash( EventFunction, EventClass ) self:F2() - self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_CRASH ) + self:OnEventGeneric( EventFunction, EventClass, world.event.S_EVENT_CRASH ) return self end @@ -6763,24 +4344,24 @@ do -- OnCrash -- @param #EVENT self -- @param #string EventDCSUnitName -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @param Base#BASE EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnCrashForUnit( EventDCSUnitName, EventFunction, EventSelf ) + function EVENT:OnCrashForUnit( EventDCSUnitName, EventFunction, EventClass ) self:F2( EventDCSUnitName ) - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_CRASH ) + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventClass, world.event.S_EVENT_CRASH ) return self end --- Stop listening to S_EVENT_CRASH event. -- @param #EVENT self - -- @param Base#BASE EventSelf + -- @param Base#BASE EventClass -- @return #EVENT - function EVENT:OnCrashRemove( EventSelf ) + function EVENT:OnCrashRemove( EventClass ) self:F2() - self:Remove( EventSelf, world.event.S_EVENT_CRASH ) + self:Remove( EventClass, world.event.S_EVENT_CRASH ) return self end @@ -6791,14 +4372,14 @@ do -- OnDead --- Create an OnDead event handler for a group -- @param #EVENT self - -- @param Group#GROUP EventGroup + -- @param Wrapper.Group#GROUP EventGroup -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param EventSelf The self instance of the class for which the event is. + -- @param EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnDeadForTemplate( EventTemplate, EventFunction, EventSelf ) + function EVENT:OnDeadForTemplate( EventTemplate, EventFunction, EventClass ) self:F2( EventTemplate.name ) - self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnDeadForUnit ) + self:OnEventForTemplate( EventTemplate, EventFunction, EventClass, self.OnDeadForUnit ) return self end @@ -6806,12 +4387,12 @@ do -- OnDead --- Set a new listener for an S_EVENT_DEAD event. -- @param #EVENT self -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf + -- @param Base#BASE EventClass -- @return #EVENT - function EVENT:OnDead( EventFunction, EventSelf ) + function EVENT:OnDead( EventFunction, EventClass ) self:F2() - self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_DEAD ) + self:OnEventGeneric( EventFunction, EventClass, world.event.S_EVENT_DEAD ) return self end @@ -6821,24 +4402,24 @@ do -- OnDead -- @param #EVENT self -- @param #string EventDCSUnitName -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @param Base#BASE EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnDeadForUnit( EventDCSUnitName, EventFunction, EventSelf ) + function EVENT:OnDeadForUnit( EventDCSUnitName, EventFunction, EventClass ) self:F2( EventDCSUnitName ) - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_DEAD ) + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventClass, world.event.S_EVENT_DEAD ) return self end --- Stop listening to S_EVENT_DEAD event. -- @param #EVENT self - -- @param Base#BASE EventSelf + -- @param Base#BASE EventClass -- @return #EVENT - function EVENT:OnDeadRemove( EventSelf ) + function EVENT:OnDeadRemove( EventClass ) self:F2() - self:Remove( EventSelf, world.event.S_EVENT_DEAD ) + self:Remove( EventClass, world.event.S_EVENT_DEAD ) return self end @@ -6851,12 +4432,12 @@ do -- OnPilotDead --- Set a new listener for an S_EVENT_PILOT_DEAD event. -- @param #EVENT self -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf + -- @param Base#BASE EventClass -- @return #EVENT - function EVENT:OnPilotDead( EventFunction, EventSelf ) + function EVENT:OnPilotDead( EventFunction, EventClass ) self:F2() - self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_PILOT_DEAD ) + self:OnEventGeneric( EventFunction, EventClass, world.event.S_EVENT_PILOT_DEAD ) return self end @@ -6865,24 +4446,24 @@ do -- OnPilotDead -- @param #EVENT self -- @param #string EventDCSUnitName -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @param Base#BASE EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnPilotDeadForUnit( EventDCSUnitName, EventFunction, EventSelf ) + function EVENT:OnPilotDeadForUnit( EventDCSUnitName, EventFunction, EventClass ) self:F2( EventDCSUnitName ) - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_PILOT_DEAD ) + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventClass, world.event.S_EVENT_PILOT_DEAD ) return self end --- Stop listening to S_EVENT_PILOT_DEAD event. -- @param #EVENT self - -- @param Base#BASE EventSelf + -- @param Base#BASE EventClass -- @return #EVENT - function EVENT:OnPilotDeadRemove( EventSelf ) + function EVENT:OnPilotDeadRemove( EventClass ) self:F2() - self:Remove( EventSelf, world.event.S_EVENT_PILOT_DEAD ) + self:Remove( EventClass, world.event.S_EVENT_PILOT_DEAD ) return self end @@ -6894,12 +4475,12 @@ do -- OnLand -- @param #EVENT self -- @param #table EventTemplate -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param EventSelf The self instance of the class for which the event is. + -- @param EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnLandForTemplate( EventTemplate, EventFunction, EventSelf ) + function EVENT:OnLandForTemplate( EventTemplate, EventFunction, EventClass ) self:F2( EventTemplate.name ) - self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnLandForUnit ) + self:OnEventForTemplate( EventTemplate, EventFunction, EventClass, self.OnLandForUnit ) return self end @@ -6908,24 +4489,24 @@ do -- OnLand -- @param #EVENT self -- @param #string EventDCSUnitName -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @param Base#BASE EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnLandForUnit( EventDCSUnitName, EventFunction, EventSelf ) + function EVENT:OnLandForUnit( EventDCSUnitName, EventFunction, EventClass ) self:F2( EventDCSUnitName ) - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_LAND ) + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventClass, world.event.S_EVENT_LAND ) return self end --- Stop listening to S_EVENT_LAND event. -- @param #EVENT self - -- @param Base#BASE EventSelf + -- @param Base#BASE EventClass -- @return #EVENT - function EVENT:OnLandRemove( EventSelf ) + function EVENT:OnLandRemove( EventClass ) self:F2() - self:Remove( EventSelf, world.event.S_EVENT_LAND ) + self:Remove( EventClass, world.event.S_EVENT_LAND ) return self end @@ -6938,12 +4519,12 @@ do -- OnTakeOff -- @param #EVENT self -- @param #table EventTemplate -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param EventSelf The self instance of the class for which the event is. + -- @param EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnTakeOffForTemplate( EventTemplate, EventFunction, EventSelf ) + function EVENT:OnTakeOffForTemplate( EventTemplate, EventFunction, EventClass ) self:F2( EventTemplate.name ) - self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnTakeOffForUnit ) + self:OnEventForTemplate( EventTemplate, EventFunction, EventClass, self.OnTakeOffForUnit ) return self end @@ -6952,24 +4533,24 @@ do -- OnTakeOff -- @param #EVENT self -- @param #string EventDCSUnitName -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @param Base#BASE EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnTakeOffForUnit( EventDCSUnitName, EventFunction, EventSelf ) + function EVENT:OnTakeOffForUnit( EventDCSUnitName, EventFunction, EventClass ) self:F2( EventDCSUnitName ) - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_TAKEOFF ) + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventClass, world.event.S_EVENT_TAKEOFF ) return self end --- Stop listening to S_EVENT_TAKEOFF event. -- @param #EVENT self - -- @param Base#BASE EventSelf + -- @param Base#BASE EventClass -- @return #EVENT - function EVENT:OnTakeOffRemove( EventSelf ) + function EVENT:OnTakeOffRemove( EventClass ) self:F2() - self:Remove( EventSelf, world.event.S_EVENT_TAKEOFF ) + self:Remove( EventClass, world.event.S_EVENT_TAKEOFF ) return self end @@ -6983,12 +4564,12 @@ do -- OnEngineShutDown -- @param #EVENT self -- @param #table EventTemplate -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param EventSelf The self instance of the class for which the event is. + -- @param EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnEngineShutDownForTemplate( EventTemplate, EventFunction, EventSelf ) + function EVENT:OnEngineShutDownForTemplate( EventTemplate, EventFunction, EventClass ) self:F2( EventTemplate.name ) - self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnEngineShutDownForUnit ) + self:OnEventForTemplate( EventTemplate, EventFunction, EventClass, self.OnEngineShutDownForUnit ) return self end @@ -6997,24 +4578,24 @@ do -- OnEngineShutDown -- @param #EVENT self -- @param #string EventDCSUnitName -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @param Base#BASE EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnEngineShutDownForUnit( EventDCSUnitName, EventFunction, EventSelf ) + function EVENT:OnEngineShutDownForUnit( EventDCSUnitName, EventFunction, EventClass ) self:F2( EventDCSUnitName ) - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_ENGINE_SHUTDOWN ) + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventClass, world.event.S_EVENT_ENGINE_SHUTDOWN ) return self end --- Stop listening to S_EVENT_ENGINE_SHUTDOWN event. -- @param #EVENT self - -- @param Base#BASE EventSelf + -- @param Base#BASE EventClass -- @return #EVENT - function EVENT:OnEngineShutDownRemove( EventSelf ) + function EVENT:OnEngineShutDownRemove( EventClass ) self:F2() - self:Remove( EventSelf, world.event.S_EVENT_ENGINE_SHUTDOWN ) + self:Remove( EventClass, world.event.S_EVENT_ENGINE_SHUTDOWN ) return self end @@ -7027,24 +4608,24 @@ do -- OnEngineStartUp -- @param #EVENT self -- @param #string EventDCSUnitName -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @param Base#BASE EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnEngineStartUpForUnit( EventDCSUnitName, EventFunction, EventSelf ) + function EVENT:OnEngineStartUpForUnit( EventDCSUnitName, EventFunction, EventClass ) self:F2( EventDCSUnitName ) - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_ENGINE_STARTUP ) + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventClass, world.event.S_EVENT_ENGINE_STARTUP ) return self end --- Stop listening to S_EVENT_ENGINE_STARTUP event. -- @param #EVENT self - -- @param Base#BASE EventSelf + -- @param Base#BASE EventClass -- @return #EVENT - function EVENT:OnEngineStartUpRemove( EventSelf ) + function EVENT:OnEngineStartUpRemove( EventClass ) self:F2() - self:Remove( EventSelf, world.event.S_EVENT_ENGINE_STARTUP ) + self:Remove( EventClass, world.event.S_EVENT_ENGINE_STARTUP ) return self end @@ -7055,12 +4636,12 @@ do -- OnShot --- Set a new listener for an S_EVENT_SHOT event. -- @param #EVENT self -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @param Base#BASE EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnShot( EventFunction, EventSelf ) + function EVENT:OnShot( EventFunction, EventClass ) self:F2() - self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_SHOT ) + self:OnEventGeneric( EventFunction, EventClass, world.event.S_EVENT_SHOT ) return self end @@ -7069,24 +4650,24 @@ do -- OnShot -- @param #EVENT self -- @param #string EventDCSUnitName -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @param Base#BASE EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnShotForUnit( EventDCSUnitName, EventFunction, EventSelf ) + function EVENT:OnShotForUnit( EventDCSUnitName, EventFunction, EventClass ) self:F2( EventDCSUnitName ) - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_SHOT ) + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventClass, world.event.S_EVENT_SHOT ) return self end --- Stop listening to S_EVENT_SHOT event. -- @param #EVENT self - -- @param Base#BASE EventSelf + -- @param Base#BASE EventClass -- @return #EVENT - function EVENT:OnShotRemove( EventSelf ) + function EVENT:OnShotRemove( EventClass ) self:F2() - self:Remove( EventSelf, world.event.S_EVENT_SHOT ) + self:Remove( EventClass, world.event.S_EVENT_SHOT ) return self end @@ -7099,12 +4680,12 @@ do -- OnHit --- Set a new listener for an S_EVENT_HIT event. -- @param #EVENT self -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @param Base#BASE EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnHit( EventFunction, EventSelf ) + function EVENT:OnHit( EventFunction, EventClass ) self:F2() - self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_HIT ) + self:OnEventGeneric( EventFunction, EventClass, world.event.S_EVENT_HIT ) return self end @@ -7113,24 +4694,24 @@ do -- OnHit -- @param #EVENT self -- @param #string EventDCSUnitName -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @param Base#BASE EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnHitForUnit( EventDCSUnitName, EventFunction, EventSelf ) + function EVENT:OnHitForUnit( EventDCSUnitName, EventFunction, EventClass ) self:F2( EventDCSUnitName ) - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_HIT ) + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventClass, world.event.S_EVENT_HIT ) return self end --- Stop listening to S_EVENT_HIT event. -- @param #EVENT self - -- @param Base#BASE EventSelf + -- @param Base#BASE EventClass -- @return #EVENT - function EVENT:OnHitRemove( EventSelf ) + function EVENT:OnHitRemove( EventClass ) self:F2() - self:Remove( EventSelf, world.event.S_EVENT_HIT ) + self:Remove( EventClass, world.event.S_EVENT_HIT ) return self end @@ -7142,24 +4723,24 @@ do -- OnPlayerEnterUnit --- Set a new listener for an S_EVENT_PLAYER_ENTER_UNIT event. -- @param #EVENT self -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @param Base#BASE EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnPlayerEnterUnit( EventFunction, EventSelf ) + function EVENT:OnPlayerEnterUnit( EventFunction, EventClass ) self:F2() - self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_PLAYER_ENTER_UNIT ) + self:OnEventGeneric( EventFunction, EventClass, world.event.S_EVENT_PLAYER_ENTER_UNIT ) return self end --- Stop listening to S_EVENT_PLAYER_ENTER_UNIT event. -- @param #EVENT self - -- @param Base#BASE EventSelf + -- @param Base#BASE EventClass -- @return #EVENT - function EVENT:OnPlayerEnterRemove( EventSelf ) + function EVENT:OnPlayerEnterRemove( EventClass ) self:F2() - self:Remove( EventSelf, world.event.S_EVENT_PLAYER_ENTER_UNIT ) + self:Remove( EventClass, world.event.S_EVENT_PLAYER_ENTER_UNIT ) return self end @@ -7170,24 +4751,24 @@ do -- OnPlayerLeaveUnit --- Set a new listener for an S_EVENT_PLAYER_LEAVE_UNIT event. -- @param #EVENT self -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @param Base#BASE EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnPlayerLeaveUnit( EventFunction, EventSelf ) + function EVENT:OnPlayerLeaveUnit( EventFunction, EventClass ) self:F2() - self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_PLAYER_LEAVE_UNIT ) + self:OnEventGeneric( EventFunction, EventClass, world.event.S_EVENT_PLAYER_LEAVE_UNIT ) return self end --- Stop listening to S_EVENT_PLAYER_LEAVE_UNIT event. -- @param #EVENT self - -- @param Base#BASE EventSelf + -- @param Base#BASE EventClass -- @return #EVENT - function EVENT:OnPlayerLeaveRemove( EventSelf ) + function EVENT:OnPlayerLeaveRemove( EventClass ) self:F2() - self:Remove( EventSelf, world.event.S_EVENT_PLAYER_LEAVE_UNIT ) + self:Remove( EventClass, world.event.S_EVENT_PLAYER_LEAVE_UNIT ) return self end @@ -7207,6 +4788,10 @@ function EVENT:onEvent( Event ) Event.IniDCSUnitName = Event.IniDCSUnit:getName() Event.IniUnitName = Event.IniDCSUnitName Event.IniUnit = UNIT:FindByName( Event.IniDCSUnitName ) + if not Event.IniUnit then + -- Unit can be a CLIENT. Most likely this will be the case ... + Event.IniUnit = CLIENT:FindByName( Event.IniDCSUnitName, '', true ) + end Event.IniDCSGroupName = "" if Event.IniDCSGroup and Event.IniDCSGroup:isExist() then Event.IniDCSGroupName = Event.IniDCSGroup:getName() @@ -7230,16 +4815,21 @@ function EVENT:onEvent( Event ) Event.WeaponName = Event.Weapon:getTypeName() --Event.WeaponTgtDCSUnit = Event.Weapon:getTarget() end - self:E( { _EVENTCODES[Event.id], Event.initiator, Event.IniDCSUnitName, Event.target, Event.TgtDCSUnitName, Event.weapon, Event.WeaponName } ) - for ClassName, EventData in pairs( self.Events[Event.id] ) do + self:E( { _EVENTCODES[Event.id], Event, Event.IniDCSUnitName, Event.TgtDCSUnitName } ) + + -- Okay, we got the event from DCS. Now loop the self.Events[] table for the received Event.id, and for each EventData registered, check if a function needs to be called. + for EventClass, EventData in pairs( self.Events[Event.id] ) do + -- If the EventData is for a UNIT, the call directly the EventClass EventFunction for that UNIT. if Event.IniDCSUnitName and EventData.IniUnit and EventData.IniUnit[Event.IniDCSUnitName] then - self:T( { "Calling event function for class ", ClassName, " unit ", Event.IniUnitName } ) - EventData.IniUnit[Event.IniDCSUnitName].EventFunction( EventData.IniUnit[Event.IniDCSUnitName].EventSelf, Event ) + self:T( { "Calling EventFunction for Class ", EventClass:GetClassNameAndID(), ", Unit ", Event.IniUnitName } ) + EventData.IniUnit[Event.IniDCSUnitName].EventFunction( EventData.IniUnit[Event.IniDCSUnitName].EventClass, Event ) else + -- If the EventData is not bound to a specific unit, then call the EventClass EventFunction. + -- Note that here the EventFunction will need to implement and determine the logic for the relevant source- or target unit, or weapon. if Event.IniDCSUnit and not EventData.IniUnit then - if ClassName == EventData.EventSelf:GetClassNameAndID() then - self:T( { "Calling event function for class ", ClassName } ) - EventData.EventFunction( EventData.EventSelf, Event ) + if EventClass == EventData.EventClass then + self:T( { "Calling EventFunction for Class ", EventClass:GetClassNameAndID() } ) + EventData.EventFunction( EventData.EventClass, Event ) end end end @@ -7266,17 +4856,17 @@ end -- -- ### To manage **main menus**, the classes begin with **MENU_**: -- --- * @{Menu#MENU_MISSION}: Manages main menus for whole mission file. --- * @{Menu#MENU_COALITION}: Manages main menus for whole coalition. --- * @{Menu#MENU_GROUP}: Manages main menus for GROUPs. --- * @{Menu#MENU_CLIENT}: Manages main menus for CLIENTs. This manages menus for units with the skill level "Client". +-- * @{Core.Menu#MENU_MISSION}: Manages main menus for whole mission file. +-- * @{Core.Menu#MENU_COALITION}: Manages main menus for whole coalition. +-- * @{Core.Menu#MENU_GROUP}: Manages main menus for GROUPs. +-- * @{Core.Menu#MENU_CLIENT}: Manages main menus for CLIENTs. This manages menus for units with the skill level "Client". -- -- ### To manage **command menus**, which are menus that allow the player to issue **functions**, the classes begin with **MENU_COMMAND_**: -- --- * @{Menu#MENU_MISSION_COMMAND}: Manages command menus for whole mission file. --- * @{Menu#MENU_COALITION_COMMAND}: Manages command menus for whole coalition. --- * @{Menu#MENU_GROUP_COMMAND}: Manages command menus for GROUPs. --- * @{Menu#MENU_CLIENT_COMMAND}: Manages command menus for CLIENTs. This manages menus for units with the skill level "Client". +-- * @{Core.Menu#MENU_MISSION_COMMAND}: Manages command menus for whole mission file. +-- * @{Core.Menu#MENU_COALITION_COMMAND}: Manages command menus for whole coalition. +-- * @{Core.Menu#MENU_GROUP_COMMAND}: Manages command menus for GROUPs. +-- * @{Core.Menu#MENU_CLIENT_COMMAND}: Manages command menus for CLIENTs. This manages menus for units with the skill level "Client". -- -- === -- @@ -7288,11 +4878,11 @@ end -- These are simply abstract base classes defining a couple of fields that are used by the -- derived MENU_ classes to manage menus. -- --- 1.1) @{Menu#MENU_BASE} class, extends @{Base#BASE} +-- 1.1) @{Core.Menu#MENU_BASE} class, extends @{Core.Base#BASE} -- -------------------------------------------------- -- The @{#MENU_BASE} class defines the main MENU class where other MENU classes are derived from. -- --- 1.2) @{Menu#MENU_COMMAND_BASE} class, extends @{Base#BASE} +-- 1.2) @{Core.Menu#MENU_COMMAND_BASE} class, extends @{Core.Base#BASE} -- ---------------------------------------------------------- -- The @{#MENU_COMMAND_BASE} class defines the main MENU class where other MENU COMMAND_ classes are derived from, in order to set commands. -- @@ -7304,15 +4894,15 @@ end -- ====================== -- The underlying classes manage the menus for a complete mission file. -- --- 2.1) @{Menu#MENU_MISSION} class, extends @{Menu#MENU_BASE} +-- 2.1) @{Menu#MENU_MISSION} class, extends @{Core.Menu#MENU_BASE} -- --------------------------------------------------------- --- The @{Menu#MENU_MISSION} class manages the main menus for a complete mission. +-- The @{Core.Menu#MENU_MISSION} class manages the main menus for a complete mission. -- You can add menus with the @{#MENU_MISSION.New} method, which constructs a MENU_MISSION object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_MISSION.Remove}. -- --- 2.2) @{Menu#MENU_MISSION_COMMAND} class, extends @{Menu#MENU_COMMAND_BASE} +-- 2.2) @{Menu#MENU_MISSION_COMMAND} class, extends @{Core.Menu#MENU_COMMAND_BASE} -- ------------------------------------------------------------------------- --- The @{Menu#MENU_MISSION_COMMAND} class manages the command menus for a complete mission, which allow players to execute functions during mission execution. +-- The @{Core.Menu#MENU_MISSION_COMMAND} class manages the command menus for a complete mission, which allow players to execute functions during mission execution. -- You can add menus with the @{#MENU_MISSION_COMMAND.New} method, which constructs a MENU_MISSION_COMMAND object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_MISSION_COMMAND.Remove}. -- @@ -7322,15 +4912,15 @@ end -- ========================= -- The underlying classes manage the menus for whole coalitions. -- --- 3.1) @{Menu#MENU_COALITION} class, extends @{Menu#MENU_BASE} +-- 3.1) @{Menu#MENU_COALITION} class, extends @{Core.Menu#MENU_BASE} -- ------------------------------------------------------------ --- The @{Menu#MENU_COALITION} class manages the main menus for coalitions. +-- The @{Core.Menu#MENU_COALITION} class manages the main menus for coalitions. -- You can add menus with the @{#MENU_COALITION.New} method, which constructs a MENU_COALITION object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_COALITION.Remove}. -- --- 3.2) @{Menu#MENU_COALITION_COMMAND} class, extends @{Menu#MENU_COMMAND_BASE} +-- 3.2) @{Menu#MENU_COALITION_COMMAND} class, extends @{Core.Menu#MENU_COMMAND_BASE} -- ---------------------------------------------------------------------------- --- The @{Menu#MENU_COALITION_COMMAND} class manages the command menus for coalitions, which allow players to execute functions during mission execution. +-- The @{Core.Menu#MENU_COALITION_COMMAND} class manages the command menus for coalitions, which allow players to execute functions during mission execution. -- You can add menus with the @{#MENU_COALITION_COMMAND.New} method, which constructs a MENU_COALITION_COMMAND object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_COALITION_COMMAND.Remove}. -- @@ -7340,15 +4930,15 @@ end -- ===================== -- The underlying classes manage the menus for groups. Note that groups can be inactive, alive or can be destroyed. -- --- 4.1) @{Menu#MENU_GROUP} class, extends @{Menu#MENU_BASE} +-- 4.1) @{Menu#MENU_GROUP} class, extends @{Core.Menu#MENU_BASE} -- -------------------------------------------------------- --- The @{Menu#MENU_GROUP} class manages the main menus for coalitions. +-- The @{Core.Menu#MENU_GROUP} class manages the main menus for coalitions. -- You can add menus with the @{#MENU_GROUP.New} method, which constructs a MENU_GROUP object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_GROUP.Remove}. -- --- 4.2) @{Menu#MENU_GROUP_COMMAND} class, extends @{Menu#MENU_COMMAND_BASE} +-- 4.2) @{Menu#MENU_GROUP_COMMAND} class, extends @{Core.Menu#MENU_COMMAND_BASE} -- ------------------------------------------------------------------------ --- The @{Menu#MENU_GROUP_COMMAND} class manages the command menus for coalitions, which allow players to execute functions during mission execution. +-- The @{Core.Menu#MENU_GROUP_COMMAND} class manages the command menus for coalitions, which allow players to execute functions during mission execution. -- You can add menus with the @{#MENU_GROUP_COMMAND.New} method, which constructs a MENU_GROUP_COMMAND object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_GROUP_COMMAND.Remove}. -- @@ -7358,15 +4948,15 @@ end -- ====================== -- The underlying classes manage the menus for units with skill level client or player. -- --- 5.1) @{Menu#MENU_CLIENT} class, extends @{Menu#MENU_BASE} +-- 5.1) @{Menu#MENU_CLIENT} class, extends @{Core.Menu#MENU_BASE} -- --------------------------------------------------------- --- The @{Menu#MENU_CLIENT} class manages the main menus for coalitions. +-- The @{Core.Menu#MENU_CLIENT} class manages the main menus for coalitions. -- You can add menus with the @{#MENU_CLIENT.New} method, which constructs a MENU_CLIENT object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_CLIENT.Remove}. -- --- 5.2) @{Menu#MENU_CLIENT_COMMAND} class, extends @{Menu#MENU_COMMAND_BASE} +-- 5.2) @{Menu#MENU_CLIENT_COMMAND} class, extends @{Core.Menu#MENU_COMMAND_BASE} -- ------------------------------------------------------------------------- --- The @{Menu#MENU_CLIENT_COMMAND} class manages the command menus for coalitions, which allow players to execute functions during mission execution. +-- The @{Core.Menu#MENU_CLIENT_COMMAND} class manages the command menus for coalitions, which allow players to execute functions during mission execution. -- You can add menus with the @{#MENU_CLIENT_COMMAND.New} method, which constructs a MENU_CLIENT_COMMAND object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_CLIENT_COMMAND.Remove}. -- @@ -7602,7 +5192,7 @@ do -- MENU_COALITION --- MENU_COALITION constructor. Creates a new MENU_COALITION object and creates the menu for a complete coalition. -- @param #MENU_COALITION self - -- @param DCSCoalition#coalition.side Coalition The coalition owning the menu. + -- @param Dcs.DCSCoalition#coalition.side Coalition The coalition owning the menu. -- @param #string MenuText The text for the menu. -- @param #table ParentMenu The parent menu. This parameter can be ignored if you want the menu to be located at the perent menu of DCS world (under F10 other). -- @return #MENU_COALITION self @@ -7671,7 +5261,7 @@ do -- MENU_COALITION_COMMAND --- MENU_COALITION constructor. Creates a new radio command item for a coalition, which can invoke a function with parameters. -- @param #MENU_COALITION_COMMAND self - -- @param DCSCoalition#coalition.side Coalition The coalition owning the menu. + -- @param Dcs.DCSCoalition#coalition.side Coalition The coalition owning the menu. -- @param #string MenuText The text for the menu. -- @param Menu#MENU_COALITION ParentMenu The parent menu. -- @param CommandMenuFunction A function that is called when the menu key is pressed. @@ -7744,7 +5334,7 @@ do -- MENU_CLIENT -- MenuStatus[MenuClientName]:Remove() -- end -- - -- --- @param Client#CLIENT MenuClient + -- --- @param Wrapper.Client#CLIENT MenuClient -- local function AddStatusMenu( MenuClient ) -- local MenuClientName = MenuClient:GetName() -- -- This would create a menu for the red coalition under the MenuCoalitionRed menu object. @@ -7777,7 +5367,7 @@ do -- MENU_CLIENT --- MENU_CLIENT constructor. Creates a new radio menu item for a client. -- @param #MENU_CLIENT self - -- @param Client#CLIENT Client The Client owning the menu. + -- @param Wrapper.Client#CLIENT Client The Client owning the menu. -- @param #string MenuText The text for the menu. -- @param #table ParentMenu The parent menu. -- @return #MENU_CLIENT self @@ -7869,7 +5459,7 @@ do -- MENU_CLIENT --- MENU_CLIENT_COMMAND constructor. Creates a new radio command item for a client, which can invoke a function with parameters. -- @param #MENU_CLIENT_COMMAND self - -- @param Client#CLIENT Client The Client owning the menu. + -- @param Wrapper.Client#CLIENT Client The Client owning the menu. -- @param #string MenuText The text for the menu. -- @param #MENU_BASE ParentMenu The parent menu. -- @param CommandMenuFunction A function that is called when the menu key is pressed. @@ -7970,7 +5560,7 @@ do -- MenuStatus[MenuGroupName]:Remove() -- end -- - -- --- @param Group#GROUP MenuGroup + -- --- @param Wrapper.Group#GROUP MenuGroup -- local function AddStatusMenu( MenuGroup ) -- local MenuGroupName = MenuGroup:GetName() -- -- This would create a menu for the red coalition under the MenuCoalitionRed menu object. @@ -8004,51 +5594,41 @@ do --- MENU_GROUP constructor. Creates a new radio menu item for a group. -- @param #MENU_GROUP self - -- @param Group#GROUP MenuGroup The Group owning the menu. + -- @param Wrapper.Group#GROUP MenuGroup The Group owning the menu. -- @param #string MenuText The text for the menu. -- @param #table ParentMenu The parent menu. -- @return #MENU_GROUP self function MENU_GROUP:New( MenuGroup, MenuText, ParentMenu ) - -- Arrange meta tables - local MenuParentPath = {} - if ParentMenu ~= nil then - MenuParentPath = ParentMenu.MenuPath - end - - local self = BASE:Inherit( self, MENU_BASE:New( MenuText, MenuParentPath ) ) - self:F( { MenuGroup, MenuText, ParentMenu } ) - - self.MenuGroup = MenuGroup - self.MenuGroupID = MenuGroup:GetID() - self.MenuParentPath = MenuParentPath - self.MenuText = MenuText - self.ParentMenu = ParentMenu + -- Determine if the menu was not already created and already visible at the group. + -- If it is visible, then return the cached self, otherwise, create self and cache it. - self.Menus = {} - - if not _MENUGROUPS[self.MenuGroupID] then - _MENUGROUPS[self.MenuGroupID] = {} + MenuGroup._Menus = MenuGroup._Menus or {} + local Path = ( ParentMenu and ( table.concat( ParentMenu.MenuPath or {}, "@" ) .. "@" .. MenuText ) ) or MenuText + if MenuGroup._Menus[Path] then + self = MenuGroup._Menus[Path] + else + self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) ) + MenuGroup._Menus[Path] = self + + self.Menus = {} + + self.MenuGroup = MenuGroup + self.Path = Path + self.MenuGroupID = MenuGroup:GetID() + self.MenuText = MenuText + self.ParentMenu = ParentMenu + + self:T( { "Adding Menu ", MenuText, self.MenuParentPath } ) + self.MenuPath = missionCommands.addSubMenuForGroup( self.MenuGroupID, MenuText, self.MenuParentPath ) + + if ParentMenu and ParentMenu.Menus then + ParentMenu.Menus[self.MenuPath] = self + end end - local MenuPath = _MENUGROUPS[self.MenuGroupID] - - self:T( { MenuGroup:GetName(), MenuPath[table.concat(MenuParentPath)], MenuParentPath, MenuText } ) - - local MenuPathID = table.concat(MenuParentPath) .. "/" .. MenuText - if MenuPath[MenuPathID] then - missionCommands.removeItemForGroup( self.MenuGroupID, MenuPath[MenuPathID] ) - end - - self:T( { "Adding for MenuPath ", MenuText, MenuParentPath } ) - self.MenuPath = missionCommands.addSubMenuForGroup( self.MenuGroupID, MenuText, MenuParentPath ) - MenuPath[MenuPathID] = self.MenuPath - - self:T( { self.MenuGroupID, self.MenuPath } ) - - if ParentMenu and ParentMenu.Menus then - ParentMenu.Menus[self.MenuPath] = self - end + self:F( { MenuGroup:GetName(), MenuText, ParentMenu.MenuPath } ) + return self end @@ -8068,23 +5648,20 @@ do -- @param #MENU_GROUP self -- @return #nil function MENU_GROUP:Remove() - self:F( self.MenuPath ) + self:F( { self.MenuGroupID, self.MenuPath } ) self:RemoveSubMenus() - if not _MENUGROUPS[self.MenuGroupID] then - _MENUGROUPS[self.MenuGroupID] = {} - end + if self.MenuGroup._Menus[self.Path] then + self = self.MenuGroup._Menus[self.Path] - local MenuPath = _MENUGROUPS[self.MenuGroupID] - - if MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] then - MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] = nil - end - - missionCommands.removeItemForGroup( self.MenuGroupID, self.MenuPath ) - if self.ParentMenu then - self.ParentMenu.Menus[self.MenuPath] = nil + missionCommands.removeItemForGroup( self.MenuGroupID, self.MenuPath ) + if self.ParentMenu then + self.ParentMenu.Menus[self.MenuPath] = nil + end + self:E( self.MenuGroup._Menus[self.Path] ) + self.MenuGroup._Menus[self.Path] = nil + self = nil end return nil end @@ -8099,7 +5676,7 @@ do --- Creates a new radio command item for a group -- @param #MENU_GROUP_COMMAND self - -- @param Group#GROUP MenuGroup The Group owning the menu. + -- @param Wrapper.Group#GROUP MenuGroup The Group owning the menu. -- @param MenuText The text for the menu. -- @param ParentMenu The parent menu. -- @param CommandMenuFunction A function that is called when the menu key is pressed. @@ -8107,32 +5684,30 @@ do -- @return Menu#MENU_GROUP_COMMAND self function MENU_GROUP_COMMAND:New( MenuGroup, MenuText, ParentMenu, CommandMenuFunction, ... ) - local self = BASE:Inherit( self, MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, arg ) ) - - self.MenuGroup = MenuGroup - self.MenuGroupID = MenuGroup:GetID() - self.MenuText = MenuText - self.ParentMenu = ParentMenu - - if not _MENUGROUPS[self.MenuGroupID] then - _MENUGROUPS[self.MenuGroupID] = {} + MenuGroup._Menus = MenuGroup._Menus or {} + local Path = ( ParentMenu and ( table.concat( ParentMenu.MenuPath or {}, "@" ) .. "@" .. MenuText ) ) or MenuText + if MenuGroup._Menus[Path] then + self = MenuGroup._Menus[Path] + else + self = BASE:Inherit( self, MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, arg ) ) + MenuGroup._Menus[Path] = self + + self.Path = Path + self.MenuGroup = MenuGroup + self.MenuGroupID = MenuGroup:GetID() + self.MenuText = MenuText + self.ParentMenu = ParentMenu + + self:T( { "Adding Command Menu ", MenuText, self.MenuParentPath } ) + self.MenuPath = missionCommands.addCommandForGroup( self.MenuGroupID, MenuText, self.MenuParentPath, self.MenuCallHandler, arg ) + + if ParentMenu and ParentMenu.Menus then + ParentMenu.Menus[self.MenuPath] = self + end end - - local MenuPath = _MENUGROUPS[self.MenuGroupID] - - self:T( { MenuGroup:GetName(), MenuPath[table.concat(self.MenuParentPath)], self.MenuParentPath, MenuText, CommandMenuFunction, arg } ) - - local MenuPathID = table.concat(self.MenuParentPath) .. "/" .. MenuText - if MenuPath[MenuPathID] then - missionCommands.removeItemForGroup( self.MenuGroupID, MenuPath[MenuPathID] ) - end - - self:T( { "Adding for MenuPath ", MenuText, self.MenuParentPath } ) - self.MenuPath = missionCommands.addCommandForGroup( self.MenuGroupID, MenuText, self.MenuParentPath, self.MenuCallHandler, arg ) - MenuPath[MenuPathID] = self.MenuPath - - ParentMenu.Menus[self.MenuPath] = self - + + self:F( { MenuGroup:GetName(), MenuText, ParentMenu.MenuPath } ) + return self end @@ -8140,1693 +5715,24 @@ do -- @param #MENU_GROUP_COMMAND self -- @return #nil function MENU_GROUP_COMMAND:Remove() - self:F( self.MenuPath ) - - if not _MENUGROUPS[self.MenuGroupID] then - _MENUGROUPS[self.MenuGroupID] = {} - end - - local MenuPath = _MENUGROUPS[self.MenuGroupID] + self:F( { self.MenuGroupID, self.MenuPath } ) + if self.MenuGroup._Menus[self.Path] then + self = self.MenuGroup._Menus[self.Path] - if MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] then - MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] = nil + missionCommands.removeItemForGroup( self.MenuGroupID, self.MenuPath ) + self.ParentMenu.Menus[self.MenuPath] = nil + self:E( self.MenuGroup._Menus[self.Path] ) + self.MenuGroup._Menus[self.Path] = nil + self = nil end - missionCommands.removeItemForGroup( self.MenuGroupID, self.MenuPath ) - self.ParentMenu.Menus[self.MenuPath] = nil return nil end end ---- This module contains the GROUP class. --- --- 1) @{Group#GROUP} class, extends @{Controllable#CONTROLLABLE} --- ============================================================= --- The @{Group#GROUP} class is a wrapper class to handle the DCS Group objects: --- --- * Support all DCS Group APIs. --- * Enhance with Group specific APIs not in the DCS Group API set. --- * Handle local Group Controller. --- * Manage the "state" of the DCS Group. --- --- **IMPORTANT: ONE SHOULD NEVER SANATIZE these GROUP OBJECT REFERENCES! (make the GROUP object references nil).** --- --- 1.1) GROUP reference methods --- ----------------------- --- For each DCS Group object alive within a running mission, a GROUP wrapper object (instance) will be created within the _@{DATABASE} object. --- This is done at the beginning of the mission (when the mission starts), and dynamically when new DCS Group objects are spawned (using the @{SPAWN} class). --- --- The GROUP class does not contain a :New() method, rather it provides :Find() methods to retrieve the object reference --- using the DCS Group or the DCS GroupName. --- --- Another thing to know is that GROUP objects do not "contain" the DCS Group object. --- The GROUP methods will reference the DCS Group object by name when it is needed during API execution. --- If the DCS Group object does not exist or is nil, the GROUP methods will return nil and log an exception in the DCS.log file. --- --- The GROUP class provides the following functions to retrieve quickly the relevant GROUP instance: --- --- * @{#GROUP.Find}(): Find a GROUP instance from the _DATABASE object using a DCS Group object. --- * @{#GROUP.FindByName}(): Find a GROUP instance from the _DATABASE object using a DCS Group name. --- --- 1.2) GROUP task methods --- ----------------------- --- Several group task methods are available that help you to prepare tasks. --- These methods return a string consisting of the task description, which can then be given to either a --- @{Controllable#CONTROLLABLE.PushTask} or @{Controllable#CONTROLLABLE.SetTask} method to assign the task to the GROUP. --- Tasks are specific for the category of the GROUP, more specific, for AIR, GROUND or AIR and GROUND. --- Each task description where applicable indicates for which group category the task is valid. --- There are 2 main subdivisions of tasks: Assigned tasks and EnRoute tasks. --- --- ### 1.2.1) Assigned task methods --- --- Assigned task methods make the group execute the task where the location of the (possible) targets of the task are known before being detected. --- This is different from the EnRoute tasks, where the targets of the task need to be detected before the task can be executed. --- --- Find below a list of the **assigned task** methods: --- --- * @{Controllable#CONTROLLABLE.TaskAttackGroup}: (AIR) Attack a Group. --- * @{Controllable#CONTROLLABLE.TaskAttackMapObject}: (AIR) Attacking the map object (building, structure, e.t.c). --- * @{Controllable#CONTROLLABLE.TaskAttackUnit}: (AIR) Attack the Unit. --- * @{Controllable#CONTROLLABLE.TaskBombing}: (Controllable#CONTROLLABLEDelivering weapon at the point on the ground. --- * @{Controllable#CONTROLLABLE.TaskBombingRunway}: (AIR) Delivering weapon on the runway. --- * @{Controllable#CONTROLLABLE.TaskEmbarking}: (AIR) Move the group to a Vec2 Point, wait for a defined duration and embark a group. --- * @{Controllable#CONTROLLABLE.TaskEmbarkToTransport}: (GROUND) Embark to a Transport landed at a location. --- * @{Controllable#CONTROLLABLE.TaskEscort}: (AIR) Escort another airborne group. --- * @{Controllable#CONTROLLABLE.TaskFAC_AttackGroup}: (AIR + GROUND) The task makes the group/unit a FAC and orders the FAC to control the target (enemy ground group) destruction. --- * @{Controllable#CONTROLLABLE.TaskFireAtPoint}: (GROUND) Fire at a VEC2 point until ammunition is finished. --- * @{Controllable#CONTROLLABLE.TaskFollow}: (AIR) Following another airborne group. --- * @{Controllable#CONTROLLABLE.TaskHold}: (GROUND) Hold ground group from moving. --- * @{Controllable#CONTROLLABLE.TaskHoldPosition}: (AIR) Hold position at the current position of the first unit of the group. --- * @{Controllable#CONTROLLABLE.TaskLand}: (AIR HELICOPTER) Landing at the ground. For helicopters only. --- * @{Controllable#CONTROLLABLE.TaskLandAtZone}: (AIR) Land the group at a @{Zone#ZONE_RADIUS). --- * @{Controllable#CONTROLLABLE.TaskOrbitCircle}: (AIR) Orbit at the current position of the first unit of the group at a specified alititude. --- * @{Controllable#CONTROLLABLE.TaskOrbitCircleAtVec2}: (AIR) Orbit at a specified position at a specified alititude during a specified duration with a specified speed. --- * @{Controllable#CONTROLLABLE.TaskRefueling}: (AIR) Refueling from the nearest tanker. No parameters. --- * @{Controllable#CONTROLLABLE.TaskRoute}: (AIR + GROUND) Return a Misson task to follow a given route defined by Points. --- * @{Controllable#CONTROLLABLE.TaskRouteToVec2}: (AIR + GROUND) Make the Group move to a given point. --- * @{Controllable#CONTROLLABLE.TaskRouteToVec3}: (AIR + GROUND) Make the Group move to a given point. --- * @{Controllable#CONTROLLABLE.TaskRouteToZone}: (AIR + GROUND) Route the group to a given zone. --- * @{Controllable#CONTROLLABLE.TaskReturnToBase}: (AIR) Route the group to an airbase. --- --- ### 1.2.2) EnRoute task methods --- --- EnRoute tasks require the targets of the task need to be detected by the group (using its sensors) before the task can be executed: --- --- * @{Controllable#CONTROLLABLE.EnRouteTaskAWACS}: (AIR) Aircraft will act as an AWACS for friendly units (will provide them with information about contacts). No parameters. --- * @{Controllable#CONTROLLABLE.EnRouteTaskEngageGroup}: (AIR) Engaging a group. The task does not assign the target group to the unit/group to attack now; it just allows the unit/group to engage the target group as well as other assigned targets. --- * @{Controllable#CONTROLLABLE.EnRouteTaskEngageTargets}: (AIR) Engaging targets of defined types. --- * @{Controllable#CONTROLLABLE.EnRouteTaskEWR}: (AIR) Attack the Unit. --- * @{Controllable#CONTROLLABLE.EnRouteTaskFAC}: (AIR + GROUND) The task makes the group/unit a FAC and lets the FAC to choose a targets (enemy ground group) around as well as other assigned targets. --- * @{Controllable#CONTROLLABLE.EnRouteTaskFAC_EngageGroup}: (AIR + GROUND) The task makes the group/unit a FAC and lets the FAC to choose the target (enemy ground group) as well as other assigned targets. --- * @{Controllable#CONTROLLABLE.EnRouteTaskTanker}: (AIR) Aircraft will act as a tanker for friendly units. No parameters. --- --- ### 1.2.3) Preparation task methods --- --- There are certain task methods that allow to tailor the task behaviour: --- --- * @{Controllable#CONTROLLABLE.TaskWrappedAction}: Return a WrappedAction Task taking a Command. --- * @{Controllable#CONTROLLABLE.TaskCombo}: Return a Combo Task taking an array of Tasks. --- * @{Controllable#CONTROLLABLE.TaskCondition}: Return a condition section for a controlled task. --- * @{Controllable#CONTROLLABLE.TaskControlled}: Return a Controlled Task taking a Task and a TaskCondition. --- --- ### 1.2.4) Obtain the mission from group templates --- --- Group templates contain complete mission descriptions. Sometimes you want to copy a complete mission from a group and assign it to another: --- --- * @{Controllable#CONTROLLABLE.TaskMission}: (AIR + GROUND) Return a mission task from a mission template. --- --- 1.3) GROUP Command methods --- -------------------------- --- Group **command methods** prepare the execution of commands using the @{Controllable#CONTROLLABLE.SetCommand} method: --- --- * @{Controllable#CONTROLLABLE.CommandDoScript}: Do Script command. --- * @{Controllable#CONTROLLABLE.CommandSwitchWayPoint}: Perform a switch waypoint command. --- --- 1.4) GROUP Option methods --- ------------------------- --- Group **Option methods** change the behaviour of the Group while being alive. --- --- ### 1.4.1) Rule of Engagement: --- --- * @{Controllable#CONTROLLABLE.OptionROEWeaponFree} --- * @{Controllable#CONTROLLABLE.OptionROEOpenFire} --- * @{Controllable#CONTROLLABLE.OptionROEReturnFire} --- * @{Controllable#CONTROLLABLE.OptionROEEvadeFire} --- --- To check whether an ROE option is valid for a specific group, use: --- --- * @{Controllable#CONTROLLABLE.OptionROEWeaponFreePossible} --- * @{Controllable#CONTROLLABLE.OptionROEOpenFirePossible} --- * @{Controllable#CONTROLLABLE.OptionROEReturnFirePossible} --- * @{Controllable#CONTROLLABLE.OptionROEEvadeFirePossible} --- --- ### 1.4.2) Rule on thread: --- --- * @{Controllable#CONTROLLABLE.OptionROTNoReaction} --- * @{Controllable#CONTROLLABLE.OptionROTPassiveDefense} --- * @{Controllable#CONTROLLABLE.OptionROTEvadeFire} --- * @{Controllable#CONTROLLABLE.OptionROTVertical} --- --- To test whether an ROT option is valid for a specific group, use: --- --- * @{Controllable#CONTROLLABLE.OptionROTNoReactionPossible} --- * @{Controllable#CONTROLLABLE.OptionROTPassiveDefensePossible} --- * @{Controllable#CONTROLLABLE.OptionROTEvadeFirePossible} --- * @{Controllable#CONTROLLABLE.OptionROTVerticalPossible} --- --- 1.5) GROUP Zone validation methods --- ---------------------------------- --- The group can be validated whether it is completely, partly or not within a @{Zone}. --- Use the following Zone validation methods on the group: --- --- * @{#GROUP.IsCompletelyInZone}: Returns true if all units of the group are within a @{Zone}. --- * @{#GROUP.IsPartlyInZone}: Returns true if some units of the group are within a @{Zone}. --- * @{#GROUP.IsNotInZone}: Returns true if none of the group units of the group are within a @{Zone}. --- --- The zone can be of any @{Zone} class derived from @{Zone#ZONE_BASE}. So, these methods are polymorphic to the zones tested on. --- --- @module Group --- @author FlightControl - ---- The GROUP class --- @type GROUP --- @extends Controllable#CONTROLLABLE --- @field #string GroupName The name of the group. -GROUP = { - ClassName = "GROUP", -} - ---- Create a new GROUP from a DCSGroup --- @param #GROUP self --- @param DCSGroup#Group GroupName The DCS Group name --- @return #GROUP self -function GROUP:Register( GroupName ) - local self = BASE:Inherit( self, CONTROLLABLE:New( GroupName ) ) - self:F2( GroupName ) - self.GroupName = GroupName - return self -end - --- Reference methods. - ---- Find the GROUP wrapper class instance using the DCS Group. --- @param #GROUP self --- @param DCSGroup#Group DCSGroup The DCS Group. --- @return #GROUP The GROUP. -function GROUP:Find( DCSGroup ) - - local GroupName = DCSGroup:getName() -- Group#GROUP - local GroupFound = _DATABASE:FindGroup( GroupName ) - return GroupFound -end - ---- Find the created GROUP using the DCS Group Name. --- @param #GROUP self --- @param #string GroupName The DCS Group Name. --- @return #GROUP The GROUP. -function GROUP:FindByName( GroupName ) - - local GroupFound = _DATABASE:FindGroup( GroupName ) - return GroupFound -end - --- DCS Group methods support. - ---- Returns the DCS Group. --- @param #GROUP self --- @return DCSGroup#Group The DCS Group. -function GROUP:GetDCSObject() - local DCSGroup = Group.getByName( self.GroupName ) - - if DCSGroup then - return DCSGroup - end - - return nil -end - - ---- Returns if the DCS Group is alive. --- When the group exists at run-time, this method will return true, otherwise false. --- @param #GROUP self --- @return #boolean true if the DCS Group is alive. -function GROUP:IsAlive() - self:F2( self.GroupName ) - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local GroupIsAlive = DCSGroup:isExist() - self:T3( GroupIsAlive ) - return GroupIsAlive - end - - return nil -end - ---- Destroys the DCS Group and all of its DCS Units. --- Note that this destroy method also raises a destroy event at run-time. --- So all event listeners will catch the destroy event of this DCS Group. --- @param #GROUP self -function GROUP:Destroy() - self:F2( self.GroupName ) - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - for Index, UnitData in pairs( DCSGroup:getUnits() ) do - self:CreateEventCrash( timer.getTime(), UnitData ) - end - DCSGroup:destroy() - DCSGroup = nil - end - - return nil -end - ---- Returns category of the DCS Group. --- @param #GROUP self --- @return DCSGroup#Group.Category The category ID -function GROUP:GetCategory() - self:F2( self.GroupName ) - - local DCSGroup = self:GetDCSObject() - if DCSGroup then - local GroupCategory = DCSGroup:getCategory() - self:T3( GroupCategory ) - return GroupCategory - end - - return nil -end - ---- Returns the category name of the DCS Group. --- @param #GROUP self --- @return #string Category name = Helicopter, Airplane, Ground Unit, Ship -function GROUP:GetCategoryName() - self:F2( self.GroupName ) - - local DCSGroup = self:GetDCSObject() - if DCSGroup then - local CategoryNames = { - [Group.Category.AIRPLANE] = "Airplane", - [Group.Category.HELICOPTER] = "Helicopter", - [Group.Category.GROUND] = "Ground Unit", - [Group.Category.SHIP] = "Ship", - } - local GroupCategory = DCSGroup:getCategory() - self:T3( GroupCategory ) - - return CategoryNames[GroupCategory] - end - - return nil -end - - ---- Returns the coalition of the DCS Group. --- @param #GROUP self --- @return DCSCoalitionObject#coalition.side The coalition side of the DCS Group. -function GROUP:GetCoalition() - self:F2( self.GroupName ) - - local DCSGroup = self:GetDCSObject() - if DCSGroup then - local GroupCoalition = DCSGroup:getCoalition() - self:T3( GroupCoalition ) - return GroupCoalition - end - - return nil -end - ---- Returns the country of the DCS Group. --- @param #GROUP self --- @return DCScountry#country.id The country identifier. --- @return #nil The DCS Group is not existing or alive. -function GROUP:GetCountry() - self:F2( self.GroupName ) - - local DCSGroup = self:GetDCSObject() - if DCSGroup then - local GroupCountry = DCSGroup:getUnit(1):getCountry() - self:T3( GroupCountry ) - return GroupCountry - end - - return nil -end - ---- Returns the UNIT wrapper class with number UnitNumber. --- If the underlying DCS Unit does not exist, the method will return nil. . --- @param #GROUP self --- @param #number UnitNumber The number of the UNIT wrapper class to be returned. --- @return Unit#UNIT The UNIT wrapper class. -function GROUP:GetUnit( UnitNumber ) - self:F2( { self.GroupName, UnitNumber } ) - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local UnitFound = UNIT:Find( DCSGroup:getUnit( UnitNumber ) ) - self:T3( UnitFound.UnitName ) - self:T2( UnitFound ) - return UnitFound - end - - return nil -end - ---- Returns the DCS Unit with number UnitNumber. --- If the underlying DCS Unit does not exist, the method will return nil. . --- @param #GROUP self --- @param #number UnitNumber The number of the DCS Unit to be returned. --- @return DCSUnit#Unit The DCS Unit. -function GROUP:GetDCSUnit( UnitNumber ) - self:F2( { self.GroupName, UnitNumber } ) - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local DCSUnitFound = DCSGroup:getUnit( UnitNumber ) - self:T3( DCSUnitFound ) - return DCSUnitFound - end - - return nil -end - ---- Returns current size of the DCS Group. --- If some of the DCS Units of the DCS Group are destroyed the size of the DCS Group is changed. --- @param #GROUP self --- @return #number The DCS Group size. -function GROUP:GetSize() - self:F2( { self.GroupName } ) - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local GroupSize = DCSGroup:getSize() - self:T3( GroupSize ) - return GroupSize - end - - return nil -end - ---- ---- Returns the initial size of the DCS Group. --- If some of the DCS Units of the DCS Group are destroyed, the initial size of the DCS Group is unchanged. --- @param #GROUP self --- @return #number The DCS Group initial size. -function GROUP:GetInitialSize() - self:F2( { self.GroupName } ) - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local GroupInitialSize = DCSGroup:getInitialSize() - self:T3( GroupInitialSize ) - return GroupInitialSize - end - - return nil -end - ---- Returns the UNITs wrappers of the DCS Units of the DCS Group. --- @param #GROUP self --- @return #table The UNITs wrappers. -function GROUP:GetUnits() - self:F2( { self.GroupName } ) - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local DCSUnits = DCSGroup:getUnits() - local Units = {} - for Index, UnitData in pairs( DCSUnits ) do - Units[#Units+1] = UNIT:Find( UnitData ) - end - self:T3( Units ) - return Units - end - - return nil -end - - ---- Returns the DCS Units of the DCS Group. --- @param #GROUP self --- @return #table The DCS Units. -function GROUP:GetDCSUnits() - self:F2( { self.GroupName } ) - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local DCSUnits = DCSGroup:getUnits() - self:T3( DCSUnits ) - return DCSUnits - end - - return nil -end - - ---- Activates a GROUP. --- @param #GROUP self -function GROUP:Activate() - self:F2( { self.GroupName } ) - trigger.action.activateGroup( self:GetDCSObject() ) - return self:GetDCSObject() -end - - ---- Gets the type name of the group. --- @param #GROUP self --- @return #string The type name of the group. -function GROUP:GetTypeName() - self:F2( self.GroupName ) - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local GroupTypeName = DCSGroup:getUnit(1):getTypeName() - self:T3( GroupTypeName ) - return( GroupTypeName ) - end - - return nil -end - ---- Gets the CallSign of the first DCS Unit of the DCS Group. --- @param #GROUP self --- @return #string The CallSign of the first DCS Unit of the DCS Group. -function GROUP:GetCallsign() - self:F2( self.GroupName ) - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local GroupCallSign = DCSGroup:getUnit(1):getCallsign() - self:T3( GroupCallSign ) - return GroupCallSign - end - - return nil -end - ---- Returns the current point (Vec2 vector) of the first DCS Unit in the DCS Group. --- @param #GROUP self --- @return DCSTypes#Vec2 Current Vec2 point of the first DCS Unit of the DCS Group. -function GROUP:GetVec2() - self:F2( self.GroupName ) - - local UnitPoint = self:GetUnit(1) - UnitPoint:GetVec2() - local GroupPointVec2 = UnitPoint:GetVec2() - self:T3( GroupPointVec2 ) - return GroupPointVec2 -end - ---- Returns the current Vec3 vector of the first DCS Unit in the GROUP. --- @return DCSTypes#Vec3 Current Vec3 of the first DCS Unit of the GROUP. -function GROUP:GetVec3() - self:F2( self.GroupName ) - - local GroupVec3 = self:GetUnit(1):GetVec3() - self:T3( GroupVec3 ) - return GroupVec3 -end - - - --- Is Zone Functions - ---- Returns true if all units of the group are within a @{Zone}. --- @param #GROUP self --- @param Zone#ZONE_BASE Zone The zone to test. --- @return #boolean Returns true if the Group is completely within the @{Zone#ZONE_BASE} -function GROUP:IsCompletelyInZone( Zone ) - self:F2( { self.GroupName, Zone } ) - - for UnitID, UnitData in pairs( self:GetUnits() ) do - local Unit = UnitData -- Unit#UNIT - -- TODO: Rename IsPointVec3InZone to IsVec3InZone - if Zone:IsPointVec3InZone( Unit:GetVec3() ) then - else - return false - end - end - - return true -end - ---- Returns true if some units of the group are within a @{Zone}. --- @param #GROUP self --- @param Zone#ZONE_BASE Zone The zone to test. --- @return #boolean Returns true if the Group is completely within the @{Zone#ZONE_BASE} -function GROUP:IsPartlyInZone( Zone ) - self:F2( { self.GroupName, Zone } ) - - for UnitID, UnitData in pairs( self:GetUnits() ) do - local Unit = UnitData -- Unit#UNIT - if Zone:IsPointVec3InZone( Unit:GetVec3() ) then - return true - end - end - - return false -end - ---- Returns true if none of the group units of the group are within a @{Zone}. --- @param #GROUP self --- @param Zone#ZONE_BASE Zone The zone to test. --- @return #boolean Returns true if the Group is completely within the @{Zone#ZONE_BASE} -function GROUP:IsNotInZone( Zone ) - self:F2( { self.GroupName, Zone } ) - - for UnitID, UnitData in pairs( self:GetUnits() ) do - local Unit = UnitData -- Unit#UNIT - if Zone:IsPointVec3InZone( Unit:GetVec3() ) then - return false - end - end - - return true -end - ---- Returns if the group is of an air category. --- If the group is a helicopter or a plane, then this method will return true, otherwise false. --- @param #GROUP self --- @return #boolean Air category evaluation result. -function GROUP:IsAir() - self:F2( self.GroupName ) - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local IsAirResult = DCSGroup:getCategory() == Group.Category.AIRPLANE or DCSGroup:getCategory() == Group.Category.HELICOPTER - self:T3( IsAirResult ) - return IsAirResult - end - - return nil -end - ---- Returns if the DCS Group contains Helicopters. --- @param #GROUP self --- @return #boolean true if DCS Group contains Helicopters. -function GROUP:IsHelicopter() - self:F2( self.GroupName ) - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local GroupCategory = DCSGroup:getCategory() - self:T2( GroupCategory ) - return GroupCategory == Group.Category.HELICOPTER - end - - return nil -end - ---- Returns if the DCS Group contains AirPlanes. --- @param #GROUP self --- @return #boolean true if DCS Group contains AirPlanes. -function GROUP:IsAirPlane() - self:F2() - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local GroupCategory = DCSGroup:getCategory() - self:T2( GroupCategory ) - return GroupCategory == Group.Category.AIRPLANE - end - - return nil -end - ---- Returns if the DCS Group contains Ground troops. --- @param #GROUP self --- @return #boolean true if DCS Group contains Ground troops. -function GROUP:IsGround() - self:F2() - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local GroupCategory = DCSGroup:getCategory() - self:T2( GroupCategory ) - return GroupCategory == Group.Category.GROUND - end - - return nil -end - ---- Returns if the DCS Group contains Ships. --- @param #GROUP self --- @return #boolean true if DCS Group contains Ships. -function GROUP:IsShip() - self:F2() - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local GroupCategory = DCSGroup:getCategory() - self:T2( GroupCategory ) - return GroupCategory == Group.Category.SHIP - end - - return nil -end - ---- Returns if all units of the group are on the ground or landed. --- If all units of this group are on the ground, this function will return true, otherwise false. --- @param #GROUP self --- @return #boolean All units on the ground result. -function GROUP:AllOnGround() - self:F2() - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local AllOnGroundResult = true - - for Index, UnitData in pairs( DCSGroup:getUnits() ) do - if UnitData:inAir() then - AllOnGroundResult = false - end - end - - self:T3( AllOnGroundResult ) - return AllOnGroundResult - end - - return nil -end - ---- Returns the current maximum velocity of the group. --- Each unit within the group gets evaluated, and the maximum velocity (= the unit which is going the fastest) is returned. --- @param #GROUP self --- @return #number Maximum velocity found. -function GROUP:GetMaxVelocity() - self:F2() - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local MaxVelocity = 0 - - for Index, UnitData in pairs( DCSGroup:getUnits() ) do - - local Velocity = UnitData:getVelocity() - local VelocityTotal = math.abs( Velocity.x ) + math.abs( Velocity.y ) + math.abs( Velocity.z ) - - if VelocityTotal < MaxVelocity then - MaxVelocity = VelocityTotal - end - end - - return MaxVelocity - end - - return nil -end - ---- Returns the current minimum height of the group. --- Each unit within the group gets evaluated, and the minimum height (= the unit which is the lowest elevated) is returned. --- @param #GROUP self --- @return #number Minimum height found. -function GROUP:GetMinHeight() - self:F2() - -end - ---- Returns the current maximum height of the group. --- Each unit within the group gets evaluated, and the maximum height (= the unit which is the highest elevated) is returned. --- @param #GROUP self --- @return #number Maximum height found. -function GROUP:GetMaxHeight() - self:F2() - -end - --- SPAWNING - ---- Respawn the @{GROUP} using a (tweaked) template of the Group. --- The template must be retrieved with the @{Group#GROUP.GetTemplate}() function. --- The template contains all the definitions as declared within the mission file. --- To understand templates, do the following: --- --- * unpack your .miz file into a directory using 7-zip. --- * browse in the directory created to the file **mission**. --- * open the file and search for the country group definitions. --- --- Your group template will contain the fields as described within the mission file. --- --- This function will: --- --- * Get the current position and heading of the group. --- * When the group is alive, it will tweak the template x, y and heading coordinates of the group and the embedded units to the current units positions. --- * Then it will destroy the current alive group. --- * And it will respawn the group using your new template definition. --- @param Group#GROUP self --- @param #table Template The template of the Group retrieved with GROUP:GetTemplate() -function GROUP:Respawn( Template ) - - local Vec3 = self:GetVec3() - Template.x = Vec3.x - Template.y = Vec3.z - --Template.x = nil - --Template.y = nil - - self:E( #Template.units ) - for UnitID, UnitData in pairs( self:GetUnits() ) do - local GroupUnit = UnitData -- Unit#UNIT - self:E( GroupUnit:GetName() ) - if GroupUnit:IsAlive() then - local GroupUnitVec3 = GroupUnit:GetVec3() - local GroupUnitHeading = GroupUnit:GetHeading() - Template.units[UnitID].alt = GroupUnitVec3.y - Template.units[UnitID].x = GroupUnitVec3.x - Template.units[UnitID].y = GroupUnitVec3.z - Template.units[UnitID].heading = GroupUnitHeading - self:E( { UnitID, Template.units[UnitID], Template.units[UnitID] } ) - end - end - - self:Destroy() - _DATABASE:Spawn( Template ) -end - ---- Returns the group template from the @{DATABASE} (_DATABASE object). --- @param #GROUP self --- @return #table -function GROUP:GetTemplate() - local GroupName = self:GetName() - self:E( GroupName ) - return _DATABASE:GetGroupTemplate( GroupName ) -end - ---- Sets the controlled status in a Template. --- @param #GROUP self --- @param #boolean Controlled true is controlled, false is uncontrolled. --- @return #table -function GROUP:SetTemplateControlled( Template, Controlled ) - Template.uncontrolled = not Controlled - return Template -end - ---- Sets the CountryID of the group in a Template. --- @param #GROUP self --- @param DCScountry#country.id CountryID The country ID. --- @return #table -function GROUP:SetTemplateCountry( Template, CountryID ) - Template.CountryID = CountryID - return Template -end - ---- Sets the CoalitionID of the group in a Template. --- @param #GROUP self --- @param DCSCoalitionObject#coalition.side CoalitionID The coalition ID. --- @return #table -function GROUP:SetTemplateCoalition( Template, CoalitionID ) - Template.CoalitionID = CoalitionID - return Template -end - - - - ---- Return the mission template of the group. --- @param #GROUP self --- @return #table The MissionTemplate -function GROUP:GetTaskMission() - self:F2( self.GroupName ) - - return routines.utils.deepCopy( _DATABASE.Templates.Groups[self.GroupName].Template ) -end - ---- Return the mission route of the group. --- @param #GROUP self --- @return #table The mission route defined by points. -function GROUP:GetTaskRoute() - self:F2( self.GroupName ) - - return routines.utils.deepCopy( _DATABASE.Templates.Groups[self.GroupName].Template.route.points ) -end - ---- Return the route of a group by using the @{Database#DATABASE} class. --- @param #GROUP self --- @param #number Begin The route point from where the copy will start. The base route point is 0. --- @param #number End The route point where the copy will end. The End point is the last point - the End point. The last point has base 0. --- @param #boolean Randomize Randomization of the route, when true. --- @param #number Radius When randomization is on, the randomization is within the radius. -function GROUP:CopyRoute( Begin, End, Randomize, Radius ) - self:F2( { Begin, End } ) - - local Points = {} - - -- Could be a Spawned Group - local GroupName = string.match( self:GetName(), ".*#" ) - if GroupName then - GroupName = GroupName:sub( 1, -2 ) - else - GroupName = self:GetName() - end - - self:T3( { GroupName } ) - - local Template = _DATABASE.Templates.Groups[GroupName].Template - - if Template then - if not Begin then - Begin = 0 - end - if not End then - End = 0 - end - - for TPointID = Begin + 1, #Template.route.points - End do - if Template.route.points[TPointID] then - Points[#Points+1] = routines.utils.deepCopy( Template.route.points[TPointID] ) - if Randomize then - if not Radius then - Radius = 500 - end - Points[#Points].x = Points[#Points].x + math.random( Radius * -1, Radius ) - Points[#Points].y = Points[#Points].y + math.random( Radius * -1, Radius ) - end - end - end - return Points - else - error( "Template not found for Group : " .. GroupName ) - end - - return nil -end - - ---- This module contains the UNIT class. --- --- 1) @{Unit#UNIT} class, extends @{Controllable#CONTROLLABLE} --- =========================================================== --- The @{Unit#UNIT} class is a wrapper class to handle the DCS Unit objects: --- --- * Support all DCS Unit APIs. --- * Enhance with Unit specific APIs not in the DCS Unit API set. --- * Handle local Unit Controller. --- * Manage the "state" of the DCS Unit. --- --- --- 1.1) UNIT reference methods --- ---------------------- --- For each DCS Unit object alive within a running mission, a UNIT wrapper object (instance) will be created within the _@{DATABASE} object. --- This is done at the beginning of the mission (when the mission starts), and dynamically when new DCS Unit objects are spawned (using the @{SPAWN} class). --- --- The UNIT class **does not contain a :New()** method, rather it provides **:Find()** methods to retrieve the object reference --- using the DCS Unit or the DCS UnitName. --- --- Another thing to know is that UNIT objects do not "contain" the DCS Unit object. --- The UNIT methods will reference the DCS Unit object by name when it is needed during API execution. --- If the DCS Unit object does not exist or is nil, the UNIT methods will return nil and log an exception in the DCS.log file. --- --- The UNIT class provides the following functions to retrieve quickly the relevant UNIT instance: --- --- * @{#UNIT.Find}(): Find a UNIT instance from the _DATABASE object using a DCS Unit object. --- * @{#UNIT.FindByName}(): Find a UNIT instance from the _DATABASE object using a DCS Unit name. --- --- IMPORTANT: ONE SHOULD NEVER SANATIZE these UNIT OBJECT REFERENCES! (make the UNIT object references nil). --- --- 1.2) DCS UNIT APIs --- ------------------ --- The DCS Unit APIs are used extensively within MOOSE. The UNIT class has for each DCS Unit API a corresponding method. --- To be able to distinguish easily in your code the difference between a UNIT API call and a DCS Unit API call, --- the first letter of the method is also capitalized. So, by example, the DCS Unit method @{DCSUnit#Unit.getName}() --- is implemented in the UNIT class as @{#UNIT.GetName}(). --- --- 1.3) Smoke, Flare Units --- ----------------------- --- The UNIT class provides methods to smoke or flare units easily. --- The @{#UNIT.SmokeBlue}(), @{#UNIT.SmokeGreen}(),@{#UNIT.SmokeOrange}(), @{#UNIT.SmokeRed}(), @{#UNIT.SmokeRed}() methods --- will smoke the unit in the corresponding color. Note that smoking a unit is done at the current position of the DCS Unit. --- When the DCS Unit moves for whatever reason, the smoking will still continue! --- The @{#UNIT.FlareGreen}(), @{#UNIT.FlareRed}(), @{#UNIT.FlareWhite}(), @{#UNIT.FlareYellow}() --- methods will fire off a flare in the air with the corresponding color. Note that a flare is a one-off shot and its effect is of very short duration. --- --- 1.4) Location Position, Point --- ----------------------------- --- The UNIT class provides methods to obtain the current point or position of the DCS Unit. --- The @{#UNIT.GetPointVec2}(), @{#UNIT.GetVec3}() will obtain the current **location** of the DCS Unit in a Vec2 (2D) or a **point** in a Vec3 (3D) vector respectively. --- If you want to obtain the complete **3D position** including ori�ntation and direction vectors, consult the @{#UNIT.GetPositionVec3}() method respectively. --- --- 1.5) Test if alive --- ------------------ --- The @{#UNIT.IsAlive}(), @{#UNIT.IsActive}() methods determines if the DCS Unit is alive, meaning, it is existing and active. --- --- 1.6) Test for proximity --- ----------------------- --- The UNIT class contains methods to test the location or proximity against zones or other objects. --- --- ### 1.6.1) Zones --- To test whether the Unit is within a **zone**, use the @{#UNIT.IsInZone}() or the @{#UNIT.IsNotInZone}() methods. Any zone can be tested on, but the zone must be derived from @{Zone#ZONE_BASE}. --- --- ### 1.6.2) Units --- Test if another DCS Unit is within a given radius of the current DCS Unit, use the @{#UNIT.OtherUnitInRadius}() method. --- --- @module Unit --- @author FlightControl - - - - - ---- The UNIT class --- @type UNIT --- @extends Controllable#CONTROLLABLE --- @field #UNIT.FlareColor FlareColor --- @field #UNIT.SmokeColor SmokeColor -UNIT = { - ClassName="UNIT", - FlareColor = { - Green = trigger.flareColor.Green, - Red = trigger.flareColor.Red, - White = trigger.flareColor.White, - Yellow = trigger.flareColor.Yellow - }, - SmokeColor = { - Green = trigger.smokeColor.Green, - Red = trigger.smokeColor.Red, - White = trigger.smokeColor.White, - Orange = trigger.smokeColor.Orange, - Blue = trigger.smokeColor.Blue - }, - } - ---- FlareColor --- @type UNIT.FlareColor --- @field Green --- @field Red --- @field White --- @field Yellow - ---- SmokeColor --- @type UNIT.SmokeColor --- @field Green --- @field Red --- @field White --- @field Orange --- @field Blue - ---- Unit.SensorType --- @type Unit.SensorType --- @field OPTIC --- @field RADAR --- @field IRST --- @field RWR - - --- Registration. - ---- Create a new UNIT from DCSUnit. --- @param #UNIT self --- @param #string UnitName The name of the DCS unit. --- @return Unit#UNIT -function UNIT:Register( UnitName ) - local self = BASE:Inherit( self, CONTROLLABLE:New( UnitName ) ) - self.UnitName = UnitName - return self -end - --- Reference methods. - ---- Finds a UNIT from the _DATABASE using a DCSUnit object. --- @param #UNIT self --- @param DCSUnit#Unit DCSUnit An existing DCS Unit object reference. --- @return Unit#UNIT self -function UNIT:Find( DCSUnit ) - - local UnitName = DCSUnit:getName() - local UnitFound = _DATABASE:FindUnit( UnitName ) - return UnitFound -end - ---- Find a UNIT in the _DATABASE using the name of an existing DCS Unit. --- @param #UNIT self --- @param #string UnitName The Unit Name. --- @return Unit#UNIT self -function UNIT:FindByName( UnitName ) - - local UnitFound = _DATABASE:FindUnit( UnitName ) - return UnitFound -end - ---- Return the name of the UNIT. --- @param #UNIT self --- @return #string The UNIT name. -function UNIT:Name() - - return self.UnitName -end - - ---- @param #UNIT self --- @return DCSUnit#Unit -function UNIT:GetDCSObject() - - local DCSUnit = Unit.getByName( self.UnitName ) - - if DCSUnit then - return DCSUnit - end - - return nil -end - ---- Respawn the @{Unit} using a (tweaked) template of the parent Group. --- --- This function will: --- --- * Get the current position and heading of the group. --- * When the unit is alive, it will tweak the template x, y and heading coordinates of the group and the embedded units to the current units positions. --- * Then it will respawn the re-modelled group. --- --- @param Unit#UNIT self --- @param DCSTypes#Vec3 SpawnVec3 The position where to Spawn the new Unit at. --- @param #number Heading The heading of the unit respawn. -function UNIT:ReSpawn( SpawnVec3, Heading ) - - local SpawnGroupTemplate = UTILS.DeepCopy( _DATABASE:GetGroupTemplateFromUnitName( self:Name() ) ) - self:T( SpawnGroupTemplate ) - local SpawnGroup = self:GetGroup() - - if SpawnGroup then - - local Vec3 = SpawnGroup:GetVec3() - SpawnGroupTemplate.x = Vec3.x - SpawnGroupTemplate.y = Vec3.z - - self:E( #SpawnGroupTemplate.units ) - for UnitID, UnitData in pairs( SpawnGroup:GetUnits() ) do - local GroupUnit = UnitData -- Unit#UNIT - self:E( GroupUnit:GetName() ) - if GroupUnit:IsAlive() then - local GroupUnitVec3 = GroupUnit:GetVec3() - local GroupUnitHeading = GroupUnit:GetHeading() - SpawnGroupTemplate.units[UnitID].alt = GroupUnitVec3.y - SpawnGroupTemplate.units[UnitID].x = GroupUnitVec3.x - SpawnGroupTemplate.units[UnitID].y = GroupUnitVec3.z - SpawnGroupTemplate.units[UnitID].heading = GroupUnitHeading - self:E( { UnitID, SpawnGroupTemplate.units[UnitID], SpawnGroupTemplate.units[UnitID] } ) - end - end - end - - for UnitTemplateID, UnitTemplateData in pairs( SpawnGroupTemplate.units ) do - self:T( UnitTemplateData.name ) - if UnitTemplateData.name == self:Name() then - self:T("Adjusting") - SpawnGroupTemplate.units[UnitTemplateID].alt = SpawnVec3.y - SpawnGroupTemplate.units[UnitTemplateID].x = SpawnVec3.x - SpawnGroupTemplate.units[UnitTemplateID].y = SpawnVec3.z - SpawnGroupTemplate.units[UnitTemplateID].heading = Heading - self:E( { UnitTemplateID, SpawnGroupTemplate.units[UnitTemplateID], SpawnGroupTemplate.units[UnitTemplateID] } ) - end - end - - _DATABASE:Spawn( SpawnGroupTemplate ) -end - - - ---- Returns if the unit is activated. --- @param Unit#UNIT self --- @return #boolean true if Unit is activated. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:IsActive() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - - local UnitIsActive = DCSUnit:isActive() - return UnitIsActive - end - - return nil -end - - - ---- Returns the Unit's callsign - the localized string. --- @param Unit#UNIT self --- @return #string The Callsign of the Unit. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetCallsign() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitCallSign = DCSUnit:getCallsign() - return UnitCallSign - end - - self:E( self.ClassName .. " " .. self.UnitName .. " not found!" ) - return nil -end - - ---- Returns name of the player that control the unit or nil if the unit is controlled by A.I. --- @param Unit#UNIT self --- @return #string Player Name --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetPlayerName() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - - local PlayerName = DCSUnit:getPlayerName() - if PlayerName == nil then - PlayerName = "" - end - return PlayerName - end - - return nil -end - ---- Returns the unit's number in the group. --- The number is the same number the unit has in ME. --- It may not be changed during the mission. --- If any unit in the group is destroyed, the numbers of another units will not be changed. --- @param Unit#UNIT self --- @return #number The Unit number. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetNumber() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitNumber = DCSUnit:getNumber() - return UnitNumber - end - - return nil -end - ---- Returns the unit's group if it exist and nil otherwise. --- @param Unit#UNIT self --- @return Group#GROUP The Group of the Unit. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetGroup() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitGroup = GROUP:Find( DCSUnit:getGroup() ) - return UnitGroup - end - - return nil -end - - --- Need to add here functions to check if radar is on and which object etc. - ---- Returns the prefix name of the DCS Unit. A prefix name is a part of the name before a '#'-sign. --- DCS Units spawned with the @{SPAWN} class contain a '#'-sign to indicate the end of the (base) DCS Unit name. --- The spawn sequence number and unit number are contained within the name after the '#' sign. --- @param Unit#UNIT self --- @return #string The name of the DCS Unit. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetPrefix() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitPrefix = string.match( self.UnitName, ".*#" ):sub( 1, -2 ) - self:T3( UnitPrefix ) - return UnitPrefix - end - - return nil -end - ---- Returns the Unit's ammunition. --- @param Unit#UNIT self --- @return DCSUnit#Unit.Ammo --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetAmmo() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitAmmo = DCSUnit:getAmmo() - return UnitAmmo - end - - return nil -end - ---- Returns the unit sensors. --- @param Unit#UNIT self --- @return DCSUnit#Unit.Sensors --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetSensors() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitSensors = DCSUnit:getSensors() - return UnitSensors - end - - return nil -end - --- Need to add here a function per sensortype --- unit:hasSensors(Unit.SensorType.RADAR, Unit.RadarType.AS) - ---- Returns if the unit has sensors of a certain type. --- @param Unit#UNIT self --- @return #boolean returns true if the unit has specified types of sensors. This function is more preferable than Unit.getSensors() if you don't want to get information about all the unit's sensors, and just want to check if the unit has specified types of sensors. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:HasSensors( ... ) - self:F2( arg ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local HasSensors = DCSUnit:hasSensors( unpack( arg ) ) - return HasSensors - end - - return nil -end - ---- Returns if the unit is SEADable. --- @param Unit#UNIT self --- @return #boolean returns true if the unit is SEADable. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:HasSEAD() - self:F2() - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitSEADAttributes = DCSUnit:getDesc().attributes - - local HasSEAD = false - if UnitSEADAttributes["RADAR_BAND1_FOR_ARM"] and UnitSEADAttributes["RADAR_BAND1_FOR_ARM"] == true or - UnitSEADAttributes["RADAR_BAND2_FOR_ARM"] and UnitSEADAttributes["RADAR_BAND2_FOR_ARM"] == true then - HasSEAD = true - end - return HasSEAD - end - - return nil -end - ---- Returns two values: --- --- * First value indicates if at least one of the unit's radar(s) is on. --- * Second value is the object of the radar's interest. Not nil only if at least one radar of the unit is tracking a target. --- @param Unit#UNIT self --- @return #boolean Indicates if at least one of the unit's radar(s) is on. --- @return DCSObject#Object The object of the radar's interest. Not nil only if at least one radar of the unit is tracking a target. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetRadar() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitRadarOn, UnitRadarObject = DCSUnit:getRadar() - return UnitRadarOn, UnitRadarObject - end - - return nil, nil -end - ---- Returns relative amount of fuel (from 0.0 to 1.0) the unit has in its internal tanks. If there are additional fuel tanks the value may be greater than 1.0. --- @param Unit#UNIT self --- @return #number The relative amount of fuel (from 0.0 to 1.0). --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetFuel() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitFuel = DCSUnit:getFuel() - return UnitFuel - end - - return nil -end - ---- Returns the unit's health. Dead units has health <= 1.0. --- @param Unit#UNIT self --- @return #number The Unit's health value. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetLife() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitLife = DCSUnit:getLife() - return UnitLife - end - - return nil -end - ---- Returns the Unit's initial health. --- @param Unit#UNIT self --- @return #number The Unit's initial health value. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetLife0() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitLife0 = DCSUnit:getLife0() - return UnitLife0 - end - - return nil -end - ---- Returns the Unit's A2G threat level on a scale from 1 to 10 ... --- The following threat levels are foreseen: --- --- * Threat level 0: Unit is unarmed. --- * Threat level 1: Unit is infantry. --- * Threat level 2: Unit is an infantry vehicle. --- * Threat level 3: Unit is ground artillery. --- * Threat level 4: Unit is a tank. --- * Threat level 5: Unit is a modern tank or ifv with ATGM. --- * Threat level 6: Unit is a AAA. --- * Threat level 7: Unit is a SAM or manpad, IR guided. --- * Threat level 8: Unit is a Short Range SAM, radar guided. --- * Threat level 9: Unit is a Medium Range SAM, radar guided. --- * Threat level 10: Unit is a Long Range SAM, radar guided. -function UNIT:GetThreatLevel() - - local Attributes = self:GetDesc().attributes - local ThreatLevel = 0 - - local ThreatLevels = { - "Unarmed", - "Infantry", - "Old Tanks & APCs", - "Tanks & IFVs without ATGM", - "Tanks & IFV with ATGM", - "Modern Tanks", - "AAA", - "IR Guided SAMs", - "SR SAMs", - "MR SAMs", - "LR SAMs" - } - - self:T2( Attributes ) - - if Attributes["LR SAM"] then ThreatLevel = 10 - elseif Attributes["MR SAM"] then ThreatLevel = 9 - elseif Attributes["SR SAM"] and - not Attributes["IR Guided SAM"] then ThreatLevel = 8 - elseif ( Attributes["SR SAM"] or Attributes["MANPADS"] ) and - Attributes["IR Guided SAM"] then ThreatLevel = 7 - elseif Attributes["AAA"] then ThreatLevel = 6 - elseif Attributes["Modern Tanks"] then ThreatLevel = 5 - elseif ( Attributes["Tanks"] or Attributes["IFV"] ) and - Attributes["ATGM"] then ThreatLevel = 4 - elseif ( Attributes["Tanks"] or Attributes["IFV"] ) and - not Attributes["ATGM"] then ThreatLevel = 3 - elseif Attributes["Old Tanks"] or Attributes["APC"] then ThreatLevel = 2 - elseif Attributes["Infantry"] then ThreatLevel = 1 - end - - self:T2( ThreatLevel ) - return ThreatLevel, ThreatLevels[ThreatLevel+1] - -end - - --- Is functions - ---- Returns true if the unit is within a @{Zone}. --- @param #UNIT self --- @param Zone#ZONE_BASE Zone The zone to test. --- @return #boolean Returns true if the unit is within the @{Zone#ZONE_BASE} -function UNIT:IsInZone( Zone ) - self:F2( { self.UnitName, Zone } ) - - if self:IsAlive() then - local IsInZone = Zone:IsPointVec3InZone( self:GetVec3() ) - - self:T( { IsInZone } ) - return IsInZone - end - - return false -end - ---- Returns true if the unit is not within a @{Zone}. --- @param #UNIT self --- @param Zone#ZONE_BASE Zone The zone to test. --- @return #boolean Returns true if the unit is not within the @{Zone#ZONE_BASE} -function UNIT:IsNotInZone( Zone ) - self:F2( { self.UnitName, Zone } ) - - if self:IsAlive() then - local IsInZone = not Zone:IsPointVec3InZone( self:GetVec3() ) - - self:T( { IsInZone } ) - return IsInZone - else - return false - end -end - - ---- Returns true if there is an **other** DCS Unit within a radius of the current 2D point of the DCS Unit. --- @param Unit#UNIT self --- @param Unit#UNIT AwaitUnit The other UNIT wrapper object. --- @param Radius The radius in meters with the DCS Unit in the centre. --- @return true If the other DCS Unit is within the radius of the 2D point of the DCS Unit. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:OtherUnitInRadius( AwaitUnit, Radius ) - self:F2( { self.UnitName, AwaitUnit.UnitName, Radius } ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitVec3 = self:GetVec3() - local AwaitUnitVec3 = AwaitUnit:GetVec3() - - if (((UnitVec3.x - AwaitUnitVec3.x)^2 + (UnitVec3.z - AwaitUnitVec3.z)^2)^0.5 <= Radius) then - self:T3( "true" ) - return true - else - self:T3( "false" ) - return false - end - end - - return nil -end - - - ---- Signal a flare at the position of the UNIT. --- @param #UNIT self -function UNIT:Flare( FlareColor ) - self:F2() - trigger.action.signalFlare( self:GetVec3(), FlareColor , 0 ) -end - ---- Signal a white flare at the position of the UNIT. --- @param #UNIT self -function UNIT:FlareWhite() - self:F2() - trigger.action.signalFlare( self:GetVec3(), trigger.flareColor.White , 0 ) -end - ---- Signal a yellow flare at the position of the UNIT. --- @param #UNIT self -function UNIT:FlareYellow() - self:F2() - trigger.action.signalFlare( self:GetVec3(), trigger.flareColor.Yellow , 0 ) -end - ---- Signal a green flare at the position of the UNIT. --- @param #UNIT self -function UNIT:FlareGreen() - self:F2() - trigger.action.signalFlare( self:GetVec3(), trigger.flareColor.Green , 0 ) -end - ---- Signal a red flare at the position of the UNIT. --- @param #UNIT self -function UNIT:FlareRed() - self:F2() - local Vec3 = self:GetVec3() - if Vec3 then - trigger.action.signalFlare( Vec3, trigger.flareColor.Red, 0 ) - end -end - ---- Smoke the UNIT. --- @param #UNIT self -function UNIT:Smoke( SmokeColor, Range ) - self:F2() - if Range then - trigger.action.smoke( self:GetRandomVec3( Range ), SmokeColor ) - else - trigger.action.smoke( self:GetVec3(), SmokeColor ) - end - -end - ---- Smoke the UNIT Green. --- @param #UNIT self -function UNIT:SmokeGreen() - self:F2() - trigger.action.smoke( self:GetVec3(), trigger.smokeColor.Green ) -end - ---- Smoke the UNIT Red. --- @param #UNIT self -function UNIT:SmokeRed() - self:F2() - trigger.action.smoke( self:GetVec3(), trigger.smokeColor.Red ) -end - ---- Smoke the UNIT White. --- @param #UNIT self -function UNIT:SmokeWhite() - self:F2() - trigger.action.smoke( self:GetVec3(), trigger.smokeColor.White ) -end - ---- Smoke the UNIT Orange. --- @param #UNIT self -function UNIT:SmokeOrange() - self:F2() - trigger.action.smoke( self:GetVec3(), trigger.smokeColor.Orange ) -end - ---- Smoke the UNIT Blue. --- @param #UNIT self -function UNIT:SmokeBlue() - self:F2() - trigger.action.smoke( self:GetVec3(), trigger.smokeColor.Blue ) -end - --- Is methods - ---- Returns if the unit is of an air category. --- If the unit is a helicopter or a plane, then this method will return true, otherwise false. --- @param #UNIT self --- @return #boolean Air category evaluation result. -function UNIT:IsAir() - self:F2() - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitDescriptor = DCSUnit:getDesc() - self:T3( { UnitDescriptor.category, Unit.Category.AIRPLANE, Unit.Category.HELICOPTER } ) - - local IsAirResult = ( UnitDescriptor.category == Unit.Category.AIRPLANE ) or ( UnitDescriptor.category == Unit.Category.HELICOPTER ) - - self:T3( IsAirResult ) - return IsAirResult - end - - return nil -end - ---- Returns if the unit is of an ground category. --- If the unit is a ground vehicle or infantry, this method will return true, otherwise false. --- @param #UNIT self --- @return #boolean Ground category evaluation result. -function UNIT:IsGround() - self:F2() - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitDescriptor = DCSUnit:getDesc() - self:T3( { UnitDescriptor.category, Unit.Category.GROUND_UNIT } ) - - local IsGroundResult = ( UnitDescriptor.category == Unit.Category.GROUND_UNIT ) - - self:T3( IsGroundResult ) - return IsGroundResult - end - - return nil -end - ---- Returns if the unit is a friendly unit. --- @param #UNIT self --- @return #boolean IsFriendly evaluation result. -function UNIT:IsFriendly( FriendlyCoalition ) - self:F2() - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitCoalition = DCSUnit:getCoalition() - self:T3( { UnitCoalition, FriendlyCoalition } ) - - local IsFriendlyResult = ( UnitCoalition == FriendlyCoalition ) - - self:E( IsFriendlyResult ) - return IsFriendlyResult - end - - return nil -end - ---- Returns if the unit is of a ship category. --- If the unit is a ship, this method will return true, otherwise false. --- @param #UNIT self --- @return #boolean Ship category evaluation result. -function UNIT:IsShip() - self:F2() - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitDescriptor = DCSUnit:getDesc() - self:T3( { UnitDescriptor.category, Unit.Category.SHIP } ) - - local IsShipResult = ( UnitDescriptor.category == Unit.Category.SHIP ) - - self:T3( IsShipResult ) - return IsShipResult - end - - return nil -end - ---- This module contains the ZONE classes, inherited from @{Zone#ZONE_BASE}. +--- This module contains the ZONE classes, inherited from @{Core.Zone#ZONE_BASE}. -- There are essentially two core functions that zones accomodate: -- -- * Test if an object is within the zone boundaries. @@ -9835,7 +5741,7 @@ end -- The object classes are using the zone classes to test the zone boundaries, which can take various forms: -- -- * Test if completely within the zone. --- * Test if partly within the zone (for @{Group#GROUP} objects). +-- * Test if partly within the zone (for @{Wrapper.Group#GROUP} objects). -- * Test if not in the zone. -- * Distance to the nearest intersecting point of the zone. -- * Distance to the center of the zone. @@ -9843,53 +5749,125 @@ end -- -- Each of these ZONE classes have a zone name, and specific parameters defining the zone type: -- --- * @{Zone#ZONE_BASE}: The ZONE_BASE class defining the base for all other zone classes. --- * @{Zone#ZONE_RADIUS}: The ZONE_RADIUS class defined by a zone name, a location and a radius. --- * @{Zone#ZONE}: The ZONE class, defined by the zone name as defined within the Mission Editor. --- * @{Zone#ZONE_UNIT}: The ZONE_UNIT class defines by a zone around a @{Unit#UNIT} with a radius. --- * @{Zone#ZONE_GROUP}: The ZONE_GROUP class defines by a zone around a @{Group#GROUP} with a radius. --- * @{Zone#ZONE_POLYGON}: The ZONE_POLYGON class defines by a sequence of @{Group#GROUP} waypoints within the Mission Editor, forming a polygon. --- --- Each zone implements two polymorphic functions defined in @{Zone#ZONE_BASE}: --- --- * @{#ZONE_BASE.IsPointVec2InZone}: Returns if a location is within the zone. --- * @{#ZONE_BASE.IsPointVec3InZone}: Returns if a point is within the zone. +-- * @{Core.Zone#ZONE_BASE}: The ZONE_BASE class defining the base for all other zone classes. +-- * @{Core.Zone#ZONE_RADIUS}: The ZONE_RADIUS class defined by a zone name, a location and a radius. +-- * @{Core.Zone#ZONE}: The ZONE class, defined by the zone name as defined within the Mission Editor. +-- * @{Core.Zone#ZONE_UNIT}: The ZONE_UNIT class defines by a zone around a @{Wrapper.Unit#UNIT} with a radius. +-- * @{Core.Zone#ZONE_GROUP}: The ZONE_GROUP class defines by a zone around a @{Wrapper.Group#GROUP} with a radius. +-- * @{Core.Zone#ZONE_POLYGON}: The ZONE_POLYGON class defines by a sequence of @{Wrapper.Group#GROUP} waypoints within the Mission Editor, forming a polygon. -- -- === -- --- 1) @{Zone#ZONE_BASE} class, extends @{Base#BASE} +-- 1) @{Core.Zone#ZONE_BASE} class, extends @{Core.Base#BASE} -- ================================================ --- The ZONE_BASE class defining the base for all other zone classes. +-- This class is an abstract BASE class for derived classes, and is not meant to be instantiated. +-- +-- ### 1.1) Each zone has a name: +-- +-- * @{#ZONE_BASE.GetName}(): Returns the name of the zone. +-- +-- ### 1.2) Each zone implements two polymorphic functions defined in @{Core.Zone#ZONE_BASE}: +-- +-- * @{#ZONE_BASE.IsPointVec2InZone}(): Returns if a @{Core.Point#POINT_VEC2} is within the zone. +-- * @{#ZONE_BASE.IsPointVec3InZone}(): Returns if a @{Core.Point#POINT_VEC3} is within the zone. +-- +-- ### 1.3) A zone has a probability factor that can be set to randomize a selection between zones: +-- +-- * @{#ZONE_BASE.SetRandomizeProbability}(): Set the randomization probability of a zone to be selected, taking a value between 0 and 1 ( 0 = 0%, 1 = 100% ) +-- * @{#ZONE_BASE.GetRandomizeProbability}(): Get the randomization probability of a zone to be selected, passing a value between 0 and 1 ( 0 = 0%, 1 = 100% ) +-- * @{#ZONE_BASE.GetZoneMaybe}(): Get the zone taking into account the randomization probability. nil is returned if this zone is not a candidate. +-- +-- ### 1.4) A zone manages Vectors: +-- +-- * @{#ZONE_BASE.GetVec2}(): Returns the @{Dcs.DCSTypes#Vec2} coordinate of the zone. +-- * @{#ZONE_BASE.GetRandomVec2}(): Define a random @{Dcs.DCSTypes#Vec2} within the zone. +-- +-- ### 1.5) A zone has a bounding square: +-- +-- * @{#ZONE_BASE.GetBoundingSquare}(): Get the outer most bounding square of the zone. +-- +-- ### 1.6) A zone can be marked: +-- +-- * @{#ZONE_BASE.SmokeZone}(): Smokes the zone boundaries in a color. +-- * @{#ZONE_BASE.FlareZone}(): Flares the zone boundaries in a color. -- -- === -- --- 2) @{Zone#ZONE_RADIUS} class, extends @{Zone#ZONE_BASE} +-- 2) @{Core.Zone#ZONE_RADIUS} class, extends @{Core.Zone#ZONE_BASE} -- ======================================================= -- The ZONE_RADIUS class defined by a zone name, a location and a radius. +-- This class implements the inherited functions from Core.Zone#ZONE_BASE taking into account the own zone format and properties. +-- +-- ### 2.1) @{Core.Zone#ZONE_RADIUS} constructor: +-- +-- * @{#ZONE_BASE.New}(): Constructor. +-- +-- ### 2.2) Manage the radius of the zone: +-- +-- * @{#ZONE_BASE.SetRadius}(): Sets the radius of the zone. +-- * @{#ZONE_BASE.GetRadius}(): Returns the radius of the zone. +-- +-- ### 2.3) Manage the location of the zone: +-- +-- * @{#ZONE_BASE.SetVec2}(): Sets the @{Dcs.DCSTypes#Vec2} of the zone. +-- * @{#ZONE_BASE.GetVec2}(): Returns the @{Dcs.DCSTypes#Vec2} of the zone. +-- * @{#ZONE_BASE.GetVec3}(): Returns the @{Dcs.DCSTypes#Vec3} of the zone, taking an additional height parameter. -- -- === -- --- 3) @{Zone#ZONE} class, extends @{Zone#ZONE_RADIUS} +-- 3) @{Core.Zone#ZONE} class, extends @{Core.Zone#ZONE_RADIUS} -- ========================================== -- The ZONE class, defined by the zone name as defined within the Mission Editor. +-- This class implements the inherited functions from {Core.Zone#ZONE_RADIUS} taking into account the own zone format and properties. -- -- === -- --- 4) @{Zone#ZONE_UNIT} class, extends @{Zone#ZONE_RADIUS} +-- 4) @{Core.Zone#ZONE_UNIT} class, extends @{Core.Zone#ZONE_RADIUS} -- ======================================================= --- The ZONE_UNIT class defined by a zone around a @{Unit#UNIT} with a radius. +-- The ZONE_UNIT class defined by a zone around a @{Wrapper.Unit#UNIT} with a radius. +-- This class implements the inherited functions from @{Core.Zone#ZONE_RADIUS} taking into account the own zone format and properties. -- -- === -- --- 5) @{Zone#ZONE_GROUP} class, extends @{Zone#ZONE_RADIUS} +-- 5) @{Core.Zone#ZONE_GROUP} class, extends @{Core.Zone#ZONE_RADIUS} -- ======================================================= --- The ZONE_GROUP class defines by a zone around a @{Group#GROUP} with a radius. The current leader of the group defines the center of the zone. +-- The ZONE_GROUP class defines by a zone around a @{Wrapper.Group#GROUP} with a radius. The current leader of the group defines the center of the zone. +-- This class implements the inherited functions from @{Core.Zone#ZONE_RADIUS} taking into account the own zone format and properties. -- -- === -- --- 6) @{Zone#ZONE_POLYGON} class, extends @{Zone#ZONE_BASE} +-- 6) @{Core.Zone#ZONE_POLYGON_BASE} class, extends @{Core.Zone#ZONE_BASE} -- ======================================================== --- The ZONE_POLYGON class defined by a sequence of @{Group#GROUP} waypoints within the Mission Editor, forming a polygon. +-- The ZONE_POLYGON_BASE class defined by a sequence of @{Wrapper.Group#GROUP} waypoints within the Mission Editor, forming a polygon. +-- This class implements the inherited functions from @{Core.Zone#ZONE_RADIUS} taking into account the own zone format and properties. +-- This class is an abstract BASE class for derived classes, and is not meant to be instantiated. +-- +-- === +-- +-- 7) @{Core.Zone#ZONE_POLYGON} class, extends @{Core.Zone#ZONE_POLYGON_BASE} +-- ================================================================ +-- The ZONE_POLYGON class defined by a sequence of @{Wrapper.Group#GROUP} waypoints within the Mission Editor, forming a polygon. +-- This class implements the inherited functions from @{Core.Zone#ZONE_RADIUS} taking into account the own zone format and properties. +-- +-- ==== +-- +-- **API CHANGE HISTORY** +-- ====================== +-- +-- The underlying change log documents the API changes. Please read this carefully. The following notation is used: +-- +-- * **Added** parts are expressed in bold type face. +-- * _Removed_ parts are expressed in italic type face. +-- +-- Hereby the change log: +-- +-- 2016-08-15: ZONE_BASE:**GetName()** added. +-- +-- 2016-08-15: ZONE_BASE:**SetZoneProbability( ZoneProbability )** added. +-- +-- 2016-08-15: ZONE_BASE:**GetZoneProbability()** added. +-- +-- 2016-08-15: ZONE_BASE:**GetZoneMaybe()** added. -- -- === -- @@ -9900,18 +5878,21 @@ end --- The ZONE_BASE class -- @type ZONE_BASE -- @field #string ZoneName Name of the zone. --- @extends Base#BASE +-- @field #number ZoneProbability A value between 0 and 1. 0 = 0% and 1 = 100% probability. +-- @extends Core.Base#BASE ZONE_BASE = { ClassName = "ZONE_BASE", + ZoneName = "", + ZoneProbability = 1, } --- The ZONE_BASE.BoundingSquare -- @type ZONE_BASE.BoundingSquare --- @field DCSTypes#Distance x1 The lower x coordinate (left down) --- @field DCSTypes#Distance y1 The lower y coordinate (left down) --- @field DCSTypes#Distance x2 The higher x coordinate (right up) --- @field DCSTypes#Distance y2 The higher y coordinate (right up) +-- @field Dcs.DCSTypes#Distance x1 The lower x coordinate (left down) +-- @field Dcs.DCSTypes#Distance y1 The lower y coordinate (left down) +-- @field Dcs.DCSTypes#Distance x2 The higher x coordinate (right up) +-- @field Dcs.DCSTypes#Distance y2 The higher y coordinate (right up) --- ZONE_BASE constructor @@ -9927,9 +5908,17 @@ function ZONE_BASE:New( ZoneName ) return self end +--- Returns the name of the zone. +-- @param #ZONE_BASE self +-- @return #string The name of the zone. +function ZONE_BASE:GetName() + self:F2() + + return self.ZoneName +end --- Returns if a location is within the zone. -- @param #ZONE_BASE self --- @param DCSTypes#Vec2 Vec2 The location to test. +-- @param Dcs.DCSTypes#Vec2 Vec2 The location to test. -- @return #boolean true if the location is within the zone. function ZONE_BASE:IsPointVec2InZone( Vec2 ) self:F2( Vec2 ) @@ -9939,7 +5928,7 @@ end --- Returns if a point is within the zone. -- @param #ZONE_BASE self --- @param DCSTypes#Vec3 Vec3 The point to test. +-- @param Dcs.DCSTypes#Vec3 Vec3 The point to test. -- @return #boolean true if the point is within the zone. function ZONE_BASE:IsPointVec3InZone( Vec3 ) self:F2( Vec3 ) @@ -9949,7 +5938,7 @@ function ZONE_BASE:IsPointVec3InZone( Vec3 ) return InZone end ---- Returns the Vec2 coordinate of the zone. +--- Returns the @{Dcs.DCSTypes#Vec2} coordinate of the zone. -- @param #ZONE_BASE self -- @return #nil. function ZONE_BASE:GetVec2() @@ -9957,9 +5946,9 @@ function ZONE_BASE:GetVec2() return nil end ---- Define a random @{DCSTypes#Vec2} within the zone. +--- Define a random @{Dcs.DCSTypes#Vec2} within the zone. -- @param #ZONE_BASE self --- @return #nil The Vec2 coordinates. +-- @return Dcs.DCSTypes#Vec2 The Vec2 coordinates. function ZONE_BASE:GetRandomVec2() return nil end @@ -9975,27 +5964,61 @@ end --- Smokes the zone boundaries in a color. -- @param #ZONE_BASE self --- @param SmokeColor The smoke color. +-- @param Utilities.Utils#SMOKECOLOR SmokeColor The smoke color. function ZONE_BASE:SmokeZone( SmokeColor ) self:F2( SmokeColor ) end +--- Set the randomization probability of a zone to be selected. +-- @param #ZONE_BASE self +-- @param ZoneProbability A value between 0 and 1. 0 = 0% and 1 = 100% probability. +function ZONE_BASE:SetZoneProbability( ZoneProbability ) + self:F2( ZoneProbability ) + + self.ZoneProbability = ZoneProbability or 1 + return self +end + +--- Get the randomization probability of a zone to be selected. +-- @param #ZONE_BASE self +-- @return #number A value between 0 and 1. 0 = 0% and 1 = 100% probability. +function ZONE_BASE:GetZoneProbability() + self:F2() + + return self.ZoneProbability +end + +--- Get the zone taking into account the randomization probability of a zone to be selected. +-- @param #ZONE_BASE self +-- @return #ZONE_BASE The zone is selected taking into account the randomization probability factor. +-- @return #nil The zone is not selected taking into account the randomization probability factor. +function ZONE_BASE:GetZoneMaybe() + self:F2() + + local Randomization = math.random() + if Randomization <= self.ZoneProbability then + return self + else + return nil + end +end + --- The ZONE_RADIUS class, defined by a zone name, a location and a radius. -- @type ZONE_RADIUS --- @field DCSTypes#Vec2 Vec2 The current location of the zone. --- @field DCSTypes#Distance Radius The radius of the zone. --- @extends Zone#ZONE_BASE +-- @field Dcs.DCSTypes#Vec2 Vec2 The current location of the zone. +-- @field Dcs.DCSTypes#Distance Radius The radius of the zone. +-- @extends Core.Zone#ZONE_BASE ZONE_RADIUS = { ClassName="ZONE_RADIUS", } ---- Constructor of ZONE_RADIUS, taking the zone name, the zone location and a radius. +--- Constructor of @{#ZONE_RADIUS}, taking the zone name, the zone location and a radius. -- @param #ZONE_RADIUS self -- @param #string ZoneName Name of the zone. --- @param DCSTypes#Vec2 Vec2 The location of the zone. --- @param DCSTypes#Distance Radius The radius of the zone. +-- @param Dcs.DCSTypes#Vec2 Vec2 The location of the zone. +-- @param Dcs.DCSTypes#Distance Radius The radius of the zone. -- @return #ZONE_RADIUS self function ZONE_RADIUS:New( ZoneName, Vec2, Radius ) local self = BASE:Inherit( self, ZONE_BASE:New( ZoneName ) ) @@ -10009,7 +6032,7 @@ end --- Smokes the zone boundaries in a color. -- @param #ZONE_RADIUS self --- @param #POINT_VEC3.SmokeColor SmokeColor The smoke color. +-- @param Utilities.Utils#SMOKECOLOR SmokeColor The smoke color. -- @param #number Points (optional) The amount of points in the circle. -- @return #ZONE_RADIUS self function ZONE_RADIUS:SmokeZone( SmokeColor, Points ) @@ -10036,9 +6059,9 @@ end --- Flares the zone boundaries in a color. -- @param #ZONE_RADIUS self --- @param #POINT_VEC3.FlareColor FlareColor The flare color. +-- @param Utilities.Utils#FLARECOLOR FlareColor The flare color. -- @param #number Points (optional) The amount of points in the circle. --- @param DCSTypes#Azimuth Azimuth (optional) Azimuth The azimuth of the flare. +-- @param Dcs.DCSTypes#Azimuth Azimuth (optional) Azimuth The azimuth of the flare. -- @return #ZONE_RADIUS self function ZONE_RADIUS:FlareZone( FlareColor, Points, Azimuth ) self:F2( { FlareColor, Azimuth } ) @@ -10063,7 +6086,7 @@ end --- Returns the radius of the zone. -- @param #ZONE_RADIUS self --- @return DCSTypes#Distance The radius of the zone. +-- @return Dcs.DCSTypes#Distance The radius of the zone. function ZONE_RADIUS:GetRadius() self:F2( self.ZoneName ) @@ -10074,8 +6097,8 @@ end --- Sets the radius of the zone. -- @param #ZONE_RADIUS self --- @param DCSTypes#Distance Radius The radius of the zone. --- @return DCSTypes#Distance The radius of the zone. +-- @param Dcs.DCSTypes#Distance Radius The radius of the zone. +-- @return Dcs.DCSTypes#Distance The radius of the zone. function ZONE_RADIUS:SetRadius( Radius ) self:F2( self.ZoneName ) @@ -10085,9 +6108,9 @@ function ZONE_RADIUS:SetRadius( Radius ) return self.Radius end ---- Returns the location of the zone. +--- Returns the @{Dcs.DCSTypes#Vec2} of the zone. -- @param #ZONE_RADIUS self --- @return DCSTypes#Vec2 The location of the zone. +-- @return Dcs.DCSTypes#Vec2 The location of the zone. function ZONE_RADIUS:GetVec2() self:F2( self.ZoneName ) @@ -10096,11 +6119,11 @@ function ZONE_RADIUS:GetVec2() return self.Vec2 end ---- Sets the location of the zone. +--- Sets the @{Dcs.DCSTypes#Vec2} of the zone. -- @param #ZONE_RADIUS self --- @param DCSTypes#Vec2 Vec2 The new location of the zone. --- @return DCSTypes#Vec2 The new location of the zone. -function ZONE_RADIUS:SetPointVec2( Vec2 ) +-- @param Dcs.DCSTypes#Vec2 Vec2 The new location of the zone. +-- @return Dcs.DCSTypes#Vec2 The new location of the zone. +function ZONE_RADIUS:SetVec2( Vec2 ) self:F2( self.ZoneName ) self.Vec2 = Vec2 @@ -10110,10 +6133,10 @@ function ZONE_RADIUS:SetPointVec2( Vec2 ) return self.Vec2 end ---- Returns the @{DCSTypes#Vec3} of the ZONE_RADIUS. +--- Returns the @{Dcs.DCSTypes#Vec3} of the ZONE_RADIUS. -- @param #ZONE_RADIUS self --- @param DCSTypes#Distance Height The height to add to the land height where the center of the zone is located. --- @return DCSTypes#Vec3 The point of the zone. +-- @param Dcs.DCSTypes#Distance Height The height to add to the land height where the center of the zone is located. +-- @return Dcs.DCSTypes#Vec3 The point of the zone. function ZONE_RADIUS:GetVec3( Height ) self:F2( { self.ZoneName, Height } ) @@ -10130,7 +6153,7 @@ end --- Returns if a location is within the zone. -- @param #ZONE_RADIUS self --- @param DCSTypes#Vec2 Vec2 The location to test. +-- @param Dcs.DCSTypes#Vec2 Vec2 The location to test. -- @return #boolean true if the location is within the zone. function ZONE_RADIUS:IsPointVec2InZone( Vec2 ) self:F2( Vec2 ) @@ -10148,7 +6171,7 @@ end --- Returns if a point is within the zone. -- @param #ZONE_RADIUS self --- @param DCSTypes#Vec3 Vec3 The point to test. +-- @param Dcs.DCSTypes#Vec3 Vec3 The point to test. -- @return #boolean true if the point is within the zone. function ZONE_RADIUS:IsPointVec3InZone( Vec3 ) self:F2( Vec3 ) @@ -10160,7 +6183,7 @@ end --- Returns a random location within the zone. -- @param #ZONE_RADIUS self --- @return DCSTypes#Vec2 The random location within the zone. +-- @return Dcs.DCSTypes#Vec2 The random location within the zone. function ZONE_RADIUS:GetRandomVec2() self:F( self.ZoneName ) @@ -10180,7 +6203,7 @@ end --- The ZONE class, defined by the zone name as defined within the Mission Editor. The location and the radius are automatically collected from the mission settings. -- @type ZONE --- @extends Zone#ZONE_RADIUS +-- @extends Core.Zone#ZONE_RADIUS ZONE = { ClassName="ZONE", } @@ -10208,10 +6231,10 @@ function ZONE:New( ZoneName ) end ---- The ZONE_UNIT class defined by a zone around a @{Unit#UNIT} with a radius. +--- The ZONE_UNIT class defined by a zone around a @{Wrapper.Unit#UNIT} with a radius. -- @type ZONE_UNIT --- @field Unit#UNIT ZoneUNIT --- @extends Zone#ZONE_RADIUS +-- @field Wrapper.Unit#UNIT ZoneUNIT +-- @extends Core.Zone#ZONE_RADIUS ZONE_UNIT = { ClassName="ZONE_UNIT", } @@ -10219,8 +6242,8 @@ ZONE_UNIT = { --- Constructor to create a ZONE_UNIT instance, taking the zone name, a zone unit and a radius. -- @param #ZONE_UNIT self -- @param #string ZoneName Name of the zone. --- @param Unit#UNIT ZoneUNIT The unit as the center of the zone. --- @param DCSTypes#Distance Radius The radius of the zone. +-- @param Wrapper.Unit#UNIT ZoneUNIT The unit as the center of the zone. +-- @param Dcs.DCSTypes#Distance Radius The radius of the zone. -- @return #ZONE_UNIT self function ZONE_UNIT:New( ZoneName, ZoneUNIT, Radius ) local self = BASE:Inherit( self, ZONE_RADIUS:New( ZoneName, ZoneUNIT:GetVec2(), Radius ) ) @@ -10233,9 +6256,9 @@ function ZONE_UNIT:New( ZoneName, ZoneUNIT, Radius ) end ---- Returns the current location of the @{Unit#UNIT}. +--- Returns the current location of the @{Wrapper.Unit#UNIT}. -- @param #ZONE_UNIT self --- @return DCSTypes#Vec2 The location of the zone based on the @{Unit#UNIT}location. +-- @return Dcs.DCSTypes#Vec2 The location of the zone based on the @{Wrapper.Unit#UNIT}location. function ZONE_UNIT:GetVec2() self:F( self.ZoneName ) @@ -10254,29 +6277,30 @@ end --- Returns a random location within the zone. -- @param #ZONE_UNIT self --- @return DCSTypes#Vec2 The random location within the zone. +-- @return Dcs.DCSTypes#Vec2 The random location within the zone. function ZONE_UNIT:GetRandomVec2() self:F( self.ZoneName ) - local Point = {} - local PointVec2 = self.ZoneUNIT:GetPointVec2() - if not PointVec2 then - PointVec2 = self.LastVec2 + local RandomVec2 = {} + local Vec2 = self.ZoneUNIT:GetVec2() + + if not Vec2 then + Vec2 = self.LastVec2 end local angle = math.random() * math.pi*2; - Point.x = PointVec2.x + math.cos( angle ) * math.random() * self:GetRadius(); - Point.y = PointVec2.y + math.sin( angle ) * math.random() * self:GetRadius(); + RandomVec2.x = Vec2.x + math.cos( angle ) * math.random() * self:GetRadius(); + RandomVec2.y = Vec2.y + math.sin( angle ) * math.random() * self:GetRadius(); - self:T( { Point } ) + self:T( { RandomVec2 } ) - return Point + return RandomVec2 end ---- Returns the @{DCSTypes#Vec3} of the ZONE_UNIT. +--- Returns the @{Dcs.DCSTypes#Vec3} of the ZONE_UNIT. -- @param #ZONE_UNIT self --- @param DCSTypes#Distance Height The height to add to the land height where the center of the zone is located. --- @return DCSTypes#Vec3 The point of the zone. +-- @param Dcs.DCSTypes#Distance Height The height to add to the land height where the center of the zone is located. +-- @return Dcs.DCSTypes#Vec3 The point of the zone. function ZONE_UNIT:GetVec3( Height ) self:F2( self.ZoneName ) @@ -10293,17 +6317,17 @@ end --- The ZONE_GROUP class defined by a zone around a @{Group}, taking the average center point of all the units within the Group, with a radius. -- @type ZONE_GROUP --- @field Group#GROUP ZoneGROUP --- @extends Zone#ZONE_RADIUS +-- @field Wrapper.Group#GROUP ZoneGROUP +-- @extends Core.Zone#ZONE_RADIUS ZONE_GROUP = { ClassName="ZONE_GROUP", } ---- Constructor to create a ZONE_GROUP instance, taking the zone name, a zone @{Group#GROUP} and a radius. +--- Constructor to create a ZONE_GROUP instance, taking the zone name, a zone @{Wrapper.Group#GROUP} and a radius. -- @param #ZONE_GROUP self -- @param #string ZoneName Name of the zone. --- @param Group#GROUP ZoneGROUP The @{Group} as the center of the zone. --- @param DCSTypes#Distance Radius The radius of the zone. +-- @param Wrapper.Group#GROUP ZoneGROUP The @{Group} as the center of the zone. +-- @param Dcs.DCSTypes#Distance Radius The radius of the zone. -- @return #ZONE_GROUP self function ZONE_GROUP:New( ZoneName, ZoneGROUP, Radius ) local self = BASE:Inherit( self, ZONE_RADIUS:New( ZoneName, ZoneGROUP:GetVec2(), Radius ) ) @@ -10317,7 +6341,7 @@ end --- Returns the current location of the @{Group}. -- @param #ZONE_GROUP self --- @return DCSTypes#Vec2 The location of the zone based on the @{Group} location. +-- @return Dcs.DCSTypes#Vec2 The location of the zone based on the @{Group} location. function ZONE_GROUP:GetVec2() self:F( self.ZoneName ) @@ -10330,7 +6354,7 @@ end --- Returns a random location within the zone of the @{Group}. -- @param #ZONE_GROUP self --- @return DCSTypes#Vec2 The random location of the zone based on the @{Group} location. +-- @return Dcs.DCSTypes#Vec2 The random location of the zone based on the @{Group} location. function ZONE_GROUP:GetRandomVec2() self:F( self.ZoneName ) @@ -10350,23 +6374,23 @@ end -- Polygons ---- The ZONE_POLYGON_BASE class defined by an array of @{DCSTypes#Vec2}, forming a polygon. +--- The ZONE_POLYGON_BASE class defined by an array of @{Dcs.DCSTypes#Vec2}, forming a polygon. -- @type ZONE_POLYGON_BASE --- @field #ZONE_POLYGON_BASE.ListVec2 Polygon The polygon defined by an array of @{DCSTypes#Vec2}. --- @extends Zone#ZONE_BASE +-- @field #ZONE_POLYGON_BASE.ListVec2 Polygon The polygon defined by an array of @{Dcs.DCSTypes#Vec2}. +-- @extends Core.Zone#ZONE_BASE ZONE_POLYGON_BASE = { ClassName="ZONE_POLYGON_BASE", } --- A points array. -- @type ZONE_POLYGON_BASE.ListVec2 --- @list +-- @list ---- Constructor to create a ZONE_POLYGON_BASE instance, taking the zone name and an array of @{DCSTypes#Vec2}, forming a polygon. --- The @{Group#GROUP} waypoints define the polygon corners. The first and the last point are automatically connected. +--- Constructor to create a ZONE_POLYGON_BASE instance, taking the zone name and an array of @{Dcs.DCSTypes#Vec2}, forming a polygon. +-- The @{Wrapper.Group#GROUP} waypoints define the polygon corners. The first and the last point are automatically connected. -- @param #ZONE_POLYGON_BASE self -- @param #string ZoneName Name of the zone. --- @param #ZONE_POLYGON_BASE.ListVec2 PointsArray An array of @{DCSTypes#Vec2}, forming a polygon.. +-- @param #ZONE_POLYGON_BASE.ListVec2 PointsArray An array of @{Dcs.DCSTypes#Vec2}, forming a polygon.. -- @return #ZONE_POLYGON_BASE self function ZONE_POLYGON_BASE:New( ZoneName, PointsArray ) local self = BASE:Inherit( self, ZONE_BASE:New( ZoneName ) ) @@ -10399,7 +6423,7 @@ end --- Smokes the zone boundaries in a color. -- @param #ZONE_POLYGON_BASE self --- @param #POINT_VEC3.SmokeColor SmokeColor The smoke color. +-- @param Utilities.Utils#SMOKECOLOR SmokeColor The smoke color. -- @return #ZONE_POLYGON_BASE self function ZONE_POLYGON_BASE:SmokeZone( SmokeColor ) self:F2( SmokeColor ) @@ -10435,7 +6459,7 @@ end --- Returns if a location is within the zone. -- Source learned and taken from: https://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html -- @param #ZONE_POLYGON_BASE self --- @param DCSTypes#Vec2 Vec2 The location to test. +-- @param Dcs.DCSTypes#Vec2 Vec2 The location to test. -- @return #boolean true if the location is within the zone. function ZONE_POLYGON_BASE:IsPointVec2InZone( Vec2 ) self:F2( Vec2 ) @@ -10463,9 +6487,9 @@ function ZONE_POLYGON_BASE:IsPointVec2InZone( Vec2 ) return InPolygon end ---- Define a random @{DCSTypes#Vec2} within the zone. +--- Define a random @{Dcs.DCSTypes#Vec2} within the zone. -- @param #ZONE_POLYGON_BASE self --- @return DCSTypes#Vec2 The Vec2 coordinate. +-- @return Dcs.DCSTypes#Vec2 The Vec2 coordinate. function ZONE_POLYGON_BASE:GetRandomVec2() self:F2() @@ -10515,18 +6539,18 @@ end ---- The ZONE_POLYGON class defined by a sequence of @{Group#GROUP} waypoints within the Mission Editor, forming a polygon. +--- The ZONE_POLYGON class defined by a sequence of @{Wrapper.Group#GROUP} waypoints within the Mission Editor, forming a polygon. -- @type ZONE_POLYGON --- @extends Zone#ZONE_POLYGON_BASE +-- @extends Core.Zone#ZONE_POLYGON_BASE ZONE_POLYGON = { ClassName="ZONE_POLYGON", } ---- Constructor to create a ZONE_POLYGON instance, taking the zone name and the name of the @{Group#GROUP} defined within the Mission Editor. --- The @{Group#GROUP} waypoints define the polygon corners. The first and the last point are automatically connected by ZONE_POLYGON. +--- Constructor to create a ZONE_POLYGON instance, taking the zone name and the name of the @{Wrapper.Group#GROUP} defined within the Mission Editor. +-- The @{Wrapper.Group#GROUP} waypoints define the polygon corners. The first and the last point are automatically connected by ZONE_POLYGON. -- @param #ZONE_POLYGON self -- @param #string ZoneName Name of the zone. --- @param Group#GROUP ZoneGroup The GROUP waypoints as defined within the Mission Editor define the polygon shape. +-- @param Wrapper.Group#GROUP ZoneGroup The GROUP waypoints as defined within the Mission Editor define the polygon shape. -- @return #ZONE_POLYGON self function ZONE_POLYGON:New( ZoneName, ZoneGroup ) @@ -10538,672 +6562,11 @@ function ZONE_POLYGON:New( ZoneName, ZoneGroup ) return self end ---- This module contains the CLIENT class. --- --- 1) @{Client#CLIENT} class, extends @{Unit#UNIT} --- =============================================== --- Clients are those **Units** defined within the Mission Editor that have the skillset defined as __Client__ or __Player__. --- Note that clients are NOT the same as Units, they are NOT necessarily alive. --- The @{Client#CLIENT} class is a wrapper class to handle the DCS Unit objects that have the skillset defined as __Client__ or __Player__: --- --- * Wraps the DCS Unit objects with skill level set to Player or Client. --- * Support all DCS Unit APIs. --- * Enhance with Unit specific APIs not in the DCS Group API set. --- * When player joins Unit, execute alive init logic. --- * Handles messages to players. --- * Manage the "state" of the DCS Unit. --- --- Clients are being used by the @{MISSION} class to follow players and register their successes. --- --- 1.1) CLIENT reference methods --- ----------------------------- --- For each DCS Unit having skill level Player or Client, a CLIENT wrapper object (instance) will be created within the _@{DATABASE} object. --- This is done at the beginning of the mission (when the mission starts). --- --- The CLIENT class does not contain a :New() method, rather it provides :Find() methods to retrieve the object reference --- using the DCS Unit or the DCS UnitName. --- --- Another thing to know is that CLIENT objects do not "contain" the DCS Unit object. --- The CLIENT methods will reference the DCS Unit object by name when it is needed during API execution. --- If the DCS Unit object does not exist or is nil, the CLIENT methods will return nil and log an exception in the DCS.log file. --- --- The CLIENT class provides the following functions to retrieve quickly the relevant CLIENT instance: --- --- * @{#CLIENT.Find}(): Find a CLIENT instance from the _DATABASE object using a DCS Unit object. --- * @{#CLIENT.FindByName}(): Find a CLIENT instance from the _DATABASE object using a DCS Unit name. --- --- IMPORTANT: ONE SHOULD NEVER SANATIZE these CLIENT OBJECT REFERENCES! (make the CLIENT object references nil). --- --- @module Client --- @author FlightControl - ---- The CLIENT class --- @type CLIENT --- @extends Unit#UNIT -CLIENT = { - ONBOARDSIDE = { - NONE = 0, - LEFT = 1, - RIGHT = 2, - BACK = 3, - FRONT = 4 - }, - ClassName = "CLIENT", - ClientName = nil, - ClientAlive = false, - ClientTransport = false, - ClientBriefingShown = false, - _Menus = {}, - _Tasks = {}, - Messages = { - } -} - - ---- Finds a CLIENT from the _DATABASE using the relevant DCS Unit. --- @param #CLIENT self --- @param #string ClientName Name of the DCS **Unit** as defined within the Mission Editor. --- @param #string ClientBriefing Text that describes the briefing of the mission when a Player logs into the Client. --- @return #CLIENT --- @usage --- -- Create new Clients. --- local Mission = MISSIONSCHEDULER.AddMission( 'Russia Transport Troops SA-6', 'Operational', 'Transport troops from the control center to one of the SA-6 SAM sites to activate their operation.', 'Russia' ) --- Mission:AddGoal( DeploySA6TroopsGoal ) --- --- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*HOT-Deploy Troops 1' ):Transport() ) --- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*RAMP-Deploy Troops 3' ):Transport() ) --- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*HOT-Deploy Troops 2' ):Transport() ) --- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*RAMP-Deploy Troops 4' ):Transport() ) -function CLIENT:Find( DCSUnit ) - local ClientName = DCSUnit:getName() - local ClientFound = _DATABASE:FindClient( ClientName ) - - if ClientFound then - ClientFound:F( ClientName ) - return ClientFound - end - - error( "CLIENT not found for: " .. ClientName ) -end - - ---- Finds a CLIENT from the _DATABASE using the relevant Client Unit Name. --- As an optional parameter, a briefing text can be given also. --- @param #CLIENT self --- @param #string ClientName Name of the DCS **Unit** as defined within the Mission Editor. --- @param #string ClientBriefing Text that describes the briefing of the mission when a Player logs into the Client. --- @return #CLIENT --- @usage --- -- Create new Clients. --- local Mission = MISSIONSCHEDULER.AddMission( 'Russia Transport Troops SA-6', 'Operational', 'Transport troops from the control center to one of the SA-6 SAM sites to activate their operation.', 'Russia' ) --- Mission:AddGoal( DeploySA6TroopsGoal ) --- --- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*HOT-Deploy Troops 1' ):Transport() ) --- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*RAMP-Deploy Troops 3' ):Transport() ) --- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*HOT-Deploy Troops 2' ):Transport() ) --- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*RAMP-Deploy Troops 4' ):Transport() ) -function CLIENT:FindByName( ClientName, ClientBriefing ) - local ClientFound = _DATABASE:FindClient( ClientName ) - - if ClientFound then - ClientFound:F( { ClientName, ClientBriefing } ) - ClientFound:AddBriefing( ClientBriefing ) - ClientFound.MessageSwitch = true - - return ClientFound - end - - error( "CLIENT not found for: " .. ClientName ) -end - -function CLIENT:Register( ClientName ) - local self = BASE:Inherit( self, UNIT:Register( ClientName ) ) - - self:F( ClientName ) - self.ClientName = ClientName - self.MessageSwitch = true - self.ClientAlive2 = false - - --self.AliveCheckScheduler = routines.scheduleFunction( self._AliveCheckScheduler, { self }, timer.getTime() + 1, 5 ) - self.AliveCheckScheduler = SCHEDULER:New( self, self._AliveCheckScheduler, { "Client Alive " .. ClientName }, 1, 5 ) - - self:E( self ) - return self -end - - ---- Transport defines that the Client is a Transport. Transports show cargo. --- @param #CLIENT self --- @return #CLIENT -function CLIENT:Transport() - self:F() - - self.ClientTransport = true - return self -end - ---- AddBriefing adds a briefing to a CLIENT when a player joins a mission. --- @param #CLIENT self --- @param #string ClientBriefing is the text defining the Mission briefing. --- @return #CLIENT self -function CLIENT:AddBriefing( ClientBriefing ) - self:F( ClientBriefing ) - self.ClientBriefing = ClientBriefing - self.ClientBriefingShown = false - - return self -end - ---- Show the briefing of a CLIENT. --- @param #CLIENT self --- @return #CLIENT self -function CLIENT:ShowBriefing() - self:F( { self.ClientName, self.ClientBriefingShown } ) - - if not self.ClientBriefingShown then - self.ClientBriefingShown = true - local Briefing = "" - if self.ClientBriefing then - Briefing = Briefing .. self.ClientBriefing - end - Briefing = Briefing .. " Press [LEFT ALT]+[B] to view the complete mission briefing." - self:Message( Briefing, 60, "Briefing" ) - end - - return self -end - ---- Show the mission briefing of a MISSION to the CLIENT. --- @param #CLIENT self --- @param #string MissionBriefing --- @return #CLIENT self -function CLIENT:ShowMissionBriefing( MissionBriefing ) - self:F( { self.ClientName } ) - - if MissionBriefing then - self:Message( MissionBriefing, 60, "Mission Briefing" ) - end - - return self -end - - - ---- Resets a CLIENT. --- @param #CLIENT self --- @param #string ClientName Name of the Group as defined within the Mission Editor. The Group must have a Unit with the type Client. -function CLIENT:Reset( ClientName ) - self:F() - self._Menus = {} -end - --- Is Functions - ---- Checks if the CLIENT is a multi-seated UNIT. --- @param #CLIENT self --- @return #boolean true if multi-seated. -function CLIENT:IsMultiSeated() - self:F( self.ClientName ) - - local ClientMultiSeatedTypes = { - ["Mi-8MT"] = "Mi-8MT", - ["UH-1H"] = "UH-1H", - ["P-51B"] = "P-51B" - } - - if self:IsAlive() then - local ClientTypeName = self:GetClientGroupUnit():GetTypeName() - if ClientMultiSeatedTypes[ClientTypeName] then - return true - end - end - - return false -end - ---- Checks for a client alive event and calls a function on a continuous basis. --- @param #CLIENT self --- @param #function CallBack Function. --- @return #CLIENT -function CLIENT:Alive( CallBackFunction, ... ) - self:F() - - self.ClientCallBack = CallBackFunction - self.ClientParameters = arg - - return self -end - ---- @param #CLIENT self -function CLIENT:_AliveCheckScheduler( SchedulerName ) - self:F( { SchedulerName, self.ClientName, self.ClientAlive2, self.ClientBriefingShown, self.ClientCallBack } ) - - if self:IsAlive() then - if self.ClientAlive2 == false then - self:ShowBriefing() - if self.ClientCallBack then - self:T("Calling Callback function") - self.ClientCallBack( self, unpack( self.ClientParameters ) ) - end - self.ClientAlive2 = true - end - else - if self.ClientAlive2 == true then - self.ClientAlive2 = false - end - end - - return true -end - ---- Return the DCSGroup of a Client. --- This function is modified to deal with a couple of bugs in DCS 1.5.3 --- @param #CLIENT self --- @return DCSGroup#Group -function CLIENT:GetDCSGroup() - self:F3() - --- local ClientData = Group.getByName( self.ClientName ) --- if ClientData and ClientData:isExist() then --- self:T( self.ClientName .. " : group found!" ) --- return ClientData --- else --- return nil --- end - - local ClientUnit = Unit.getByName( self.ClientName ) - - local CoalitionsData = { AlivePlayersRed = coalition.getPlayers( coalition.side.RED ), AlivePlayersBlue = coalition.getPlayers( coalition.side.BLUE ) } - for CoalitionId, CoalitionData in pairs( CoalitionsData ) do - self:T3( { "CoalitionData:", CoalitionData } ) - for UnitId, UnitData in pairs( CoalitionData ) do - self:T3( { "UnitData:", UnitData } ) - if UnitData and UnitData:isExist() then - - --self:E(self.ClientName) - if ClientUnit then - local ClientGroup = ClientUnit:getGroup() - if ClientGroup then - self:T3( "ClientGroup = " .. self.ClientName ) - if ClientGroup:isExist() and UnitData:getGroup():isExist() then - if ClientGroup:getID() == UnitData:getGroup():getID() then - self:T3( "Normal logic" ) - self:T3( self.ClientName .. " : group found!" ) - self.ClientGroupID = ClientGroup:getID() - self.ClientGroupName = ClientGroup:getName() - return ClientGroup - end - else - -- Now we need to resolve the bugs in DCS 1.5 ... - -- Consult the database for the units of the Client Group. (ClientGroup:getUnits() returns nil) - self:T3( "Bug 1.5 logic" ) - local ClientGroupTemplate = _DATABASE.Templates.Units[self.ClientName].GroupTemplate - self.ClientGroupID = ClientGroupTemplate.groupId - self.ClientGroupName = _DATABASE.Templates.Units[self.ClientName].GroupName - self:T3( self.ClientName .. " : group found in bug 1.5 resolvement logic!" ) - return ClientGroup - end - -- else - -- error( "Client " .. self.ClientName .. " not found!" ) - end - else - --self:E( { "Client not found!", self.ClientName } ) - end - end - end - end - - -- For non player clients - if ClientUnit then - local ClientGroup = ClientUnit:getGroup() - if ClientGroup then - self:T3( "ClientGroup = " .. self.ClientName ) - if ClientGroup:isExist() then - self:T3( "Normal logic" ) - self:T3( self.ClientName .. " : group found!" ) - return ClientGroup - end - end - end - - self.ClientGroupID = nil - self.ClientGroupUnit = nil - - return nil -end - - --- TODO: Check DCSTypes#Group.ID ---- Get the group ID of the client. --- @param #CLIENT self --- @return DCSTypes#Group.ID -function CLIENT:GetClientGroupID() - - local ClientGroup = self:GetDCSGroup() - - --self:E( self.ClientGroupID ) -- Determined in GetDCSGroup() - return self.ClientGroupID -end - - ---- Get the name of the group of the client. --- @param #CLIENT self --- @return #string -function CLIENT:GetClientGroupName() - - local ClientGroup = self:GetDCSGroup() - - self:T( self.ClientGroupName ) -- Determined in GetDCSGroup() - return self.ClientGroupName -end - ---- Returns the UNIT of the CLIENT. --- @param #CLIENT self --- @return Unit#UNIT -function CLIENT:GetClientGroupUnit() - self:F2() - - local ClientDCSUnit = Unit.getByName( self.ClientName ) - - self:T( self.ClientDCSUnit ) - if ClientDCSUnit and ClientDCSUnit:isExist() then - local ClientUnit = _DATABASE:FindUnit( self.ClientName ) - self:T2( ClientUnit ) - return ClientUnit - end -end - ---- Returns the DCSUnit of the CLIENT. --- @param #CLIENT self --- @return DCSTypes#Unit -function CLIENT:GetClientGroupDCSUnit() - self:F2() - - local ClientDCSUnit = Unit.getByName( self.ClientName ) - - if ClientDCSUnit and ClientDCSUnit:isExist() then - self:T2( ClientDCSUnit ) - return ClientDCSUnit - end -end - - ---- Evaluates if the CLIENT is a transport. --- @param #CLIENT self --- @return #boolean true is a transport. -function CLIENT:IsTransport() - self:F() - return self.ClientTransport -end - ---- Shows the @{Cargo#CARGO} contained within the CLIENT to the player as a message. --- The @{Cargo#CARGO} is shown using the @{Message#MESSAGE} distribution system. --- @param #CLIENT self -function CLIENT:ShowCargo() - self:F() - - local CargoMsg = "" - - for CargoName, Cargo in pairs( CARGOS ) do - if self == Cargo:IsLoadedInClient() then - CargoMsg = CargoMsg .. Cargo.CargoName .. " Type:" .. Cargo.CargoType .. " Weight: " .. Cargo.CargoWeight .. "\n" - end - end - - if CargoMsg == "" then - CargoMsg = "empty" - end - - self:Message( CargoMsg, 15, "Co-Pilot: Cargo Status", 30 ) - -end - --- TODO (1) I urgently need to revise this. ---- A local function called by the DCS World Menu system to switch off messages. -function CLIENT.SwitchMessages( PrmTable ) - PrmTable[1].MessageSwitch = PrmTable[2] -end - ---- The main message driver for the CLIENT. --- This function displays various messages to the Player logged into the CLIENT through the DCS World Messaging system. --- @param #CLIENT self --- @param #string Message is the text describing the message. --- @param #number MessageDuration is the duration in seconds that the Message should be displayed. --- @param #string MessageCategory is the category of the message (the title). --- @param #number MessageInterval is the interval in seconds between the display of the @{Message#MESSAGE} when the CLIENT is in the air. --- @param #string MessageID is the identifier of the message when displayed with intervals. -function CLIENT:Message( Message, MessageDuration, MessageCategory, MessageInterval, MessageID ) - self:F( { Message, MessageDuration, MessageCategory, MessageInterval } ) - - if self.MessageSwitch == true then - if MessageCategory == nil then - MessageCategory = "Messages" - end - if MessageID ~= nil then - if self.Messages[MessageID] == nil then - self.Messages[MessageID] = {} - self.Messages[MessageID].MessageId = MessageID - self.Messages[MessageID].MessageTime = timer.getTime() - self.Messages[MessageID].MessageDuration = MessageDuration - if MessageInterval == nil then - self.Messages[MessageID].MessageInterval = 600 - else - self.Messages[MessageID].MessageInterval = MessageInterval - end - MESSAGE:New( Message, MessageDuration, MessageCategory ):ToClient( self ) - else - if self:GetClientGroupDCSUnit() and not self:GetClientGroupDCSUnit():inAir() then - if timer.getTime() - self.Messages[MessageID].MessageTime >= self.Messages[MessageID].MessageDuration + 10 then - MESSAGE:New( Message, MessageDuration , MessageCategory):ToClient( self ) - self.Messages[MessageID].MessageTime = timer.getTime() - end - else - if timer.getTime() - self.Messages[MessageID].MessageTime >= self.Messages[MessageID].MessageDuration + self.Messages[MessageID].MessageInterval then - MESSAGE:New( Message, MessageDuration, MessageCategory ):ToClient( self ) - self.Messages[MessageID].MessageTime = timer.getTime() - end - end - end - else - MESSAGE:New( Message, MessageDuration, MessageCategory ):ToClient( self ) - end - end -end ---- This module contains the STATIC class. --- --- 1) @{Static#STATIC} class, extends @{Positionable#POSITIONABLE} --- =============================================================== --- Statics are **Static Units** defined within the Mission Editor. --- Note that Statics are almost the same as Units, but they don't have a controller. --- The @{Static#STATIC} class is a wrapper class to handle the DCS Static objects: --- --- * Wraps the DCS Static objects. --- * Support all DCS Static APIs. --- * Enhance with Static specific APIs not in the DCS API set. --- --- 1.1) STATIC reference methods --- ----------------------------- --- For each DCS Static will have a STATIC wrapper object (instance) within the _@{DATABASE} object. --- This is done at the beginning of the mission (when the mission starts). --- --- The STATIC class does not contain a :New() method, rather it provides :Find() methods to retrieve the object reference --- using the Static Name. --- --- Another thing to know is that STATIC objects do not "contain" the DCS Static object. --- The STATIc methods will reference the DCS Static object by name when it is needed during API execution. --- If the DCS Static object does not exist or is nil, the STATIC methods will return nil and log an exception in the DCS.log file. --- --- The STATIc class provides the following functions to retrieve quickly the relevant STATIC instance: --- --- * @{#STATIC.FindByName}(): Find a STATIC instance from the _DATABASE object using a DCS Static name. --- --- IMPORTANT: ONE SHOULD NEVER SANATIZE these STATIC OBJECT REFERENCES! (make the STATIC object references nil). --- --- @module Static --- @author FlightControl - - - - - - ---- The STATIC class --- @type STATIC --- @extends Positionable#POSITIONABLE -STATIC = { - ClassName = "STATIC", -} - - ---- Finds a STATIC from the _DATABASE using the relevant Static Name. --- As an optional parameter, a briefing text can be given also. --- @param #STATIC self --- @param #string StaticName Name of the DCS **Static** as defined within the Mission Editor. --- @return #STATIC -function STATIC:FindByName( StaticName ) - local StaticFound = _DATABASE:FindStatic( StaticName ) - - self.StaticName = StaticName - - if StaticFound then - StaticFound:F( { StaticName } ) - - return StaticFound - end - - error( "STATIC not found for: " .. StaticName ) -end - -function STATIC:Register( StaticName ) - local self = BASE:Inherit( self, POSITIONABLE:New( StaticName ) ) - self.StaticName = StaticName - return self -end - - -function STATIC:GetDCSObject() - local DCSStatic = StaticObject.getByName( self.StaticName ) - - if DCSStatic then - return DCSStatic - end - - return nil -end ---- This module contains the AIRBASE classes. --- --- === --- --- 1) @{Airbase#AIRBASE} class, extends @{Positionable#POSITIONABLE} --- ================================================================= --- The @{AIRBASE} class is a wrapper class to handle the DCS Airbase objects: --- --- * Support all DCS Airbase APIs. --- * Enhance with Airbase specific APIs not in the DCS Airbase API set. --- --- --- 1.1) AIRBASE reference methods --- ------------------------------ --- For each DCS Airbase object alive within a running mission, a AIRBASE wrapper object (instance) will be created within the _@{DATABASE} object. --- This is done at the beginning of the mission (when the mission starts). --- --- The AIRBASE class **does not contain a :New()** method, rather it provides **:Find()** methods to retrieve the object reference --- using the DCS Airbase or the DCS AirbaseName. --- --- Another thing to know is that AIRBASE objects do not "contain" the DCS Airbase object. --- The AIRBASE methods will reference the DCS Airbase object by name when it is needed during API execution. --- If the DCS Airbase object does not exist or is nil, the AIRBASE methods will return nil and log an exception in the DCS.log file. --- --- The AIRBASE class provides the following functions to retrieve quickly the relevant AIRBASE instance: --- --- * @{#AIRBASE.Find}(): Find a AIRBASE instance from the _DATABASE object using a DCS Airbase object. --- * @{#AIRBASE.FindByName}(): Find a AIRBASE instance from the _DATABASE object using a DCS Airbase name. --- --- IMPORTANT: ONE SHOULD NEVER SANATIZE these AIRBASE OBJECT REFERENCES! (make the AIRBASE object references nil). --- --- 1.2) DCS AIRBASE APIs --- --------------------- --- The DCS Airbase APIs are used extensively within MOOSE. The AIRBASE class has for each DCS Airbase API a corresponding method. --- To be able to distinguish easily in your code the difference between a AIRBASE API call and a DCS Airbase API call, --- the first letter of the method is also capitalized. So, by example, the DCS Airbase method @{DCSAirbase#Airbase.getName}() --- is implemented in the AIRBASE class as @{#AIRBASE.GetName}(). --- --- More functions will be added --- ---------------------------- --- During the MOOSE development, more functions will be added. --- --- @module Airbase --- @author FlightControl - - - - - ---- The AIRBASE class --- @type AIRBASE --- @extends Positionable#POSITIONABLE -AIRBASE = { - ClassName="AIRBASE", - CategoryName = { - [Airbase.Category.AIRDROME] = "Airdrome", - [Airbase.Category.HELIPAD] = "Helipad", - [Airbase.Category.SHIP] = "Ship", - }, - } - --- Registration. - ---- Create a new AIRBASE from DCSAirbase. --- @param #AIRBASE self --- @param #string AirbaseName The name of the airbase. --- @return Airbase#AIRBASE -function AIRBASE:Register( AirbaseName ) - - local self = BASE:Inherit( self, POSITIONABLE:New( AirbaseName ) ) - self.AirbaseName = AirbaseName - return self -end - --- Reference methods. - ---- Finds a AIRBASE from the _DATABASE using a DCSAirbase object. --- @param #AIRBASE self --- @param DCSAirbase#Airbase DCSAirbase An existing DCS Airbase object reference. --- @return Airbase#AIRBASE self -function AIRBASE:Find( DCSAirbase ) - - local AirbaseName = DCSAirbase:getName() - local AirbaseFound = _DATABASE:FindAirbase( AirbaseName ) - return AirbaseFound -end - ---- Find a AIRBASE in the _DATABASE using the name of an existing DCS Airbase. --- @param #AIRBASE self --- @param #string AirbaseName The Airbase Name. --- @return Airbase#AIRBASE self -function AIRBASE:FindByName( AirbaseName ) - - local AirbaseFound = _DATABASE:FindAirbase( AirbaseName ) - return AirbaseFound -end - -function AIRBASE:GetDCSObject() - local DCSAirbase = Airbase.getByName( self.AirbaseName ) - - if DCSAirbase then - return DCSAirbase - end - - return nil -end - - - --- This module contains the DATABASE class, managing the database of mission objects. -- -- ==== -- --- 1) @{Database#DATABASE} class, extends @{Base#BASE} +-- 1) @{Core.Database#DATABASE} class, extends @{Core.Base#BASE} -- =================================================== -- Mission designers can use the DATABASE class to refer to: -- @@ -11239,7 +6602,7 @@ end --- DATABASE class -- @type DATABASE --- @extends Base#BASE +-- @extends Core.Base#BASE DATABASE = { ClassName = "DATABASE", Templates = { @@ -11307,7 +6670,7 @@ end --- Finds a Unit based on the Unit Name. -- @param #DATABASE self -- @param #string UnitName --- @return Unit#UNIT The found Unit. +-- @return Wrapper.Unit#UNIT The found Unit. function DATABASE:FindUnit( UnitName ) local UnitFound = self.UNITS[UnitName] @@ -11355,7 +6718,7 @@ end --- Finds a STATIC based on the StaticName. -- @param #DATABASE self -- @param #string StaticName --- @return Static#STATIC The found STATIC. +-- @return Wrapper.Static#STATIC The found STATIC. function DATABASE:FindStatic( StaticName ) local StaticFound = self.STATICS[StaticName] @@ -11382,7 +6745,7 @@ end --- Finds a AIRBASE based on the AirbaseName. -- @param #DATABASE self -- @param #string AirbaseName --- @return Airbase#AIRBASE The found AIRBASE. +-- @return Wrapper.Airbase#AIRBASE The found AIRBASE. function DATABASE:FindAirbase( AirbaseName ) local AirbaseFound = self.AIRBASES[AirbaseName] @@ -11393,7 +6756,7 @@ end --- Finds a CLIENT based on the ClientName. -- @param #DATABASE self -- @param #string ClientName --- @return Client#CLIENT The found CLIENT. +-- @return Wrapper.Client#CLIENT The found CLIENT. function DATABASE:FindClient( ClientName ) local ClientFound = self.CLIENTS[ClientName] @@ -11416,7 +6779,7 @@ end --- Finds a GROUP based on the GroupName. -- @param #DATABASE self -- @param #string GroupName --- @return Group#GROUP The found GROUP. +-- @return Wrapper.Group#GROUP The found GROUP. function DATABASE:FindGroup( GroupName ) local GroupFound = self.GROUPS[GroupName] @@ -11736,7 +7099,7 @@ end --- Handles the OnBirth event for the alive units set. -- @param #DATABASE self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event function DATABASE:_EventOnBirth( Event ) self:F2( { Event } ) @@ -11750,7 +7113,7 @@ end --- Handles the OnDead or OnCrash event for alive units set. -- @param #DATABASE self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event function DATABASE:_EventOnDeadOrCrash( Event ) self:F2( { Event } ) @@ -11765,7 +7128,7 @@ end --- Handles the OnPlayerEnterUnit event to fill the active players table (with the unit filter applied). -- @param #DATABASE self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event function DATABASE:_EventOnPlayerEnterUnit( Event ) self:F2( { Event } ) @@ -11780,7 +7143,7 @@ end --- Handles the OnPlayerLeaveUnit event to clean the active players table. -- @param #DATABASE self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event function DATABASE:_EventOnPlayerLeaveUnit( Event ) self:F2( { Event } ) @@ -11982,9 +7345,9 @@ end -- -- === -- --- 1) @{Set#SET_BASE} class, extends @{Base#BASE} +-- 1) @{Core.Set#SET_BASE} class, extends @{Core.Base#BASE} -- ============================================== --- The @{Set#SET_BASE} class defines the core functions that define a collection of objects. +-- The @{Core.Set#SET_BASE} class defines the core functions that define a collection of objects. -- A SET provides iterators to iterate the SET, but will **temporarily** yield the ForEach interator loop at defined **"intervals"** to the mail simulator loop. -- In this way, large loops can be done while not blocking the simulator main processing loop. -- The default **"yield interval"** is after 10 objects processed. @@ -11992,18 +7355,18 @@ end -- -- 1.1) Add or remove objects from the SET -- --------------------------------------- --- Some key core functions are @{Set#SET_BASE.Add} and @{Set#SET_BASE.Remove} to add or remove objects from the SET in your logic. +-- Some key core functions are @{Core.Set#SET_BASE.Add} and @{Core.Set#SET_BASE.Remove} to add or remove objects from the SET in your logic. -- -- 1.2) Define the SET iterator **"yield interval"** and the **"time interval"** -- ----------------------------------------------------------------------------- --- Modify the iterator intervals with the @{Set#SET_BASE.SetInteratorIntervals} method. +-- Modify the iterator intervals with the @{Core.Set#SET_BASE.SetInteratorIntervals} method. -- You can set the **"yield interval"**, and the **"time interval"**. (See above). -- -- === -- --- 2) @{Set#SET_GROUP} class, extends @{Set#SET_BASE} +-- 2) @{Core.Set#SET_GROUP} class, extends @{Core.Set#SET_BASE} -- ================================================== --- Mission designers can use the @{Set#SET_GROUP} class to build sets of groups belonging to certain: +-- Mission designers can use the @{Core.Set#SET_GROUP} class to build sets of groups belonging to certain: -- -- * Coalitions -- * Categories @@ -12018,7 +7381,7 @@ end -- -- 2.2) Add or Remove GROUP(s) from SET_GROUP: -- ------------------------------------------- --- GROUPS can be added and removed using the @{Set#SET_GROUP.AddGroupsByName} and @{Set#SET_GROUP.RemoveGroupsByName} respectively. +-- GROUPS can be added and removed using the @{Core.Set#SET_GROUP.AddGroupsByName} and @{Core.Set#SET_GROUP.RemoveGroupsByName} respectively. -- These methods take a single GROUP name or an array of GROUP names to be added or removed from SET_GROUP. -- -- 2.3) SET_GROUP filter criteria: @@ -12037,7 +7400,7 @@ end -- -- Planned filter criteria within development are (so these are not yet available): -- --- * @{#SET_GROUP.FilterZones}: Builds the SET_GROUP with the groups within a @{Zone#ZONE}. +-- * @{#SET_GROUP.FilterZones}: Builds the SET_GROUP with the groups within a @{Core.Zone#ZONE}. -- -- 2.4) SET_GROUP iterators: -- ------------------------- @@ -12052,9 +7415,9 @@ end -- -- ==== -- --- 3) @{Set#SET_UNIT} class, extends @{Set#SET_BASE} +-- 3) @{Core.Set#SET_UNIT} class, extends @{Core.Set#SET_BASE} -- =================================================== --- Mission designers can use the @{Set#SET_UNIT} class to build sets of units belonging to certain: +-- Mission designers can use the @{Core.Set#SET_UNIT} class to build sets of units belonging to certain: -- -- * Coalitions -- * Categories @@ -12070,7 +7433,7 @@ end -- -- 3.2) Add or Remove UNIT(s) from SET_UNIT: -- ----------------------------------------- --- UNITs can be added and removed using the @{Set#SET_UNIT.AddUnitsByName} and @{Set#SET_UNIT.RemoveUnitsByName} respectively. +-- UNITs can be added and removed using the @{Core.Set#SET_UNIT.AddUnitsByName} and @{Core.Set#SET_UNIT.RemoveUnitsByName} respectively. -- These methods take a single UNIT name or an array of UNIT names to be added or removed from SET_UNIT. -- -- 3.3) SET_UNIT filter criteria: @@ -12090,7 +7453,7 @@ end -- -- Planned filter criteria within development are (so these are not yet available): -- --- * @{#SET_UNIT.FilterZones}: Builds the SET_UNIT with the units within a @{Zone#ZONE}. +-- * @{#SET_UNIT.FilterZones}: Builds the SET_UNIT with the units within a @{Core.Zone#ZONE}. -- -- 3.4) SET_UNIT iterators: -- ------------------------ @@ -12110,9 +7473,9 @@ end -- -- === -- --- 4) @{Set#SET_CLIENT} class, extends @{Set#SET_BASE} +-- 4) @{Core.Set#SET_CLIENT} class, extends @{Core.Set#SET_BASE} -- =================================================== --- Mission designers can use the @{Set#SET_CLIENT} class to build sets of units belonging to certain: +-- Mission designers can use the @{Core.Set#SET_CLIENT} class to build sets of units belonging to certain: -- -- * Coalitions -- * Categories @@ -12128,7 +7491,7 @@ end -- -- 4.2) Add or Remove CLIENT(s) from SET_CLIENT: -- ----------------------------------------- --- CLIENTs can be added and removed using the @{Set#SET_CLIENT.AddClientsByName} and @{Set#SET_CLIENT.RemoveClientsByName} respectively. +-- CLIENTs can be added and removed using the @{Core.Set#SET_CLIENT.AddClientsByName} and @{Core.Set#SET_CLIENT.RemoveClientsByName} respectively. -- These methods take a single CLIENT name or an array of CLIENT names to be added or removed from SET_CLIENT. -- -- 4.3) SET_CLIENT filter criteria: @@ -12148,7 +7511,7 @@ end -- -- Planned filter criteria within development are (so these are not yet available): -- --- * @{#SET_CLIENT.FilterZones}: Builds the SET_CLIENT with the clients within a @{Zone#ZONE}. +-- * @{#SET_CLIENT.FilterZones}: Builds the SET_CLIENT with the clients within a @{Core.Zone#ZONE}. -- -- 4.4) SET_CLIENT iterators: -- ------------------------ @@ -12160,9 +7523,9 @@ end -- -- ==== -- --- 5) @{Set#SET_AIRBASE} class, extends @{Set#SET_BASE} +-- 5) @{Core.Set#SET_AIRBASE} class, extends @{Core.Set#SET_BASE} -- ==================================================== --- Mission designers can use the @{Set#SET_AIRBASE} class to build sets of airbases optionally belonging to certain: +-- Mission designers can use the @{Core.Set#SET_AIRBASE} class to build sets of airbases optionally belonging to certain: -- -- * Coalitions -- @@ -12174,7 +7537,7 @@ end -- -- 5.2) Add or Remove AIRBASEs from SET_AIRBASE -- -------------------------------------------- --- AIRBASEs can be added and removed using the @{Set#SET_AIRBASE.AddAirbasesByName} and @{Set#SET_AIRBASE.RemoveAirbasesByName} respectively. +-- AIRBASEs can be added and removed using the @{Core.Set#SET_AIRBASE.AddAirbasesByName} and @{Core.Set#SET_AIRBASE.RemoveAirbasesByName} respectively. -- These methods take a single AIRBASE name or an array of AIRBASE names to be added or removed from SET_AIRBASE. -- -- 5.3) SET_AIRBASE filter criteria @@ -12198,13 +7561,12 @@ end -- -- ==== -- --- ### Contributions: --- --- * Mechanist : Concept & Testing --- -- ### Authors: -- -- * FlightControl : Design & Programming +-- +-- ### Contributions: +-- -- -- @module Set @@ -12214,7 +7576,7 @@ end -- @field #table Filter -- @field #table Set -- @field #table List --- @extends Base#BASE +-- @extends Core.Base#BASE SET_BASE = { ClassName = "SET_BASE", Filter = {}, @@ -12245,10 +7607,10 @@ function SET_BASE:New( Database ) return self end ---- Finds an @{Base#BASE} object based on the object Name. +--- Finds an @{Core.Base#BASE} object based on the object Name. -- @param #SET_BASE self -- @param #string ObjectName --- @return Base#BASE The Object found. +-- @return Core.Base#BASE The Object found. function SET_BASE:_Find( ObjectName ) local ObjectFound = self.Set[ObjectName] @@ -12265,11 +7627,11 @@ function SET_BASE:GetSet() return self.Set end ---- Adds a @{Base#BASE} object in the @{Set#SET_BASE}, using the Object Name as the index. +--- Adds a @{Core.Base#BASE} object in the @{Core.Set#SET_BASE}, using a given ObjectName as the index. -- @param #SET_BASE self -- @param #string ObjectName --- @param Base#BASE Object --- @return Base#BASE The added BASE Object. +-- @param Core.Base#BASE Object +-- @return Core.Base#BASE The added BASE Object. function SET_BASE:Add( ObjectName, Object ) self:F2( ObjectName ) @@ -12291,7 +7653,22 @@ function SET_BASE:Add( ObjectName, Object ) end ---- Removes a @{Base#BASE} object from the @{Set#SET_BASE} and derived classes, based on the Object Name. +--- Adds a @{Core.Base#BASE} object in the @{Core.Set#SET_BASE}, using the Object Name as the index. +-- @param #SET_BASE self +-- @param Wrapper.Object#OBJECT Object +-- @return Core.Base#BASE The added BASE Object. +function SET_BASE:AddObject( Object ) + self:F2( Object.ObjectName ) + + self:T( Object.UnitName ) + self:T( Object.ObjectName ) + self:Add( Object.ObjectName, Object ) + +end + + + +--- Removes a @{Core.Base#BASE} object from the @{Core.Set#SET_BASE} and derived classes, based on the Object Name. -- @param #SET_BASE self -- @param #string ObjectName function SET_BASE:Remove( ObjectName ) @@ -12330,7 +7707,22 @@ function SET_BASE:Remove( ObjectName ) end ---- Retrieves the amount of objects in the @{Set#SET_BASE} and derived classes. +--- Gets a @{Core.Base#BASE} object from the @{Core.Set#SET_BASE} and derived classes, based on the Object Name. +-- @param #SET_BASE self +-- @param #string ObjectName +-- @return Core.Base#BASE +function SET_BASE:Get( ObjectName ) + self:F( ObjectName ) + + local t = self.Set[ObjectName] + + self:T3( { ObjectName, t } ) + + return t + +end + +--- Retrieves the amount of objects in the @{Core.Set#SET_BASE} and derived classes. -- @param #SET_BASE self -- @return #number Count function SET_BASE:Count() @@ -12423,10 +7815,10 @@ function SET_BASE:FilterStop() return self end ---- Iterate the SET_BASE while identifying the nearest object from a @{Point#POINT_VEC2}. +--- Iterate the SET_BASE while identifying the nearest object from a @{Core.Point#POINT_VEC2}. -- @param #SET_BASE self --- @param Point#POINT_VEC2 PointVec2 A @{Point#POINT_VEC2} object from where to evaluate the closest object in the set. --- @return Base#BASE The closest object. +-- @param Core.Point#POINT_VEC2 PointVec2 A @{Core.Point#POINT_VEC2} object from where to evaluate the closest object in the set. +-- @return Core.Base#BASE The closest object. function SET_BASE:FindNearestObjectFromPointVec2( PointVec2 ) self:F2( PointVec2 ) @@ -12477,7 +7869,7 @@ end --- Handles the OnBirth event for the Set. -- @param #SET_BASE self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event function SET_BASE:_EventOnBirth( Event ) self:F3( { Event } ) @@ -12493,7 +7885,7 @@ end --- Handles the OnDead or OnCrash event for alive units set. -- @param #SET_BASE self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event function SET_BASE:_EventOnDeadOrCrash( Event ) self:F3( { Event } ) @@ -12507,7 +7899,7 @@ end --- Handles the OnPlayerEnterUnit event to fill the active players table (with the unit filter applied). -- @param #SET_BASE self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event function SET_BASE:_EventOnPlayerEnterUnit( Event ) self:F3( { Event } ) @@ -12523,7 +7915,7 @@ end --- Handles the OnPlayerLeaveUnit event to clean the active players table. -- @param #SET_BASE self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event function SET_BASE:_EventOnPlayerLeaveUnit( Event ) self:F3( { Event } ) @@ -12556,6 +7948,9 @@ end function SET_BASE:ForEach( IteratorFunction, arg, Set, Function, FunctionArguments ) self:F3( arg ) + Set = Set or self:GetSet() + arg = arg or {} + local function CoRoutine() local Count = 0 for ObjectID, ObjectData in pairs( Set ) do @@ -12668,7 +8063,7 @@ end --- SET_GROUP class -- @type SET_GROUP --- @extends Set#SET_BASE +-- @extends #SET_BASE SET_GROUP = { ClassName = "SET_GROUP", Filter = { @@ -12709,7 +8104,7 @@ function SET_GROUP:New() end --- Add GROUP(s) to SET_GROUP. --- @param Set#SET_GROUP self +-- @param Core.Set#SET_GROUP self -- @param #string AddGroupNames A single name or an array of GROUP names. -- @return self function SET_GROUP:AddGroupsByName( AddGroupNames ) @@ -12724,8 +8119,8 @@ function SET_GROUP:AddGroupsByName( AddGroupNames ) end --- Remove GROUP(s) from SET_GROUP. --- @param Set#SET_GROUP self --- @param Group#GROUP RemoveGroupNames A single name or an array of GROUP names. +-- @param Core.Set#SET_GROUP self +-- @param Wrapper.Group#GROUP RemoveGroupNames A single name or an array of GROUP names. -- @return self function SET_GROUP:RemoveGroupsByName( RemoveGroupNames ) @@ -12744,7 +8139,7 @@ end --- Finds a Group based on the Group Name. -- @param #SET_GROUP self -- @param #string GroupName --- @return Group#GROUP The found Group. +-- @return Wrapper.Group#GROUP The found Group. function SET_GROUP:FindGroup( GroupName ) local GroupFound = self.Set[GroupName] @@ -12845,7 +8240,7 @@ end --- Handles the Database to check on an event (birth) that the Object was added in the Database. -- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! -- @param #SET_GROUP self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event -- @return #string The name of the GROUP -- @return #table The GROUP function SET_GROUP:AddInDatabase( Event ) @@ -12862,7 +8257,7 @@ end --- Handles the Database to check on any event that Object exists in the Database. -- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa! -- @param #SET_GROUP self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event -- @return #string The name of the GROUP -- @return #table The GROUP function SET_GROUP:FindInDatabase( Event ) @@ -12885,15 +8280,15 @@ end --- Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence completely in a @{Zone}, providing the GROUP and optional parameters to the called function. -- @param #SET_GROUP self --- @param Zone#ZONE ZoneObject The Zone to be tested for. +-- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. -- @param #function IteratorFunction The function that will be called when there is an alive GROUP in the SET_GROUP. The function needs to accept a GROUP parameter. -- @return #SET_GROUP self function SET_GROUP:ForEachGroupCompletelyInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) self:ForEach( IteratorFunction, arg, self.Set, - --- @param Zone#ZONE_BASE ZoneObject - -- @param Group#GROUP GroupObject + --- @param Core.Zone#ZONE_BASE ZoneObject + -- @param Wrapper.Group#GROUP GroupObject function( ZoneObject, GroupObject ) if GroupObject:IsCompletelyInZone( ZoneObject ) then return true @@ -12907,15 +8302,15 @@ end --- Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence partly in a @{Zone}, providing the GROUP and optional parameters to the called function. -- @param #SET_GROUP self --- @param Zone#ZONE ZoneObject The Zone to be tested for. +-- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. -- @param #function IteratorFunction The function that will be called when there is an alive GROUP in the SET_GROUP. The function needs to accept a GROUP parameter. -- @return #SET_GROUP self function SET_GROUP:ForEachGroupPartlyInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) self:ForEach( IteratorFunction, arg, self.Set, - --- @param Zone#ZONE_BASE ZoneObject - -- @param Group#GROUP GroupObject + --- @param Core.Zone#ZONE_BASE ZoneObject + -- @param Wrapper.Group#GROUP GroupObject function( ZoneObject, GroupObject ) if GroupObject:IsPartlyInZone( ZoneObject ) then return true @@ -12929,15 +8324,15 @@ end --- Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence not in a @{Zone}, providing the GROUP and optional parameters to the called function. -- @param #SET_GROUP self --- @param Zone#ZONE ZoneObject The Zone to be tested for. +-- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. -- @param #function IteratorFunction The function that will be called when there is an alive GROUP in the SET_GROUP. The function needs to accept a GROUP parameter. -- @return #SET_GROUP self function SET_GROUP:ForEachGroupNotInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) self:ForEach( IteratorFunction, arg, self.Set, - --- @param Zone#ZONE_BASE ZoneObject - -- @param Group#GROUP GroupObject + --- @param Core.Zone#ZONE_BASE ZoneObject + -- @param Wrapper.Group#GROUP GroupObject function( ZoneObject, GroupObject ) if GroupObject:IsNotInZone( ZoneObject ) then return true @@ -12978,7 +8373,7 @@ end --- -- @param #SET_GROUP self --- @param Group#GROUP MooseGroup +-- @param Wrapper.Group#GROUP MooseGroup -- @return #SET_GROUP self function SET_GROUP:IsIncludeObject( MooseGroup ) self:F2( MooseGroup ) @@ -13034,7 +8429,7 @@ end --- SET_UNIT class -- @type SET_UNIT --- @extends Set#SET_BASE +-- @extends Core.Set#SET_BASE SET_UNIT = { ClassName = "SET_UNIT", Units = {}, @@ -13106,8 +8501,8 @@ function SET_UNIT:AddUnitsByName( AddUnitNames ) end --- Remove UNIT(s) from SET_UNIT. --- @param Set#SET_UNIT self --- @param Unit#UNIT RemoveUnitNames A single name or an array of UNIT names. +-- @param Core.Set#SET_UNIT self +-- @param Wrapper.Unit#UNIT RemoveUnitNames A single name or an array of UNIT names. -- @return self function SET_UNIT:RemoveUnitsByName( RemoveUnitNames ) @@ -13124,7 +8519,7 @@ end --- Finds a Unit based on the Unit Name. -- @param #SET_UNIT self -- @param #string UnitName --- @return Unit#UNIT The found Unit. +-- @return Wrapper.Unit#UNIT The found Unit. function SET_UNIT:FindUnit( UnitName ) local UnitFound = self.Set[UnitName] @@ -13270,7 +8665,7 @@ end --- Handles the Database to check on an event (birth) that the Object was added in the Database. -- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! -- @param #SET_UNIT self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event -- @return #string The name of the UNIT -- @return #table The UNIT function SET_UNIT:AddInDatabase( Event ) @@ -13287,7 +8682,7 @@ end --- Handles the Database to check on any event that Object exists in the Database. -- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa! -- @param #SET_UNIT self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event -- @return #string The name of the UNIT -- @return #table The UNIT function SET_UNIT:FindInDatabase( Event ) @@ -13311,15 +8706,15 @@ end --- Iterate the SET_UNIT and call an iterator function for each **alive** UNIT presence completely in a @{Zone}, providing the UNIT and optional parameters to the called function. -- @param #SET_UNIT self --- @param Zone#ZONE ZoneObject The Zone to be tested for. +-- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. -- @param #function IteratorFunction The function that will be called when there is an alive UNIT in the SET_UNIT. The function needs to accept a UNIT parameter. -- @return #SET_UNIT self function SET_UNIT:ForEachUnitCompletelyInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) self:ForEach( IteratorFunction, arg, self.Set, - --- @param Zone#ZONE_BASE ZoneObject - -- @param Unit#UNIT UnitObject + --- @param Core.Zone#ZONE_BASE ZoneObject + -- @param Wrapper.Unit#UNIT UnitObject function( ZoneObject, UnitObject ) if UnitObject:IsCompletelyInZone( ZoneObject ) then return true @@ -13333,15 +8728,15 @@ end --- Iterate the SET_UNIT and call an iterator function for each **alive** UNIT presence not in a @{Zone}, providing the UNIT and optional parameters to the called function. -- @param #SET_UNIT self --- @param Zone#ZONE ZoneObject The Zone to be tested for. +-- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. -- @param #function IteratorFunction The function that will be called when there is an alive UNIT in the SET_UNIT. The function needs to accept a UNIT parameter. -- @return #SET_UNIT self function SET_UNIT:ForEachUnitNotInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) self:ForEach( IteratorFunction, arg, self.Set, - --- @param Zone#ZONE_BASE ZoneObject - -- @param Unit#UNIT UnitObject + --- @param Core.Zone#ZONE_BASE ZoneObject + -- @param Wrapper.Unit#UNIT UnitObject function( ZoneObject, UnitObject ) if UnitObject:IsNotInZone( ZoneObject ) then return true @@ -13363,7 +8758,7 @@ function SET_UNIT:GetUnitTypes() local UnitTypes = {} for UnitID, UnitData in pairs( self:GetSet() ) do - local TextUnit = UnitData -- Unit#UNIT + local TextUnit = UnitData -- Wrapper.Unit#UNIT if TextUnit:IsAlive() then local UnitType = TextUnit:GetTypeName() @@ -13408,7 +8803,7 @@ function SET_UNIT:GetUnitThreatLevels() local UnitThreatLevels = {} for UnitID, UnitData in pairs( self:GetSet() ) do - local ThreatUnit = UnitData -- Unit#UNIT + local ThreatUnit = UnitData -- Wrapper.Unit#UNIT if ThreatUnit:IsAlive() then local UnitThreatLevel, UnitThreatLevelText = ThreatUnit:GetThreatLevel() local ThreatUnitName = ThreatUnit:GetName() @@ -13425,14 +8820,14 @@ end --- Returns if the @{Set} has targets having a radar (of a given type). -- @param #SET_UNIT self --- @param DCSUnit#Unit.RadarType RadarType +-- @param Dcs.DCSWrapper.Unit#Unit.RadarType RadarType -- @return #number The amount of radars in the Set with the given type function SET_UNIT:HasRadar( RadarType ) self:F2( RadarType ) local RadarCount = 0 for UnitID, UnitData in pairs( self:GetSet()) do - local UnitSensorTest = UnitData -- Unit#UNIT + local UnitSensorTest = UnitData -- Wrapper.Unit#UNIT local HasSensors if RadarType then HasSensors = UnitSensorTest:HasSensors( Unit.SensorType.RADAR, RadarType ) @@ -13456,7 +8851,7 @@ function SET_UNIT:HasSEAD() local SEADCount = 0 for UnitID, UnitData in pairs( self:GetSet()) do - local UnitSEAD = UnitData -- Unit#UNIT + local UnitSEAD = UnitData -- Wrapper.Unit#UNIT if UnitSEAD:IsAlive() then local UnitSEADAttributes = UnitSEAD:GetDesc().attributes @@ -13480,7 +8875,7 @@ function SET_UNIT:HasGroundUnits() local GroundUnitCount = 0 for UnitID, UnitData in pairs( self:GetSet()) do - local UnitTest = UnitData -- Unit#UNIT + local UnitTest = UnitData -- Wrapper.Unit#UNIT if UnitTest:IsGround() then GroundUnitCount = GroundUnitCount + 1 end @@ -13497,7 +8892,7 @@ function SET_UNIT:HasFriendlyUnits( FriendlyCoalition ) local FriendlyUnitCount = 0 for UnitID, UnitData in pairs( self:GetSet()) do - local UnitTest = UnitData -- Unit#UNIT + local UnitTest = UnitData -- Wrapper.Unit#UNIT if UnitTest:IsFriendly( FriendlyCoalition ) then FriendlyUnitCount = FriendlyUnitCount + 1 end @@ -13536,7 +8931,7 @@ end --- -- @param #SET_UNIT self --- @param Unit#UNIT MUnit +-- @param Wrapper.Unit#UNIT MUnit -- @return #SET_UNIT self function SET_UNIT:IsIncludeObject( MUnit ) self:F2( MUnit ) @@ -13629,7 +9024,7 @@ end --- SET_CLIENT class -- @type SET_CLIENT --- @extends Set#SET_BASE +-- @extends Core.Set#SET_BASE SET_CLIENT = { ClassName = "SET_CLIENT", Clients = {}, @@ -13671,7 +9066,7 @@ function SET_CLIENT:New() end --- Add CLIENT(s) to SET_CLIENT. --- @param Set#SET_CLIENT self +-- @param Core.Set#SET_CLIENT self -- @param #string AddClientNames A single name or an array of CLIENT names. -- @return self function SET_CLIENT:AddClientsByName( AddClientNames ) @@ -13686,8 +9081,8 @@ function SET_CLIENT:AddClientsByName( AddClientNames ) end --- Remove CLIENT(s) from SET_CLIENT. --- @param Set#SET_CLIENT self --- @param Client#CLIENT RemoveClientNames A single name or an array of CLIENT names. +-- @param Core.Set#SET_CLIENT self +-- @param Wrapper.Client#CLIENT RemoveClientNames A single name or an array of CLIENT names. -- @return self function SET_CLIENT:RemoveClientsByName( RemoveClientNames ) @@ -13704,7 +9099,7 @@ end --- Finds a Client based on the Client Name. -- @param #SET_CLIENT self -- @param #string ClientName --- @return Client#CLIENT The found Client. +-- @return Wrapper.Client#CLIENT The found Client. function SET_CLIENT:FindClient( ClientName ) local ClientFound = self.Set[ClientName] @@ -13825,7 +9220,7 @@ end --- Handles the Database to check on an event (birth) that the Object was added in the Database. -- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! -- @param #SET_CLIENT self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event -- @return #string The name of the CLIENT -- @return #table The CLIENT function SET_CLIENT:AddInDatabase( Event ) @@ -13837,7 +9232,7 @@ end --- Handles the Database to check on any event that Object exists in the Database. -- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa! -- @param #SET_CLIENT self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event -- @return #string The name of the CLIENT -- @return #table The CLIENT function SET_CLIENT:FindInDatabase( Event ) @@ -13860,15 +9255,15 @@ end --- Iterate the SET_CLIENT and call an iterator function for each **alive** CLIENT presence completely in a @{Zone}, providing the CLIENT and optional parameters to the called function. -- @param #SET_CLIENT self --- @param Zone#ZONE ZoneObject The Zone to be tested for. +-- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. -- @param #function IteratorFunction The function that will be called when there is an alive CLIENT in the SET_CLIENT. The function needs to accept a CLIENT parameter. -- @return #SET_CLIENT self function SET_CLIENT:ForEachClientInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) self:ForEach( IteratorFunction, arg, self.Set, - --- @param Zone#ZONE_BASE ZoneObject - -- @param Client#CLIENT ClientObject + --- @param Core.Zone#ZONE_BASE ZoneObject + -- @param Wrapper.Client#CLIENT ClientObject function( ZoneObject, ClientObject ) if ClientObject:IsInZone( ZoneObject ) then return true @@ -13882,15 +9277,15 @@ end --- Iterate the SET_CLIENT and call an iterator function for each **alive** CLIENT presence not in a @{Zone}, providing the CLIENT and optional parameters to the called function. -- @param #SET_CLIENT self --- @param Zone#ZONE ZoneObject The Zone to be tested for. +-- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. -- @param #function IteratorFunction The function that will be called when there is an alive CLIENT in the SET_CLIENT. The function needs to accept a CLIENT parameter. -- @return #SET_CLIENT self function SET_CLIENT:ForEachClientNotInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) self:ForEach( IteratorFunction, arg, self.Set, - --- @param Zone#ZONE_BASE ZoneObject - -- @param Client#CLIENT ClientObject + --- @param Core.Zone#ZONE_BASE ZoneObject + -- @param Wrapper.Client#CLIENT ClientObject function( ZoneObject, ClientObject ) if ClientObject:IsNotInZone( ZoneObject ) then return true @@ -13904,7 +9299,7 @@ end --- -- @param #SET_CLIENT self --- @param Client#CLIENT MClient +-- @param Wrapper.Client#CLIENT MClient -- @return #SET_CLIENT self function SET_CLIENT:IsIncludeObject( MClient ) self:F2( MClient ) @@ -13986,7 +9381,7 @@ end --- SET_AIRBASE class -- @type SET_AIRBASE --- @extends Set#SET_BASE +-- @extends Core.Set#SET_BASE SET_AIRBASE = { ClassName = "SET_AIRBASE", Airbases = {}, @@ -14022,7 +9417,7 @@ function SET_AIRBASE:New() end --- Add AIRBASEs to SET_AIRBASE. --- @param Set#SET_AIRBASE self +-- @param Core.Set#SET_AIRBASE self -- @param #string AddAirbaseNames A single name or an array of AIRBASE names. -- @return self function SET_AIRBASE:AddAirbasesByName( AddAirbaseNames ) @@ -14037,8 +9432,8 @@ function SET_AIRBASE:AddAirbasesByName( AddAirbaseNames ) end --- Remove AIRBASEs from SET_AIRBASE. --- @param Set#SET_AIRBASE self --- @param Airbase#AIRBASE RemoveAirbaseNames A single name or an array of AIRBASE names. +-- @param Core.Set#SET_AIRBASE self +-- @param Wrapper.Airbase#AIRBASE RemoveAirbaseNames A single name or an array of AIRBASE names. -- @return self function SET_AIRBASE:RemoveAirbasesByName( RemoveAirbaseNames ) @@ -14055,7 +9450,7 @@ end --- Finds a Airbase based on the Airbase Name. -- @param #SET_AIRBASE self -- @param #string AirbaseName --- @return Airbase#AIRBASE The found Airbase. +-- @return Wrapper.Airbase#AIRBASE The found Airbase. function SET_AIRBASE:FindAirbase( AirbaseName ) local AirbaseFound = self.Set[AirbaseName] @@ -14117,7 +9512,7 @@ end --- Handles the Database to check on an event (birth) that the Object was added in the Database. -- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! -- @param #SET_AIRBASE self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event -- @return #string The name of the AIRBASE -- @return #table The AIRBASE function SET_AIRBASE:AddInDatabase( Event ) @@ -14129,7 +9524,7 @@ end --- Handles the Database to check on any event that Object exists in the Database. -- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa! -- @param #SET_AIRBASE self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event -- @return #string The name of the AIRBASE -- @return #table The AIRBASE function SET_AIRBASE:FindInDatabase( Event ) @@ -14150,10 +9545,10 @@ function SET_AIRBASE:ForEachAirbase( IteratorFunction, ... ) return self end ---- Iterate the SET_AIRBASE while identifying the nearest @{Airbase#AIRBASE} from a @{Point#POINT_VEC2}. +--- Iterate the SET_AIRBASE while identifying the nearest @{Wrapper.Airbase#AIRBASE} from a @{Core.Point#POINT_VEC2}. -- @param #SET_AIRBASE self --- @param Point#POINT_VEC2 PointVec2 A @{Point#POINT_VEC2} object from where to evaluate the closest @{Airbase#AIRBASE}. --- @return Airbase#AIRBASE The closest @{Airbase#AIRBASE}. +-- @param Core.Point#POINT_VEC2 PointVec2 A @{Core.Point#POINT_VEC2} object from where to evaluate the closest @{Wrapper.Airbase#AIRBASE}. +-- @return Wrapper.Airbase#AIRBASE The closest @{Wrapper.Airbase#AIRBASE}. function SET_AIRBASE:FindNearestAirbaseFromPointVec2( PointVec2 ) self:F2( PointVec2 ) @@ -14165,7 +9560,7 @@ end --- -- @param #SET_AIRBASE self --- @param Airbase#AIRBASE MAirbase +-- @param Wrapper.Airbase#AIRBASE MAirbase -- @return #SET_AIRBASE self function SET_AIRBASE:IsIncludeObject( MAirbase ) self:F2( MAirbase ) @@ -14207,60 +9602,75 @@ function SET_AIRBASE:IsIncludeObject( MAirbase ) end --- This module contains the POINT classes. -- --- 1) @{Point#POINT_VEC3} class, extends @{Base#BASE} --- =============================================== --- The @{Point#POINT_VEC3} class defines a 3D point in the simulator. +-- 1) @{Core.Point#POINT_VEC3} class, extends @{Core.Base#BASE} +-- ================================================== +-- The @{Core.Point#POINT_VEC3} class defines a 3D point in the simulator. -- -- **Important Note:** Most of the functions in this section were taken from MIST, and reworked to OO concepts. -- In order to keep the credibility of the the author, I want to emphasize that the of the MIST framework was created by Grimes, who you can find on the Eagle Dynamics Forums. -- -- 1.1) POINT_VEC3 constructor -- --------------------------- --- --- A new POINT instance can be created with: +-- A new POINT_VEC3 instance can be created with: -- -- * @{#POINT_VEC3.New}(): a 3D point. +-- * @{#POINT_VEC3.NewFromVec3}(): a 3D point created from a @{Dcs.DCSTypes#Vec3}. +-- -- --- 2) @{Point#POINT_VEC2} class, extends @{Point#POINT_VEC3} +-- 2) @{Core.Point#POINT_VEC2} class, extends @{Core.Point#POINT_VEC3} -- ========================================================= --- The @{Point#POINT_VEC2} class defines a 2D point in the simulator. The height coordinate (if needed) will be the land height + an optional added height specified. +-- The @{Core.Point#POINT_VEC2} class defines a 2D point in the simulator. The height coordinate (if needed) will be the land height + an optional added height specified. -- -- 2.1) POINT_VEC2 constructor -- --------------------------- --- --- A new POINT instance can be created with: +-- A new POINT_VEC2 instance can be created with: -- --- * @{#POINT_VEC2.New}(): a 2D point. +-- * @{#POINT_VEC2.New}(): a 2D point, taking an additional height parameter. +-- * @{#POINT_VEC2.NewFromVec2}(): a 2D point created from a @{Dcs.DCSTypes#Vec2}. +-- +-- === +-- +-- **API CHANGE HISTORY** +-- ====================== +-- +-- The underlying change log documents the API changes. Please read this carefully. The following notation is used: +-- +-- * **Added** parts are expressed in bold type face. +-- * _Removed_ parts are expressed in italic type face. +-- +-- Hereby the change log: +-- +-- 2016-08-12: POINT_VEC3:**Translate( Distance, Angle )** added. +-- +-- 2016-08-06: Made PointVec3 and Vec3, PointVec2 and Vec2 terminology used in the code consistent. +-- +-- * Replaced method _Point_Vec3() to **Vec3**() where the code manages a Vec3. Replaced all references to the method. +-- * Replaced method _Point_Vec2() to **Vec2**() where the code manages a Vec2. Replaced all references to the method. +-- * Replaced method Random_Point_Vec3() to **RandomVec3**() where the code manages a Vec3. Replaced all references to the method. +-- . +-- === +-- +-- ### Authors: +-- +-- * FlightControl : Design & Programming +-- +-- ### Contributions: -- -- @module Point --- @author FlightControl --- The POINT_VEC3 class -- @type POINT_VEC3 --- @extends Base#BASE +-- @extends Core.Base#BASE -- @field #number x The x coordinate in 3D space. -- @field #number y The y coordinate in 3D space. -- @field #number z The z coordiante in 3D space. --- @field #POINT_VEC3.SmokeColor SmokeColor --- @field #POINT_VEC3.FlareColor FlareColor +-- @field Utilities.Utils#SMOKECOLOR SmokeColor +-- @field Utilities.Utils#FLARECOLOR FlareColor -- @field #POINT_VEC3.RoutePointAltType RoutePointAltType -- @field #POINT_VEC3.RoutePointType RoutePointType -- @field #POINT_VEC3.RoutePointAction RoutePointAction POINT_VEC3 = { ClassName = "POINT_VEC3", - SmokeColor = { - Green = trigger.smokeColor.Green, - Red = trigger.smokeColor.Red, - White = trigger.smokeColor.White, - Orange = trigger.smokeColor.Orange, - Blue = trigger.smokeColor.Blue - }, - FlareColor = { - Green = trigger.flareColor.Green, - Red = trigger.flareColor.Red, - White = trigger.flareColor.White, - Yellow = trigger.flareColor.Yellow - }, Metric = true, RoutePointAltType = { BARO = "BARO", @@ -14273,81 +9683,70 @@ POINT_VEC3 = { }, } - ---- SmokeColor --- @type POINT_VEC3.SmokeColor --- @field Green --- @field Red --- @field White --- @field Orange --- @field Blue - - - ---- FlareColor --- @type POINT_VEC3.FlareColor --- @field Green --- @field Red --- @field White --- @field Yellow +--- The POINT_VEC2 class +-- @type POINT_VEC2 +-- @extends #POINT_VEC3 +-- @field Dcs.DCSTypes#Distance x The x coordinate in meters. +-- @field Dcs.DCSTypes#Distance y the y coordinate in meters. +POINT_VEC2 = { + ClassName = "POINT_VEC2", +} +do -- POINT_VEC3 --- RoutePoint AltTypes -- @type POINT_VEC3.RoutePointAltType -- @field BARO "BARO" - - --- RoutePoint Types -- @type POINT_VEC3.RoutePointType -- @field TurningPoint "Turning Point" - - --- RoutePoint Actions -- @type POINT_VEC3.RoutePointAction -- @field TurningPoint "Turning Point" - - -- Constructor. --- Create a new POINT_VEC3 object. -- @param #POINT_VEC3 self --- @param DCSTypes#Distance x The x coordinate of the Vec3 point, pointing to the North. --- @param DCSTypes#Distance y The y coordinate of the Vec3 point, pointing Upwards. --- @param DCSTypes#Distance z The z coordinate of the Vec3 point, pointing to the Right. --- @return Point#POINT_VEC3 self +-- @param Dcs.DCSTypes#Distance x The x coordinate of the Vec3 point, pointing to the North. +-- @param Dcs.DCSTypes#Distance y The y coordinate of the Vec3 point, pointing Upwards. +-- @param Dcs.DCSTypes#Distance z The z coordinate of the Vec3 point, pointing to the Right. +-- @return Core.Point#POINT_VEC3 self function POINT_VEC3:New( x, y, z ) local self = BASE:Inherit( self, BASE:New() ) self.x = x self.y = y self.z = z + return self end --- Create a new POINT_VEC3 object from Vec3 coordinates. -- @param #POINT_VEC3 self --- @param DCSTypes#Vec3 Vec3 The Vec3 point. --- @return Point#POINT_VEC3 self +-- @param Dcs.DCSTypes#Vec3 Vec3 The Vec3 point. +-- @return Core.Point#POINT_VEC3 self function POINT_VEC3:NewFromVec3( Vec3 ) - return self:New( Vec3.x, Vec3.y, Vec3.z ) + self = self:New( Vec3.x, Vec3.y, Vec3.z ) + self:F2( self ) + return self end --- Return the coordinates of the POINT_VEC3 in Vec3 format. -- @param #POINT_VEC3 self --- @return DCSTypes#Vec3 The Vec3 coodinate. +-- @return Dcs.DCSTypes#Vec3 The Vec3 coodinate. function POINT_VEC3:GetVec3() return { x = self.x, y = self.y, z = self.z } end --- Return the coordinates of the POINT_VEC3 in Vec2 format. -- @param #POINT_VEC3 self --- @return DCSTypes#Vec2 The Vec2 coodinate. +-- @return Dcs.DCSTypes#Vec2 The Vec2 coodinate. function POINT_VEC3:GetVec2() return { x = self.x, y = self.z } end @@ -14394,9 +9793,9 @@ end --- Return a random Vec2 within an Outer Radius and optionally NOT within an Inner Radius of the POINT_VEC3. -- @param #POINT_VEC3 self --- @param DCSTypes#Distance OuterRadius --- @param DCSTypes#Distance InnerRadius --- @return DCSTypes#Vec2 Vec2 +-- @param Dcs.DCSTypes#Distance OuterRadius +-- @param Dcs.DCSTypes#Distance InnerRadius +-- @return Dcs.DCSTypes#Vec2 Vec2 function POINT_VEC3:GetRandomVec2InRadius( OuterRadius, InnerRadius ) self:F2( { OuterRadius, InnerRadius } ) @@ -14425,8 +9824,8 @@ end --- Return a random POINT_VEC2 within an Outer Radius and optionally NOT within an Inner Radius of the POINT_VEC3. -- @param #POINT_VEC3 self --- @param DCSTypes#Distance OuterRadius --- @param DCSTypes#Distance InnerRadius +-- @param Dcs.DCSTypes#Distance OuterRadius +-- @param Dcs.DCSTypes#Distance InnerRadius -- @return #POINT_VEC2 function POINT_VEC3:GetRandomPointVec2InRadius( OuterRadius, InnerRadius ) self:F2( { OuterRadius, InnerRadius } ) @@ -14436,9 +9835,9 @@ end --- Return a random Vec3 within an Outer Radius and optionally NOT within an Inner Radius of the POINT_VEC3. -- @param #POINT_VEC3 self --- @param DCSTypes#Distance OuterRadius --- @param DCSTypes#Distance InnerRadius --- @return DCSTypes#Vec3 Vec3 +-- @param Dcs.DCSTypes#Distance OuterRadius +-- @param Dcs.DCSTypes#Distance InnerRadius +-- @return Dcs.DCSTypes#Vec3 Vec3 function POINT_VEC3:GetRandomVec3InRadius( OuterRadius, InnerRadius ) local RandomVec2 = self:GetRandomVec2InRadius( OuterRadius, InnerRadius ) @@ -14450,8 +9849,8 @@ end --- Return a random POINT_VEC3 within an Outer Radius and optionally NOT within an Inner Radius of the POINT_VEC3. -- @param #POINT_VEC3 self --- @param DCSTypes#Distance OuterRadius --- @param DCSTypes#Distance InnerRadius +-- @param Dcs.DCSTypes#Distance OuterRadius +-- @param Dcs.DCSTypes#Distance InnerRadius -- @return #POINT_VEC3 function POINT_VEC3:GetRandomPointVec3InRadius( OuterRadius, InnerRadius ) @@ -14462,7 +9861,7 @@ end --- Return a direction vector Vec3 from POINT_VEC3 to the POINT_VEC3. -- @param #POINT_VEC3 self -- @param #POINT_VEC3 TargetPointVec3 The target POINT_VEC3. --- @return DCSTypes#Vec3 DirectionVec3 The direction vector in Vec3 format. +-- @return Dcs.DCSTypes#Vec3 DirectionVec3 The direction vector in Vec3 format. function POINT_VEC3:GetDirectionVec3( TargetPointVec3 ) return { x = TargetPointVec3:GetX() - self:GetX(), y = TargetPointVec3:GetY() - self:GetY(), z = TargetPointVec3:GetZ() - self:GetZ() } end @@ -14480,7 +9879,7 @@ end --- Return a direction in radians from the POINT_VEC3 using a direction vector in Vec3 format. -- @param #POINT_VEC3 self --- @param DCSTypes#Vec3 DirectionVec3 The direction vector in Vec3 format. +-- @param Dcs.DCSTypes#Vec3 DirectionVec3 The direction vector in Vec3 format. -- @return #number DirectionRadians The direction in radians. function POINT_VEC3:GetDirectionRadians( DirectionVec3 ) local DirectionRadians = math.atan2( DirectionVec3.z, DirectionVec3.x ) @@ -14494,7 +9893,7 @@ end --- Return the 2D distance in meters between the target POINT_VEC3 and the POINT_VEC3. -- @param #POINT_VEC3 self -- @param #POINT_VEC3 TargetPointVec3 The target POINT_VEC3. --- @return DCSTypes#Distance Distance The distance in meters. +-- @return Dcs.DCSTypes#Distance Distance The distance in meters. function POINT_VEC3:Get2DDistance( TargetPointVec3 ) local TargetVec3 = TargetPointVec3:GetVec3() local SourceVec3 = self:GetVec3() @@ -14504,7 +9903,7 @@ end --- Return the 3D distance in meters between the target POINT_VEC3 and the POINT_VEC3. -- @param #POINT_VEC3 self -- @param #POINT_VEC3 TargetPointVec3 The target POINT_VEC3. --- @return DCSTypes#Distance Distance The distance in meters. +-- @return Dcs.DCSTypes#Distance Distance The distance in meters. function POINT_VEC3:Get3DDistance( TargetPointVec3 ) local TargetVec3 = TargetPointVec3:GetVec3() local SourceVec3 = self:GetVec3() @@ -14580,6 +9979,20 @@ function POINT_VEC3:IsMetric() return self.Metric end +--- Add a Distance in meters from the POINT_VEC3 horizontal plane, with the given angle, and calculate the new POINT_VEC3. +-- @param #POINT_VEC3 self +-- @param Dcs.DCSTypes#Distance Distance The Distance to be added in meters. +-- @param Dcs.DCSTypes#Angle Angle The Angle in degrees. +-- @return #POINT_VEC3 The new calculated POINT_VEC3. +function POINT_VEC3:Translate( Distance, Angle ) + local SX = self:GetX() + local SZ = self:GetZ() + local Radians = Angle / 180 * math.pi + local TX = Distance * math.cos( Radians ) + SX + local TZ = Distance * math.sin( Radians ) + SZ + + return POINT_VEC3:New( TX, self:GetY(), TZ ) +end @@ -14588,7 +10001,7 @@ end -- @param #POINT_VEC3.RoutePointAltType AltType The altitude type. -- @param #POINT_VEC3.RoutePointType Type The route point type. -- @param #POINT_VEC3.RoutePointAction Action The route point action. --- @param DCSTypes#Speed Speed Airspeed in km/h. +-- @param Dcs.DCSTypes#Speed Speed Airspeed in km/h. -- @param #boolean SpeedLocked true means the speed is locked. -- @return #table The route point. function POINT_VEC3:RoutePointAir( AltType, Type, Action, Speed, SpeedLocked ) @@ -14629,7 +10042,7 @@ end --- Build an ground type route point. -- @param #POINT_VEC3 self --- @param DCSTypes#Speed Speed Speed in km/h. +-- @param Dcs.DCSTypes#Speed Speed Speed in km/h. -- @param #POINT_VEC3.RoutePointAction Formation The route point Formation. -- @return #table The route point. function POINT_VEC3:RoutePointGround( Speed, Formation ) @@ -14669,7 +10082,7 @@ end --- Smokes the point in a color. -- @param #POINT_VEC3 self --- @param Point#POINT_VEC3.SmokeColor SmokeColor +-- @param Utilities.Utils#SMOKECOLOR SmokeColor function POINT_VEC3:Smoke( SmokeColor ) self:F2( { SmokeColor } ) trigger.action.smoke( self:GetVec3(), SmokeColor ) @@ -14679,41 +10092,41 @@ end -- @param #POINT_VEC3 self function POINT_VEC3:SmokeGreen() self:F2() - self:Smoke( POINT_VEC3.SmokeColor.Green ) + self:Smoke( SMOKECOLOR.Green ) end --- Smoke the POINT_VEC3 Red. -- @param #POINT_VEC3 self function POINT_VEC3:SmokeRed() self:F2() - self:Smoke( POINT_VEC3.SmokeColor.Red ) + self:Smoke( SMOKECOLOR.Red ) end --- Smoke the POINT_VEC3 White. -- @param #POINT_VEC3 self function POINT_VEC3:SmokeWhite() self:F2() - self:Smoke( POINT_VEC3.SmokeColor.White ) + self:Smoke( SMOKECOLOR.White ) end --- Smoke the POINT_VEC3 Orange. -- @param #POINT_VEC3 self function POINT_VEC3:SmokeOrange() self:F2() - self:Smoke( POINT_VEC3.SmokeColor.Orange ) + self:Smoke( SMOKECOLOR.Orange ) end --- Smoke the POINT_VEC3 Blue. -- @param #POINT_VEC3 self function POINT_VEC3:SmokeBlue() self:F2() - self:Smoke( POINT_VEC3.SmokeColor.Blue ) + self:Smoke( SMOKECOLOR.Blue ) end --- Flares the point in a color. -- @param #POINT_VEC3 self --- @param Point#POINT_VEC3.FlareColor --- @param DCSTypes#Azimuth (optional) Azimuth The azimuth of the flare direction. The default azimuth is 0. +-- @param Utilities.Utils#FLARECOLOR FlareColor +-- @param Dcs.DCSTypes#Azimuth (optional) Azimuth The azimuth of the flare direction. The default azimuth is 0. function POINT_VEC3:Flare( FlareColor, Azimuth ) self:F2( { FlareColor } ) trigger.action.signalFlare( self:GetVec3(), FlareColor, Azimuth and Azimuth or 0 ) @@ -14721,51 +10134,47 @@ end --- Flare the POINT_VEC3 White. -- @param #POINT_VEC3 self --- @param DCSTypes#Azimuth (optional) Azimuth The azimuth of the flare direction. The default azimuth is 0. +-- @param Dcs.DCSTypes#Azimuth (optional) Azimuth The azimuth of the flare direction. The default azimuth is 0. function POINT_VEC3:FlareWhite( Azimuth ) self:F2( Azimuth ) - self:Flare( POINT_VEC3.FlareColor.White, Azimuth ) + self:Flare( FLARECOLOR.White, Azimuth ) end --- Flare the POINT_VEC3 Yellow. -- @param #POINT_VEC3 self --- @param DCSTypes#Azimuth (optional) Azimuth The azimuth of the flare direction. The default azimuth is 0. +-- @param Dcs.DCSTypes#Azimuth (optional) Azimuth The azimuth of the flare direction. The default azimuth is 0. function POINT_VEC3:FlareYellow( Azimuth ) self:F2( Azimuth ) - self:Flare( POINT_VEC3.FlareColor.Yellow, Azimuth ) + self:Flare( FLARECOLOR.Yellow, Azimuth ) end --- Flare the POINT_VEC3 Green. -- @param #POINT_VEC3 self --- @param DCSTypes#Azimuth (optional) Azimuth The azimuth of the flare direction. The default azimuth is 0. +-- @param Dcs.DCSTypes#Azimuth (optional) Azimuth The azimuth of the flare direction. The default azimuth is 0. function POINT_VEC3:FlareGreen( Azimuth ) self:F2( Azimuth ) - self:Flare( POINT_VEC3.FlareColor.Green, Azimuth ) + self:Flare( FLARECOLOR.Green, Azimuth ) end --- Flare the POINT_VEC3 Red. -- @param #POINT_VEC3 self function POINT_VEC3:FlareRed( Azimuth ) self:F2( Azimuth ) - self:Flare( POINT_VEC3.FlareColor.Red, Azimuth ) + self:Flare( FLARECOLOR.Red, Azimuth ) end +end ---- The POINT_VEC2 class --- @type POINT_VEC2 --- @extends Point#POINT_VEC3 --- @field #number x The x coordinate in 2D space. --- @field #number y the y coordinate in 2D space. -POINT_VEC2 = { - ClassName = "POINT_VEC2", - } +do -- POINT_VEC2 ---- Create a new POINT_VEC2 object. + + +--- POINT_VEC2 constructor. -- @param #POINT_VEC2 self --- @param DCSTypes#Distance x The x coordinate of the Vec3 point, pointing to the North. --- @param DCSTypes#Distance y The y coordinate of the Vec3 point, pointing to the Right. --- @param DCSTypes#Distance LandHeightAdd (optional) The default height if required to be evaluated will be the land height of the x, y coordinate. You can specify an extra height to be added to the land height. --- @return Point#POINT_VEC2 +-- @param Dcs.DCSTypes#Distance x The x coordinate of the Vec3 point, pointing to the North. +-- @param Dcs.DCSTypes#Distance y The y coordinate of the Vec3 point, pointing to the Right. +-- @param Dcs.DCSTypes#Distance LandHeightAdd (optional) The default height if required to be evaluated will be the land height of the x, y coordinate. You can specify an extra height to be added to the land height. +-- @return Core.Point#POINT_VEC2 function POINT_VEC2:New( x, y, LandHeightAdd ) local LandHeight = land.getHeight( { ["x"] = x, ["y"] = y } ) @@ -14773,15 +10182,16 @@ function POINT_VEC2:New( x, y, LandHeightAdd ) LandHeightAdd = LandHeightAdd or 0 LandHeight = LandHeight + LandHeightAdd - local self = BASE:Inherit( self, POINT_VEC3:New( x, LandHeight, y ) ) + self = BASE:Inherit( self, POINT_VEC3:New( x, LandHeight, y ) ) + self:F2( self ) return self end --- Create a new POINT_VEC2 object from Vec2 coordinates. -- @param #POINT_VEC2 self --- @param DCSTypes#Vec2 Vec2 The Vec2 point. --- @return Point#POINT_VEC2 self +-- @param Dcs.DCSTypes#Vec2 Vec2 The Vec2 point. +-- @return Core.Point#POINT_VEC2 self function POINT_VEC2:NewFromVec2( Vec2, LandHeightAdd ) local LandHeight = land.getHeight( Vec2 ) @@ -14789,16 +10199,16 @@ function POINT_VEC2:NewFromVec2( Vec2, LandHeightAdd ) LandHeightAdd = LandHeightAdd or 0 LandHeight = LandHeight + LandHeightAdd - local self = BASE:Inherit( self, POINT_VEC3:New( Vec2.x, LandHeight, Vec2.y ) ) - self:F2( { Vec2.x, Vec2.y, LandHeightAdd } ) + self = BASE:Inherit( self, POINT_VEC3:New( Vec2.x, LandHeight, Vec2.y ) ) + self:F2( self ) return self end --- Create a new POINT_VEC2 object from Vec3 coordinates. -- @param #POINT_VEC2 self --- @param DCSTypes#Vec3 Vec3 The Vec3 point. --- @return Point#POINT_VEC2 self +-- @param Dcs.DCSTypes#Vec3 Vec3 The Vec3 point. +-- @return Core.Point#POINT_VEC2 self function POINT_VEC2:NewFromVec3( Vec3 ) local self = BASE:Inherit( self, BASE:New() ) @@ -14806,8 +10216,8 @@ function POINT_VEC2:NewFromVec3( Vec3 ) local LandHeight = land.getHeight( Vec2 ) - local self = BASE:Inherit( self, POINT_VEC3:New( Vec2.x, LandHeight, Vec2.y ) ) - self:F2( { Vec2.x, LandHeight, Vec2.y } ) + self = BASE:Inherit( self, POINT_VEC3:New( Vec2.x, LandHeight, Vec2.y ) ) + self:F2( self ) return self end @@ -14850,7 +10260,7 @@ end --- Calculate the distance from a reference @{#POINT_VEC2}. -- @param #POINT_VEC2 self -- @param #POINT_VEC2 PointVec2Reference The reference @{#POINT_VEC2}. --- @return DCSTypes#Distance The distance from the reference @{#POINT_VEC2} in meters. +-- @return Dcs.DCSTypes#Distance The distance from the reference @{#POINT_VEC2} in meters. function POINT_VEC2:DistanceFromPointVec2( PointVec2Reference ) self:F2( PointVec2Reference ) @@ -14860,10 +10270,10 @@ function POINT_VEC2:DistanceFromPointVec2( PointVec2Reference ) return Distance end ---- Calculate the distance from a reference @{DCSTypes#Vec2}. +--- Calculate the distance from a reference @{Dcs.DCSTypes#Vec2}. -- @param #POINT_VEC2 self --- @param DCSTypes#Vec2 Vec2Reference The reference @{DCSTypes#Vec2}. --- @return DCSTypes#Distance The distance from the reference @{DCSTypes#Vec2} in meters. +-- @param Dcs.DCSTypes#Vec2 Vec2Reference The reference @{Dcs.DCSTypes#Vec2}. +-- @return Dcs.DCSTypes#Distance The distance from the reference @{Dcs.DCSTypes#Vec2} in meters. function POINT_VEC2:DistanceFromVec2( Vec2Reference ) self:F2( Vec2Reference ) @@ -14883,8 +10293,8 @@ end --- Add a Distance in meters from the POINT_VEC2 orthonormal plane, with the given angle, and calculate the new POINT_VEC2. -- @param #POINT_VEC2 self --- @param DCSTypes#Distance Distance The Distance to be added in meters. --- @param DCSTypes#Angle Angle The Angle in degrees. +-- @param Dcs.DCSTypes#Distance Distance The Distance to be added in meters. +-- @param Dcs.DCSTypes#Angle Angle The Angle in degrees. -- @return #POINT_VEC2 The new calculated POINT_VEC2. function POINT_VEC2:Translate( Distance, Angle ) local SX = self:GetX() @@ -14896,78 +10306,6279 @@ function POINT_VEC2:Translate( Distance, Angle ) return POINT_VEC2:New( TX, TY ) end +end ---- The main include file for the MOOSE system. +--- This module contains the MESSAGE class. +-- +-- 1) @{Core.Message#MESSAGE} class, extends @{Core.Base#BASE} +-- ================================================= +-- Message System to display Messages to Clients, Coalitions or All. +-- Messages are shown on the display panel for an amount of seconds, and will then disappear. +-- Messages can contain a category which is indicating the category of the message. +-- +-- 1.1) MESSAGE construction methods +-- --------------------------------- +-- Messages are created with @{Core.Message#MESSAGE.New}. Note that when the MESSAGE object is created, no message is sent yet. +-- To send messages, you need to use the To functions. +-- +-- 1.2) Send messages with MESSAGE To methods +-- ------------------------------------------ +-- Messages are sent to: +-- +-- * Clients with @{Core.Message#MESSAGE.ToClient}. +-- * Coalitions with @{Core.Message#MESSAGE.ToCoalition}. +-- * All Players with @{Core.Message#MESSAGE.ToAll}. +-- +-- @module Message +-- @author FlightControl -Include.File( "Routines" ) -Include.File( "Utils" ) -Include.File( "Base" ) -Include.File( "Object" ) -Include.File( "Identifiable" ) -Include.File( "Positionable" ) -Include.File( "Controllable" ) -Include.File( "Scheduler" ) -Include.File( "Event" ) -Include.File( "Menu" ) -Include.File( "Group" ) -Include.File( "Unit" ) -Include.File( "Zone" ) -Include.File( "Client" ) -Include.File( "Static" ) -Include.File( "Airbase" ) -Include.File( "Database" ) -Include.File( "Set" ) -Include.File( "Point" ) -Include.File( "Moose" ) -Include.File( "Scoring" ) -Include.File( "Cargo" ) -Include.File( "Message" ) -Include.File( "Stage" ) -Include.File( "Task" ) -Include.File( "GoHomeTask" ) -Include.File( "DestroyBaseTask" ) -Include.File( "DestroyGroupsTask" ) -Include.File( "DestroyRadarsTask" ) -Include.File( "DestroyUnitTypesTask" ) -Include.File( "PickupTask" ) -Include.File( "DeployTask" ) -Include.File( "NoTask" ) -Include.File( "RouteTask" ) -Include.File( "Mission" ) -Include.File( "CleanUp" ) -Include.File( "Spawn" ) -Include.File( "Movement" ) -Include.File( "Sead" ) -Include.File( "Escort" ) -Include.File( "MissileTrainer" ) -Include.File( "PatrolZone" ) -Include.File( "AIBalancer" ) -Include.File( "AirbasePolice" ) +--- The MESSAGE class +-- @type MESSAGE +-- @extends Core.Base#BASE +MESSAGE = { + ClassName = "MESSAGE", + MessageCategory = 0, + MessageID = 0, +} -Include.File( "Detection" ) -Include.File( "DetectionManager" ) -Include.File( "StateMachine" ) +--- Creates a new MESSAGE object. Note that these MESSAGE objects are not yet displayed on the display panel. You must use the functions @{ToClient} or @{ToCoalition} or @{ToAll} to send these Messages to the respective recipients. +-- @param self +-- @param #string MessageText is the text of the Message. +-- @param #number MessageDuration is a number in seconds of how long the MESSAGE should be shown on the display panel. +-- @param #string MessageCategory (optional) is a string expressing the "category" of the Message. The category will be shown as the first text in the message followed by a ": ". +-- @return #MESSAGE +-- @usage +-- -- Create a series of new Messages. +-- -- MessageAll is meant to be sent to all players, for 25 seconds, and is classified as "Score". +-- -- MessageRED is meant to be sent to the RED players only, for 10 seconds, and is classified as "End of Mission", with ID "Win". +-- -- MessageClient1 is meant to be sent to a Client, for 25 seconds, and is classified as "Score", with ID "Score". +-- -- MessageClient1 is meant to be sent to a Client, for 25 seconds, and is classified as "Score", with ID "Score". +-- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", 25, "End of Mission" ) +-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", 25, "Penalty" ) +-- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", 25, "Score" ) +-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", 25, "Score") +function MESSAGE:New( MessageText, MessageDuration, MessageCategory ) + local self = BASE:Inherit( self, BASE:New() ) + self:F( { MessageText, MessageDuration, MessageCategory } ) -Include.File( "Process" ) -Include.File( "Process_Assign" ) -Include.File( "Process_Route" ) -Include.File( "Process_Smoke" ) -Include.File( "Process_Destroy" ) -Include.File( "Process_JTAC" ) + -- When no MessageCategory is given, we don't show it as a title... + if MessageCategory and MessageCategory ~= "" then + if MessageCategory:sub(-1) ~= "\n" then + self.MessageCategory = MessageCategory .. ": " + else + self.MessageCategory = MessageCategory:sub( 1, -2 ) .. ":\n" + end + else + self.MessageCategory = "" + end -Include.File( "Task" ) -Include.File( "Task_SEAD" ) -Include.File( "Task_A2G" ) + self.MessageDuration = MessageDuration or 5 + self.MessageTime = timer.getTime() + self.MessageText = MessageText + + self.MessageSent = false + self.MessageGroup = false + self.MessageCoalition = false --- The order of the declarations is important here. Don't touch it. + return self +end + +--- Sends a MESSAGE to a Client Group. Note that the Group needs to be defined within the ME with the skillset "Client" or "Player". +-- @param #MESSAGE self +-- @param Wrapper.Client#CLIENT Client is the Group of the Client. +-- @return #MESSAGE +-- @usage +-- -- Send the 2 messages created with the @{New} method to the Client Group. +-- -- Note that the Message of MessageClient2 is overwriting the Message of MessageClient1. +-- ClientGroup = Group.getByName( "ClientGroup" ) +-- +-- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ):ToClient( ClientGroup ) +-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ):ToClient( ClientGroup ) +-- or +-- MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ):ToClient( ClientGroup ) +-- MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ):ToClient( ClientGroup ) +-- or +-- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ) +-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ) +-- MessageClient1:ToClient( ClientGroup ) +-- MessageClient2:ToClient( ClientGroup ) +function MESSAGE:ToClient( Client ) + self:F( Client ) + + if Client and Client:GetClientGroupID() then + + local ClientGroupID = Client:GetClientGroupID() + self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration ) + trigger.action.outTextForGroup( ClientGroupID, self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration ) + end + + return self +end + +--- Sends a MESSAGE to a Group. +-- @param #MESSAGE self +-- @param Wrapper.Group#GROUP Group is the Group. +-- @return #MESSAGE +function MESSAGE:ToGroup( Group ) + self:F( Group.GroupName ) + + if Group then + + self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration ) + trigger.action.outTextForGroup( Group:GetID(), self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration ) + end + + return self +end +--- Sends a MESSAGE to the Blue coalition. +-- @param #MESSAGE self +-- @return #MESSAGE +-- @usage +-- -- Send a message created with the @{New} method to the BLUE coalition. +-- MessageBLUE = MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToBlue() +-- or +-- MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToBlue() +-- or +-- MessageBLUE = MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ) +-- MessageBLUE:ToBlue() +function MESSAGE:ToBlue() + self:F() + + self:ToCoalition( coalition.side.BLUE ) + + return self +end + +--- Sends a MESSAGE to the Red Coalition. +-- @param #MESSAGE self +-- @return #MESSAGE +-- @usage +-- -- Send a message created with the @{New} method to the RED coalition. +-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToRed() +-- or +-- MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToRed() +-- or +-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ) +-- MessageRED:ToRed() +function MESSAGE:ToRed( ) + self:F() + + self:ToCoalition( coalition.side.RED ) + + return self +end + +--- Sends a MESSAGE to a Coalition. +-- @param #MESSAGE self +-- @param CoalitionSide needs to be filled out by the defined structure of the standard scripting engine @{coalition.side}. +-- @return #MESSAGE +-- @usage +-- -- Send a message created with the @{New} method to the RED coalition. +-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToCoalition( coalition.side.RED ) +-- or +-- MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToCoalition( coalition.side.RED ) +-- or +-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ) +-- MessageRED:ToCoalition( coalition.side.RED ) +function MESSAGE:ToCoalition( CoalitionSide ) + self:F( CoalitionSide ) + + if CoalitionSide then + self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration ) + trigger.action.outTextForCoalition( CoalitionSide, self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration ) + end + + return self +end + +--- Sends a MESSAGE to all players. +-- @param #MESSAGE self +-- @return #MESSAGE +-- @usage +-- -- Send a message created to all players. +-- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ):ToAll() +-- or +-- MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ):ToAll() +-- or +-- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ) +-- MessageAll:ToAll() +function MESSAGE:ToAll() + self:F() + + self:ToCoalition( coalition.side.RED ) + self:ToCoalition( coalition.side.BLUE ) + + return self +end + + + +----- The MESSAGEQUEUE class +---- @type MESSAGEQUEUE +--MESSAGEQUEUE = { +-- ClientGroups = {}, +-- CoalitionSides = {} +--} +-- +--function MESSAGEQUEUE:New( RefreshInterval ) +-- local self = BASE:Inherit( self, BASE:New() ) +-- self:F( { RefreshInterval } ) +-- +-- self.RefreshInterval = RefreshInterval +-- +-- --self.DisplayFunction = routines.scheduleFunction( self._DisplayMessages, { self }, 0, RefreshInterval ) +-- self.DisplayFunction = SCHEDULER:New( self, self._DisplayMessages, {}, 0, RefreshInterval ) +-- +-- return self +--end +-- +----- This function is called automatically by the MESSAGEQUEUE scheduler. +--function MESSAGEQUEUE:_DisplayMessages() +-- +-- -- First we display all messages that a coalition needs to receive... Also those who are not in a client (CA module clients...). +-- for CoalitionSideID, CoalitionSideData in pairs( self.CoalitionSides ) do +-- for MessageID, MessageData in pairs( CoalitionSideData.Messages ) do +-- if MessageData.MessageSent == false then +-- --trigger.action.outTextForCoalition( CoalitionSideID, MessageData.MessageCategory .. '\n' .. MessageData.MessageText:gsub("\n$",""):gsub("\n$",""), MessageData.MessageDuration ) +-- MessageData.MessageSent = true +-- end +-- local MessageTimeLeft = ( MessageData.MessageTime + MessageData.MessageDuration ) - timer.getTime() +-- if MessageTimeLeft <= 0 then +-- MessageData = nil +-- end +-- end +-- end +-- +-- -- Then we send the messages for each individual client, but also to be included are those Coalition messages for the Clients who belong to a coalition. +-- -- Because the Client messages will overwrite the Coalition messages (for that Client). +-- for ClientGroupName, ClientGroupData in pairs( self.ClientGroups ) do +-- for MessageID, MessageData in pairs( ClientGroupData.Messages ) do +-- if MessageData.MessageGroup == false then +-- trigger.action.outTextForGroup( Group.getByName(ClientGroupName):getID(), MessageData.MessageCategory .. '\n' .. MessageData.MessageText:gsub("\n$",""):gsub("\n$",""), MessageData.MessageDuration ) +-- MessageData.MessageGroup = true +-- end +-- local MessageTimeLeft = ( MessageData.MessageTime + MessageData.MessageDuration ) - timer.getTime() +-- if MessageTimeLeft <= 0 then +-- MessageData = nil +-- end +-- end +-- +-- -- Now check if the Client also has messages that belong to the Coalition of the Client... +-- for CoalitionSideID, CoalitionSideData in pairs( self.CoalitionSides ) do +-- for MessageID, MessageData in pairs( CoalitionSideData.Messages ) do +-- local CoalitionGroup = Group.getByName( ClientGroupName ) +-- if CoalitionGroup and CoalitionGroup:getCoalition() == CoalitionSideID then +-- if MessageData.MessageCoalition == false then +-- trigger.action.outTextForGroup( Group.getByName(ClientGroupName):getID(), MessageData.MessageCategory .. '\n' .. MessageData.MessageText:gsub("\n$",""):gsub("\n$",""), MessageData.MessageDuration ) +-- MessageData.MessageCoalition = true +-- end +-- end +-- local MessageTimeLeft = ( MessageData.MessageTime + MessageData.MessageDuration ) - timer.getTime() +-- if MessageTimeLeft <= 0 then +-- MessageData = nil +-- end +-- end +-- end +-- end +-- +-- return true +--end +-- +----- The _MessageQueue object is created when the MESSAGE class module is loaded. +----_MessageQueue = MESSAGEQUEUE:New( 0.5 ) +-- +--- This module contains the FSM class. +-- This development is based on a state machine implementation made by Conroy Kyle. +-- The state machine can be found here: https://github.com/kyleconroy/lua-state-machine +-- +-- I've taken the development and enhanced it to make the state machine hierarchical... +-- It is a fantastic development, this module. +-- +-- === +-- +-- 1) @{Workflow#FSM} class, extends @{Core.Base#BASE} +-- ============================================== +-- +-- 1.1) Add or remove objects from the FSM +-- -------------------------------------------- +-- @module Fsm +-- @author FlightControl + +do -- FSM + + --- FSM class + -- @type FSM + -- @extends Core.Base#BASE + FSM = { + ClassName = "FSM", + } + + --- Creates a new FSM object. + -- @param #FSM self + -- @return #FSM + function FSM:New( FsmT ) + + -- Inherits from BASE + local self = BASE:Inherit( self, BASE:New() ) + + self.options = options or {} + self.options.subs = self.options.subs or {} + self.current = self.options.initial or 'none' + self.Events = {} + self.subs = {} + self.endstates = {} + + self.Scores = {} + + self._StartState = "none" + self._Transitions = {} + self._Processes = {} + self._EndStates = {} + self._Scores = {} + + self.CallScheduler = SCHEDULER:New( self ) + + + return self + end + + + function FSM:SetStartState( State ) + + self._StartState = State + self.current = State + end + + + function FSM:GetStartState() + + return self._StartState or {} + end + + function FSM:AddTransition( From, Event, To ) + + local Transition = {} + Transition.From = From + Transition.Event = Event + Transition.To = To + + self:E( Transition ) + + self._Transitions[Transition] = Transition + self:_eventmap( self.Events, Transition ) + end + + function FSM:GetTransitions() + + return self._Transitions or {} + end + + --- Set the default @{Process} template with key ProcessName providing the ProcessClass and the process object when it is assigned to a @{Controllable} by the task. + -- @return Core.Fsm#FSM_PROCESS + function FSM:AddProcess( From, Event, Process, ReturnEvents ) + self:E( { From, Event, Process, ReturnEvents } ) + + local Sub = {} + Sub.From = From + Sub.Event = Event + Sub.fsm = Process + Sub.StartEvent = "Start" + Sub.ReturnEvents = ReturnEvents + + self._Processes[Sub] = Sub + + self:_submap( self.subs, Sub, nil ) + + self:AddTransition( From, Event, From ) + + return Process + end + + function FSM:GetProcesses() + + return self._Processes or {} + end + + function FSM:GetProcess( From, Event ) + + for ProcessID, Process in pairs( self:GetProcesses() ) do + if Process.From == From and Process.Event == Event then + self:E( Process ) + return Process.fsm + end + end + + error( "Sub-Process from state " .. From .. " with event " .. Event .. " not found!" ) + end + + function FSM:AddEndState( State ) + + self._EndStates[State] = State + self.endstates[State] = State + end + + function FSM:GetEndStates() + + return self._EndStates or {} + end + + + --- Adds a score for the FSM to be achieved. + -- @param #FSM self + -- @param #string State is the state of the process when the score needs to be given. (See the relevant state descriptions of the process). + -- @param #string ScoreText is a text describing the score that is given according the status. + -- @param #number Score is a number providing the score of the status. + -- @return #FSM self + function FSM:AddScore( State, ScoreText, Score ) + self:F2( { State, ScoreText, Score } ) + + self._Scores[State] = self._Scores[State] or {} + self._Scores[State].ScoreText = ScoreText + self._Scores[State].Score = Score + + return self + end + + --- Adds a score for the FSM_PROCESS to be achieved. + -- @param #FSM self + -- @param #string From is the From State of the main process. + -- @param #string Event is the Event of the main process. + -- @param #string State is the state of the process when the score needs to be given. (See the relevant state descriptions of the process). + -- @param #string ScoreText is a text describing the score that is given according the status. + -- @param #number Score is a number providing the score of the status. + -- @return #FSM self + function FSM:AddScoreProcess( From, Event, State, ScoreText, Score ) + self:F2( { Event, State, ScoreText, Score } ) + + local Process = self:GetProcess( From, Event ) + + self:E( { Process = Process._Name, Scores = Process._Scores, State = State, ScoreText = ScoreText, Score = Score } ) + Process._Scores[State] = Process._Scores[State] or {} + Process._Scores[State].ScoreText = ScoreText + Process._Scores[State].Score = Score + + return Process + end + + function FSM:GetScores() + + return self._Scores or {} + end + + + function FSM:GetSubs() + + return self.options.subs + end + + + function FSM:LoadCallBacks( CallBackTable ) + + for name, callback in pairs( CallBackTable or {} ) do + self[name] = callback + end + + end + + function FSM:_eventmap( Events, EventStructure ) + + local Event = EventStructure.Event + local __Event = "__" .. EventStructure.Event + self[Event] = self[Event] or self:_create_transition(Event) + self[__Event] = self[__Event] or self:_delayed_transition(Event) + self:T( "Added methods: " .. Event .. ", " .. __Event ) + Events[Event] = self.Events[Event] or { map = {} } + self:_add_to_map( Events[Event].map, EventStructure ) + + end + + function FSM:_submap( subs, sub, name ) + self:F( { sub = sub, name = name } ) + subs[sub.From] = subs[sub.From] or {} + subs[sub.From][sub.Event] = subs[sub.From][sub.Event] or {} + + -- Make the reference table weak. + -- setmetatable( subs[sub.From][sub.Event], { __mode = "k" } ) + + subs[sub.From][sub.Event][sub] = {} + subs[sub.From][sub.Event][sub].fsm = sub.fsm + subs[sub.From][sub.Event][sub].StartEvent = sub.StartEvent + subs[sub.From][sub.Event][sub].ReturnEvents = sub.ReturnEvents or {} -- these events need to be given to find the correct continue event ... if none given, the processing will stop. + subs[sub.From][sub.Event][sub].name = name + subs[sub.From][sub.Event][sub].fsmparent = self + end + + + function FSM:_call_handler(handler, params) + if self[handler] then + self:E( "Calling " .. handler ) + return self[handler]( self, unpack(params) ) + end + end + + function FSM._handler( self, EventName, ... ) + + self:E( { EventName, ... } ) + + local can, to = self:can( EventName ) + self:E( { EventName, self.current, can, to } ) + + local ReturnValues = nil + + if can then + local from = self.current + local params = { EventName, from, to, ... } + + if self:_call_handler("onbefore" .. EventName, params) == false + or self:_call_handler("onleave" .. from, params) == false then + return false + end + + self.current = to + + local execute = true + + local subtable = self:_gosub( from, EventName ) + for _, sub in pairs( subtable ) do + --if sub.nextevent then + -- self:F2( "nextevent = " .. sub.nextevent ) + -- self[sub.nextevent]( self ) + --end + self:E( "calling sub start event: " .. sub.StartEvent ) + sub.fsm.fsmparent = self + sub.fsm.ReturnEvents = sub.ReturnEvents + sub.fsm[sub.StartEvent]( sub.fsm ) + execute = true + end + + local fsmparent, Event = self:_isendstate( to ) + if fsmparent and Event then + self:F2( { "end state: ", fsmparent, Event } ) + self:_call_handler("onenter" .. to, params) + self:_call_handler("onafter" .. EventName, params) + self:_call_handler("onstatechange", params) + fsmparent[Event]( fsmparent ) + execute = false + end + + if execute then + -- only execute the call if the From state is not equal to the To state! Otherwise this function should never execute! + if from ~= to then + self:T3( { onenter = "onenter" .. to, callback = self["onenter" .. to] } ) + self:_call_handler("onenter" .. to, params) + end + + self:T3( { On = "OnBefore" .. to, callback = self["OnBefore" .. to] } ) + if ( self:_call_handler("OnBefore" .. to, params ) ~= false ) then + + self:T3( { onafter = "onafter" .. EventName, callback = self["onafter" .. EventName] } ) + self:_call_handler("onafter" .. EventName, params) + + self:T3( { On = "OnAfter" .. to, callback = self["OnAfter" .. to] } ) + ReturnValues = self:_call_handler("OnAfter" .. to, params ) + end + + self:_call_handler("onstatechange", params) + end + + return ReturnValues + end + + return nil + end + + function FSM:_delayed_transition( EventName ) + self:E( { EventName = EventName } ) + return function( self, DelaySeconds, ... ) + self:T( "Delayed Event: " .. EventName ) + local CallID = self.CallScheduler:Schedule( self, self._handler, { EventName, ... }, DelaySeconds or 1 ) + self:T( { CallID = CallID } ) + end + end + + function FSM:_create_transition( EventName ) + self:E( { Event = EventName } ) + return function( self, ... ) return self._handler( self, EventName , ... ) end + end + + function FSM:_gosub( ParentFrom, ParentEvent ) + local fsmtable = {} + if self.subs[ParentFrom] and self.subs[ParentFrom][ParentEvent] then + self:E( { ParentFrom, ParentEvent, self.subs[ParentFrom], self.subs[ParentFrom][ParentEvent] } ) + return self.subs[ParentFrom][ParentEvent] + else + return {} + end + end + + function FSM:_isendstate( Current ) + local FSMParent = self.fsmparent + if FSMParent and self.endstates[Current] then + self:E( { state = Current, endstates = self.endstates, endstate = self.endstates[Current] } ) + FSMParent.current = Current + local ParentFrom = FSMParent.current + self:E( ParentFrom ) + self:E( self.ReturnEvents ) + local Event = self.ReturnEvents[Current] + self:E( { ParentFrom, Event, self.ReturnEvents } ) + if Event then + return FSMParent, Event + else + self:E( { "Could not find parent event name for state ", ParentFrom } ) + end + end + + return nil + end + + function FSM:_add_to_map( Map, Event ) + self:F3( { Map, Event } ) + if type(Event.From) == 'string' then + Map[Event.From] = Event.To + else + for _, From in ipairs(Event.From) do + Map[From] = Event.To + end + end + self:T3( { Map, Event } ) + end + + function FSM:GetState() + return self.current + end + + + function FSM:Is( State ) + return self.current == State + end + + function FSM:is(state) + return self.current == state + end + + function FSM:can(e) + self:E( { e, self.Events, self.Events[e] } ) + local Event = self.Events[e] + self:F3( { self.current, Event } ) + local To = Event and Event.map[self.current] or Event.map['*'] + return To ~= nil, To + end + + function FSM:cannot(e) + return not self:can(e) + end + +end + +do -- FSM_CONTROLLABLE + + --- FSM_CONTROLLABLE class + -- @type FSM_CONTROLLABLE + -- @field Wrapper.Controllable#CONTROLLABLE Controllable + -- @extends Core.Fsm#FSM + FSM_CONTROLLABLE = { + ClassName = "FSM_CONTROLLABLE", + } + + --- Creates a new FSM_CONTROLLABLE object. + -- @param #FSM_CONTROLLABLE self + -- @param #table FSMT Finite State Machine Table + -- @param Wrapper.Controllable#CONTROLLABLE Controllable (optional) The CONTROLLABLE object that the FSM_CONTROLLABLE governs. + -- @return #FSM_CONTROLLABLE + function FSM_CONTROLLABLE:New( FSMT, Controllable ) + + -- Inherits from BASE + local self = BASE:Inherit( self, FSM:New( FSMT ) ) -- Core.Fsm#FSM_CONTROLLABLE + + if Controllable then + self:SetControllable( Controllable ) + end + + return self + end + + --- Sets the CONTROLLABLE object that the FSM_CONTROLLABLE governs. + -- @param #FSM_CONTROLLABLE self + -- @param Wrapper.Controllable#CONTROLLABLE FSMControllable + -- @return #FSM_CONTROLLABLE + function FSM_CONTROLLABLE:SetControllable( FSMControllable ) + self:F( FSMControllable ) + self.Controllable = FSMControllable + end + + --- Gets the CONTROLLABLE object that the FSM_CONTROLLABLE governs. + -- @param #FSM_CONTROLLABLE self + -- @return Wrapper.Controllable#CONTROLLABLE + function FSM_CONTROLLABLE:GetControllable() + return self.Controllable + end + + function FSM_CONTROLLABLE:_call_handler( handler, params ) + + local ErrorHandler = function( errmsg ) + + env.info( "Error in SCHEDULER function:" .. errmsg ) + if debug ~= nil then + env.info( debug.traceback() ) + end + + return errmsg + end + + if self[handler] then + self:E( "Calling " .. handler ) + return xpcall( function() return self[handler]( self, self.Controllable, unpack( params ) ) end, ErrorHandler ) + --return self[handler]( self, self.Controllable, unpack( params ) ) + end + end + +end + +do -- FSM_PROCESS + + --- FSM_PROCESS class + -- @type FSM_PROCESS + -- @field Tasking.Task#TASK Task + -- @extends Core.Fsm#FSM_CONTROLLABLE + FSM_PROCESS = { + ClassName = "FSM_PROCESS", + } + + --- Creates a new FSM_PROCESS object. + -- @param #FSM_PROCESS self + -- @return #FSM_PROCESS + function FSM_PROCESS:New( Controllable, Task ) + + local self = BASE:Inherit( self, FSM_CONTROLLABLE:New() ) -- Core.Fsm#FSM_PROCESS + + self:F( Controllable, Task ) + + self:Assign( Controllable, Task ) + + return self + end + + function FSM_PROCESS:Init( FsmProcess ) + self:E( "No Initialisation" ) + end + + --- Creates a new FSM_PROCESS object based on this FSM_PROCESS. + -- @param #FSM_PROCESS self + -- @return #FSM_PROCESS + function FSM_PROCESS:Copy( Controllable, Task ) + self:E( { self:GetClassNameAndID() } ) + + local NewFsm = self:New( Controllable, Task ) -- Core.Fsm#FSM_PROCESS + + NewFsm:Assign( Controllable, Task ) + + -- Polymorphic call to initialize the new FSM_PROCESS based on self FSM_PROCESS + NewFsm:Init( self ) + + -- Set Start State + NewFsm:SetStartState( self:GetStartState() ) + + -- Copy Transitions + for TransitionID, Transition in pairs( self:GetTransitions() ) do + NewFsm:AddTransition( Transition.From, Transition.Event, Transition.To ) + end + + -- Copy Processes + for ProcessID, Process in pairs( self:GetProcesses() ) do + self:E( { Process} ) + local FsmProcess = NewFsm:AddProcess( Process.From, Process.Event, Process.fsm:Copy( Controllable, Task ), Process.ReturnEvents ) + end + + -- Copy End States + for EndStateID, EndState in pairs( self:GetEndStates() ) do + self:E( EndState ) + NewFsm:AddEndState( EndState ) + end + + -- Copy the score tables + for ScoreID, Score in pairs( self:GetScores() ) do + self:E( Score ) + NewFsm:AddScore( ScoreID, Score.ScoreText, Score.Score ) + end + + return NewFsm + end + + --- Sets the task of the process. + -- @param #FSM_PROCESS self + -- @param Tasking.Task#TASK Task + -- @return #FSM_PROCESS + function FSM_PROCESS:SetTask( Task ) + + self.Task = Task + + return self + end + + --- Gets the task of the process. + -- @param #FSM_PROCESS self + -- @return Tasking.Task#TASK + function FSM_PROCESS:GetTask() + + return self.Task + end + + --- Gets the mission of the process. + -- @param #FSM_PROCESS self + -- @return Tasking.Mission#MISSION + function FSM_PROCESS:GetMission() + + return self.Task.Mission + end + + --- Gets the mission of the process. + -- @param #FSM_PROCESS self + -- @return Tasking.CommandCenter#COMMANDCENTER + function FSM_PROCESS:GetCommandCenter() + + return self:GetTask():GetMission():GetCommandCenter() + end + + --- Send a message of the @{Task} to the Group of the Unit. +-- @param #FSM_PROCESS self +function FSM_PROCESS:Message( Message ) + self:F( { Message = Message } ) + + local CC = self:GetCommandCenter() + local TaskGroup = self.Controllable:GetGroup() + + CC:MessageToGroup( Message, TaskGroup ) +end + + + + + --- Assign the process to a @{Unit} and activate the process. + -- @param #FSM_PROCESS self + -- @param Task.Tasking#TASK Task + -- @param Wrapper.Unit#UNIT ProcessUnit + -- @return #FSM_PROCESS self + function FSM_PROCESS:Assign( ProcessUnit, Task ) + self:E( { Task, ProcessUnit } ) + + self:SetControllable( ProcessUnit ) + self:SetTask( Task ) + + --self.ProcessGroup = ProcessUnit:GetGroup() + + return self + end + + --- Adds a score for the FSM_PROCESS to be achieved. + -- @param #FSM_PROCESS self + -- @param #string State is the state of the process when the score needs to be given. (See the relevant state descriptions of the process). + -- @param #string ScoreText is a text describing the score that is given according the status. + -- @param #number Score is a number providing the score of the status. + -- @return #FSM_PROCESS self + function FSM_PROCESS:AddScore( State, ScoreText, Score ) + self:F2( { State, ScoreText, Score } ) + + self.Scores[State] = self.Scores[State] or {} + self.Scores[State].ScoreText = ScoreText + self.Scores[State].Score = Score + + return self + end + + function FSM_PROCESS:onenterAssigned( ProcessUnit ) + self:E( "Assign" ) + + self.Task:Assign() + end + + function FSM_PROCESS:onenterFailed( ProcessUnit ) + self:E( "Failed" ) + + self.Task:Fail() + end + + function FSM_PROCESS:onenterSuccess( ProcessUnit ) + self:E( "Success" ) + + self.Task:Success() + end + + --- StateMachine callback function for a FSM_PROCESS + -- @param #FSM_PROCESS self + -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit + -- @param #string Event + -- @param #string From + -- @param #string To + function FSM_PROCESS:onstatechange( ProcessUnit, Event, From, To, Dummy ) + self:E( { ProcessUnit, Event, From, To, Dummy, self:IsTrace() } ) + + if self:IsTrace() then + MESSAGE:New( "@ Process " .. self:GetClassNameAndID() .. " : " .. Event .. " changed to state " .. To, 2 ):ToAll() + end + + self:E( self.Scores[To] ) + -- TODO: This needs to be reworked with a callback functions allocated within Task, and set within the mission script from the Task Objects... + if self.Scores[To] then + + local Task = self.Task + local Scoring = Task:GetScoring() + if Scoring then + Scoring:_AddMissionTaskScore( Task.Mission, ProcessUnit, self.Scores[To].ScoreText, self.Scores[To].Score ) + end + end + end + +end + +do -- FSM_TASK + + --- FSM_TASK class + -- @type FSM_TASK + -- @field Tasking.Task#TASK Task + -- @extends Core.Fsm#FSM + FSM_TASK = { + ClassName = "FSM_TASK", + } + + --- Creates a new FSM_TASK object. + -- @param #FSM_TASK self + -- @param #table FSMT + -- @param Tasking.Task#TASK Task + -- @param Wrapper.Unit#UNIT TaskUnit + -- @return #FSM_TASK + function FSM_TASK:New( FSMT ) + + local self = BASE:Inherit( self, FSM_CONTROLLABLE:New( FSMT ) ) -- Core.Fsm#FSM_TASK + + self["onstatechange"] = self.OnStateChange + + return self + end + + function FSM_TASK:_call_handler( handler, params ) + if self[handler] then + self:E( "Calling " .. handler ) + return self[handler]( self, unpack( params ) ) + end + end + +end -- FSM_TASK + +do -- FSM_SET + + --- FSM_SET class + -- @type FSM_SET + -- @field Core.Set#SET_BASE Set + -- @extends Core.Fsm#FSM + FSM_SET = { + ClassName = "FSM_SET", + } + + --- Creates a new FSM_SET object. + -- @param #FSM_SET self + -- @param #table FSMT Finite State Machine Table + -- @param Set_SET_BASE FSMSet (optional) The Set object that the FSM_SET governs. + -- @return #FSM_SET + function FSM_SET:New( FSMSet ) + + -- Inherits from BASE + local self = BASE:Inherit( self, FSM:New() ) -- Core.Fsm#FSM_SET + + if FSMSet then + self:Set( FSMSet ) + end + + return self + end + + --- Sets the SET_BASE object that the FSM_SET governs. + -- @param #FSM_SET self + -- @param Core.Set#SET_BASE FSMSet + -- @return #FSM_SET + function FSM_SET:Set( FSMSet ) + self:F( FSMSet ) + self.Set = FSMSet + end + + --- Gets the SET_BASE object that the FSM_SET governs. + -- @param #FSM_SET self + -- @return Core.Set#SET_BASE + function FSM_SET:Get() + return self.Controllable + end + + function FSM_SET:_call_handler( handler, params ) + if self[handler] then + self:E( "Calling " .. handler ) + return self[handler]( self, self.Set, unpack( params ) ) + end + end + +end -- FSM_SET + +--- This module contains the OBJECT class. +-- +-- 1) @{Wrapper.Object#OBJECT} class, extends @{Core.Base#BASE} +-- =========================================================== +-- The @{Wrapper.Object#OBJECT} class is a wrapper class to handle the DCS Object objects: +-- +-- * Support all DCS Object APIs. +-- * Enhance with Object specific APIs not in the DCS Object API set. +-- * Manage the "state" of the DCS Object. +-- +-- 1.1) OBJECT constructor: +-- ------------------------------ +-- The OBJECT class provides the following functions to construct a OBJECT instance: +-- +-- * @{Wrapper.Object#OBJECT.New}(): Create a OBJECT instance. +-- +-- 1.2) OBJECT methods: +-- -------------------------- +-- The following methods can be used to identify an Object object: +-- +-- * @{Wrapper.Object#OBJECT.GetID}(): Returns the ID of the Object object. +-- +-- === +-- +-- @module Object +-- @author FlightControl + +--- The OBJECT class +-- @type OBJECT +-- @extends Core.Base#BASE +-- @field #string ObjectName The name of the Object. +OBJECT = { + ClassName = "OBJECT", + ObjectName = "", +} + + +--- A DCSObject +-- @type DCSObject +-- @field id_ The ID of the controllable in DCS + +--- Create a new OBJECT from a DCSObject +-- @param #OBJECT self +-- @param Dcs.DCSWrapper.Object#Object ObjectName The Object name +-- @return #OBJECT self +function OBJECT:New( ObjectName ) + local self = BASE:Inherit( self, BASE:New() ) + self:F2( ObjectName ) + self.ObjectName = ObjectName + return self +end + + +--- Returns the unit's unique identifier. +-- @param Wrapper.Object#OBJECT self +-- @return Dcs.DCSWrapper.Object#Object.ID ObjectID +-- @return #nil The DCS Object is not existing or alive. +function OBJECT:GetID() + self:F2( self.ObjectName ) + + local DCSObject = self:GetDCSObject() + + if DCSObject then + local ObjectID = DCSObject:getID() + return ObjectID + end + + return nil +end + +--- Destroys the OBJECT. +-- @param #OBJECT self +-- @return #nil The DCS Unit is not existing or alive. +function OBJECT:Destroy() + self:F2( self.ObjectName ) + + local DCSObject = self:GetDCSObject() + + if DCSObject then + + DCSObject:destroy() + end + + return nil +end + + + + +--- This module contains the IDENTIFIABLE class. +-- +-- 1) @{#IDENTIFIABLE} class, extends @{Wrapper.Object#OBJECT} +-- =============================================================== +-- The @{#IDENTIFIABLE} class is a wrapper class to handle the DCS Identifiable objects: +-- +-- * Support all DCS Identifiable APIs. +-- * Enhance with Identifiable specific APIs not in the DCS Identifiable API set. +-- * Manage the "state" of the DCS Identifiable. +-- +-- 1.1) IDENTIFIABLE constructor: +-- ------------------------------ +-- The IDENTIFIABLE class provides the following functions to construct a IDENTIFIABLE instance: +-- +-- * @{#IDENTIFIABLE.New}(): Create a IDENTIFIABLE instance. +-- +-- 1.2) IDENTIFIABLE methods: +-- -------------------------- +-- The following methods can be used to identify an identifiable object: +-- +-- * @{#IDENTIFIABLE.GetName}(): Returns the name of the Identifiable. +-- * @{#IDENTIFIABLE.IsAlive}(): Returns if the Identifiable is alive. +-- * @{#IDENTIFIABLE.GetTypeName}(): Returns the type name of the Identifiable. +-- * @{#IDENTIFIABLE.GetCoalition}(): Returns the coalition of the Identifiable. +-- * @{#IDENTIFIABLE.GetCountry}(): Returns the country of the Identifiable. +-- * @{#IDENTIFIABLE.GetDesc}(): Returns the descriptor structure of the Identifiable. +-- +-- +-- === +-- +-- @module Identifiable +-- @author FlightControl + +--- The IDENTIFIABLE class +-- @type IDENTIFIABLE +-- @extends Wrapper.Object#OBJECT +-- @field #string IdentifiableName The name of the identifiable. +IDENTIFIABLE = { + ClassName = "IDENTIFIABLE", + IdentifiableName = "", +} + +local _CategoryName = { + [Unit.Category.AIRPLANE] = "Airplane", + [Unit.Category.HELICOPTER] = "Helicoper", + [Unit.Category.GROUND_UNIT] = "Ground Identifiable", + [Unit.Category.SHIP] = "Ship", + [Unit.Category.STRUCTURE] = "Structure", + } + +--- Create a new IDENTIFIABLE from a DCSIdentifiable +-- @param #IDENTIFIABLE self +-- @param Dcs.DCSWrapper.Identifiable#Identifiable IdentifiableName The DCS Identifiable name +-- @return #IDENTIFIABLE self +function IDENTIFIABLE:New( IdentifiableName ) + local self = BASE:Inherit( self, OBJECT:New( IdentifiableName ) ) + self:F2( IdentifiableName ) + self.IdentifiableName = IdentifiableName + return self +end + +--- Returns if the Identifiable is alive. +-- @param #IDENTIFIABLE self +-- @return #boolean true if Identifiable is alive. +-- @return #nil The DCS Identifiable is not existing or alive. +function IDENTIFIABLE:IsAlive() + self:F3( self.IdentifiableName ) + + local DCSIdentifiable = self:GetDCSObject() + + if DCSIdentifiable then + local IdentifiableIsAlive = DCSIdentifiable:isExist() + return IdentifiableIsAlive + end + + return false +end + + + + +--- Returns DCS Identifiable object name. +-- The function provides access to non-activated objects too. +-- @param #IDENTIFIABLE self +-- @return #string The name of the DCS Identifiable. +-- @return #nil The DCS Identifiable is not existing or alive. +function IDENTIFIABLE:GetName() + self:F2( self.IdentifiableName ) + + local DCSIdentifiable = self:GetDCSObject() + + if DCSIdentifiable then + local IdentifiableName = self.IdentifiableName + return IdentifiableName + end + + self:E( self.ClassName .. " " .. self.IdentifiableName .. " not found!" ) + return nil +end + + +--- Returns the type name of the DCS Identifiable. +-- @param #IDENTIFIABLE self +-- @return #string The type name of the DCS Identifiable. +-- @return #nil The DCS Identifiable is not existing or alive. +function IDENTIFIABLE:GetTypeName() + self:F2( self.IdentifiableName ) + + local DCSIdentifiable = self:GetDCSObject() + + if DCSIdentifiable then + local IdentifiableTypeName = DCSIdentifiable:getTypeName() + self:T3( IdentifiableTypeName ) + return IdentifiableTypeName + end + + self:E( self.ClassName .. " " .. self.IdentifiableName .. " not found!" ) + return nil +end + + +--- Returns category of the DCS Identifiable. +-- @param #IDENTIFIABLE self +-- @return Dcs.DCSWrapper.Object#Object.Category The category ID +function IDENTIFIABLE:GetCategory() + self:F2( self.ObjectName ) + + local DCSObject = self:GetDCSObject() + if DCSObject then + local ObjectCategory = DCSObject:getCategory() + self:T3( ObjectCategory ) + return ObjectCategory + end + + return nil +end + + +--- Returns the DCS Identifiable category name as defined within the DCS Identifiable Descriptor. +-- @param #IDENTIFIABLE self +-- @return #string The DCS Identifiable Category Name +function IDENTIFIABLE:GetCategoryName() + local DCSIdentifiable = self:GetDCSObject() + + if DCSIdentifiable then + local IdentifiableCategoryName = _CategoryName[ self:GetDesc().category ] + return IdentifiableCategoryName + end + + self:E( self.ClassName .. " " .. self.IdentifiableName .. " not found!" ) + return nil +end + +--- Returns coalition of the Identifiable. +-- @param #IDENTIFIABLE self +-- @return Dcs.DCSCoalitionWrapper.Object#coalition.side The side of the coalition. +-- @return #nil The DCS Identifiable is not existing or alive. +function IDENTIFIABLE:GetCoalition() + self:F2( self.IdentifiableName ) + + local DCSIdentifiable = self:GetDCSObject() + + if DCSIdentifiable then + local IdentifiableCoalition = DCSIdentifiable:getCoalition() + self:T3( IdentifiableCoalition ) + return IdentifiableCoalition + end + + self:E( self.ClassName .. " " .. self.IdentifiableName .. " not found!" ) + return nil +end + +--- Returns country of the Identifiable. +-- @param #IDENTIFIABLE self +-- @return Dcs.DCScountry#country.id The country identifier. +-- @return #nil The DCS Identifiable is not existing or alive. +function IDENTIFIABLE:GetCountry() + self:F2( self.IdentifiableName ) + + local DCSIdentifiable = self:GetDCSObject() + + if DCSIdentifiable then + local IdentifiableCountry = DCSIdentifiable:getCountry() + self:T3( IdentifiableCountry ) + return IdentifiableCountry + end + + self:E( self.ClassName .. " " .. self.IdentifiableName .. " not found!" ) + return nil +end + + + +--- Returns Identifiable descriptor. Descriptor type depends on Identifiable category. +-- @param #IDENTIFIABLE self +-- @return Dcs.DCSWrapper.Identifiable#Identifiable.Desc The Identifiable descriptor. +-- @return #nil The DCS Identifiable is not existing or alive. +function IDENTIFIABLE:GetDesc() + self:F2( self.IdentifiableName ) + + local DCSIdentifiable = self:GetDCSObject() + + if DCSIdentifiable then + local IdentifiableDesc = DCSIdentifiable:getDesc() + self:T2( IdentifiableDesc ) + return IdentifiableDesc + end + + self:E( self.ClassName .. " " .. self.IdentifiableName .. " not found!" ) + return nil +end + + + + + + + + + +--- This module contains the POSITIONABLE class. +-- +-- 1) @{Wrapper.Positionable#POSITIONABLE} class, extends @{Wrapper.Identifiable#IDENTIFIABLE} +-- =========================================================== +-- The @{Wrapper.Positionable#POSITIONABLE} class is a wrapper class to handle the POSITIONABLE objects: +-- +-- * Support all DCS APIs. +-- * Enhance with POSITIONABLE specific APIs not in the DCS API set. +-- * Manage the "state" of the POSITIONABLE. +-- +-- 1.1) POSITIONABLE constructor: +-- ------------------------------ +-- The POSITIONABLE class provides the following functions to construct a POSITIONABLE instance: +-- +-- * @{Wrapper.Positionable#POSITIONABLE.New}(): Create a POSITIONABLE instance. +-- +-- 1.2) POSITIONABLE methods: +-- -------------------------- +-- The following methods can be used to identify an measurable object: +-- +-- * @{Wrapper.Positionable#POSITIONABLE.GetID}(): Returns the ID of the measurable object. +-- * @{Wrapper.Positionable#POSITIONABLE.GetName}(): Returns the name of the measurable object. +-- +-- === +-- +-- @module Positionable +-- @author FlightControl + +--- The POSITIONABLE class +-- @type POSITIONABLE +-- @extends Wrapper.Identifiable#IDENTIFIABLE +-- @field #string PositionableName The name of the measurable. +POSITIONABLE = { + ClassName = "POSITIONABLE", + PositionableName = "", +} + +--- A DCSPositionable +-- @type DCSPositionable +-- @field id_ The ID of the controllable in DCS + +--- Create a new POSITIONABLE from a DCSPositionable +-- @param #POSITIONABLE self +-- @param Dcs.DCSWrapper.Positionable#Positionable PositionableName The POSITIONABLE name +-- @return #POSITIONABLE self +function POSITIONABLE:New( PositionableName ) + local self = BASE:Inherit( self, IDENTIFIABLE:New( PositionableName ) ) + + return self +end + +--- Returns the @{Dcs.DCSTypes#Position3} position vectors indicating the point and direction vectors in 3D of the POSITIONABLE within the mission. +-- @param Wrapper.Positionable#POSITIONABLE self +-- @return Dcs.DCSTypes#Position The 3D position vectors of the POSITIONABLE. +-- @return #nil The POSITIONABLE is not existing or alive. +function POSITIONABLE:GetPositionVec3() + self:F2( self.PositionableName ) + + local DCSPositionable = self:GetDCSObject() + + if DCSPositionable then + local PositionablePosition = DCSPositionable:getPosition() + self:T3( PositionablePosition ) + return PositionablePosition + end + + return nil +end + +--- Returns the @{Dcs.DCSTypes#Vec2} vector indicating the point in 2D of the POSITIONABLE within the mission. +-- @param Wrapper.Positionable#POSITIONABLE self +-- @return Dcs.DCSTypes#Vec2 The 2D point vector of the POSITIONABLE. +-- @return #nil The POSITIONABLE is not existing or alive. +function POSITIONABLE:GetVec2() + self:F2( self.PositionableName ) + + local DCSPositionable = self:GetDCSObject() + + if DCSPositionable then + local PositionableVec3 = DCSPositionable:getPosition().p + + local PositionableVec2 = {} + PositionableVec2.x = PositionableVec3.x + PositionableVec2.y = PositionableVec3.z + + self:T2( PositionableVec2 ) + return PositionableVec2 + end + + return nil +end + +--- Returns a POINT_VEC2 object indicating the point in 2D of the POSITIONABLE within the mission. +-- @param Wrapper.Positionable#POSITIONABLE self +-- @return Core.Point#POINT_VEC2 The 2D point vector of the POSITIONABLE. +-- @return #nil The POSITIONABLE is not existing or alive. +function POSITIONABLE:GetPointVec2() + self:F2( self.PositionableName ) + + local DCSPositionable = self:GetDCSObject() + + if DCSPositionable then + local PositionableVec3 = DCSPositionable:getPosition().p + + local PositionablePointVec2 = POINT_VEC2:NewFromVec3( PositionableVec3 ) + + self:T2( PositionablePointVec2 ) + return PositionablePointVec2 + end + + return nil +end + + +--- Returns a random @{Dcs.DCSTypes#Vec3} vector within a range, indicating the point in 3D of the POSITIONABLE within the mission. +-- @param Wrapper.Positionable#POSITIONABLE self +-- @return Dcs.DCSTypes#Vec3 The 3D point vector of the POSITIONABLE. +-- @return #nil The POSITIONABLE is not existing or alive. +function POSITIONABLE:GetRandomVec3( Radius ) + self:F2( self.PositionableName ) + + local DCSPositionable = self:GetDCSObject() + + if DCSPositionable then + local PositionablePointVec3 = DCSPositionable:getPosition().p + local PositionableRandomVec3 = {} + local angle = math.random() * math.pi*2; + PositionableRandomVec3.x = PositionablePointVec3.x + math.cos( angle ) * math.random() * Radius; + PositionableRandomVec3.y = PositionablePointVec3.y + PositionableRandomVec3.z = PositionablePointVec3.z + math.sin( angle ) * math.random() * Radius; + + self:T3( PositionableRandomVec3 ) + return PositionableRandomVec3 + end + + return nil +end + +--- Returns the @{Dcs.DCSTypes#Vec3} vector indicating the 3D vector of the POSITIONABLE within the mission. +-- @param Wrapper.Positionable#POSITIONABLE self +-- @return Dcs.DCSTypes#Vec3 The 3D point vector of the POSITIONABLE. +-- @return #nil The POSITIONABLE is not existing or alive. +function POSITIONABLE:GetVec3() + self:F2( self.PositionableName ) + + local DCSPositionable = self:GetDCSObject() + + if DCSPositionable then + local PositionableVec3 = DCSPositionable:getPosition().p + self:T3( PositionableVec3 ) + return PositionableVec3 + end + + return nil +end + +--- Returns the altitude of the POSITIONABLE. +-- @param Wrapper.Positionable#POSITIONABLE self +-- @return Dcs.DCSTypes#Distance The altitude of the POSITIONABLE. +-- @return #nil The POSITIONABLE is not existing or alive. +function POSITIONABLE:GetAltitude() + self:F2() + + local DCSPositionable = self:GetDCSObject() + + if DCSPositionable then + local PositionablePointVec3 = DCSPositionable:getPoint() --Dcs.DCSTypes#Vec3 + return PositionablePointVec3.y + end + + return nil +end + +--- Returns if the Positionable is located above a runway. +-- @param Wrapper.Positionable#POSITIONABLE self +-- @return #boolean true if Positionable is above a runway. +-- @return #nil The POSITIONABLE is not existing or alive. +function POSITIONABLE:IsAboveRunway() + self:F2( self.PositionableName ) + + local DCSPositionable = self:GetDCSObject() + + if DCSPositionable then + + local Vec2 = self:GetVec2() + local SurfaceType = land.getSurfaceType( Vec2 ) + local IsAboveRunway = SurfaceType == land.SurfaceType.RUNWAY + + self:T2( IsAboveRunway ) + return IsAboveRunway + end + + return nil +end + + + +--- Returns the POSITIONABLE heading in degrees. +-- @param Wrapper.Positionable#POSITIONABLE self +-- @return #number The POSTIONABLE heading +function POSITIONABLE:GetHeading() + local DCSPositionable = self:GetDCSObject() + + if DCSPositionable then + + local PositionablePosition = DCSPositionable:getPosition() + if PositionablePosition then + local PositionableHeading = math.atan2( PositionablePosition.x.z, PositionablePosition.x.x ) + if PositionableHeading < 0 then + PositionableHeading = PositionableHeading + 2 * math.pi + end + PositionableHeading = PositionableHeading * 180 / math.pi + self:T2( PositionableHeading ) + return PositionableHeading + end + end + + return nil +end + + +--- Returns true if the POSITIONABLE is in the air. +-- @param Wrapper.Positionable#POSITIONABLE self +-- @return #boolean true if in the air. +-- @return #nil The POSITIONABLE is not existing or alive. +function POSITIONABLE:InAir() + self:F2( self.PositionableName ) + + local DCSPositionable = self:GetDCSObject() + + if DCSPositionable then + local PositionableInAir = DCSPositionable:inAir() + self:T3( PositionableInAir ) + return PositionableInAir + end + + return nil +end + + +--- Returns the POSITIONABLE velocity vector. +-- @param Wrapper.Positionable#POSITIONABLE self +-- @return Dcs.DCSTypes#Vec3 The velocity vector +-- @return #nil The POSITIONABLE is not existing or alive. +function POSITIONABLE:GetVelocity() + self:F2( self.PositionableName ) + + local DCSPositionable = self:GetDCSObject() + + if DCSPositionable then + local PositionableVelocityVec3 = DCSPositionable:getVelocity() + self:T3( PositionableVelocityVec3 ) + return PositionableVelocityVec3 + end + + return nil +end + +--- Returns the POSITIONABLE velocity in km/h. +-- @param Wrapper.Positionable#POSITIONABLE self +-- @return #number The velocity in km/h +-- @return #nil The POSITIONABLE is not existing or alive. +function POSITIONABLE:GetVelocityKMH() + self:F2( self.PositionableName ) + + local DCSPositionable = self:GetDCSObject() + + if DCSPositionable then + local VelocityVec3 = self:GetVelocity() + local Velocity = ( VelocityVec3.x ^ 2 + VelocityVec3.y ^ 2 + VelocityVec3.z ^ 2 ) ^ 0.5 -- in meters / sec + local Velocity = Velocity * 3.6 -- now it is in km/h. + self:T3( Velocity ) + return Velocity + end + + return nil +end + +--- Returns a message with the callsign embedded (if there is one). +-- @param #POSITIONABLE self +-- @param #string Message The message text +-- @param Dcs.DCSTypes#Duration Duration The duration of the message. +-- @return Core.Message#MESSAGE +function POSITIONABLE:GetMessage( Message, Duration ) + + local DCSObject = self:GetDCSObject() + if DCSObject then + return MESSAGE:New( Message, Duration, self:GetCallsign() .. " (" .. self:GetTypeName() .. ")" ) + end + + return nil +end + +--- Send a message to all coalitions. +-- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. +-- @param #POSITIONABLE self +-- @param #string Message The message text +-- @param Dcs.DCSTypes#Duration Duration The duration of the message. +function POSITIONABLE:MessageToAll( Message, Duration ) + self:F2( { Message, Duration } ) + + local DCSObject = self:GetDCSObject() + if DCSObject then + self:GetMessage( Message, Duration ):ToAll() + end + + return nil +end + +--- Send a message to a coalition. +-- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. +-- @param #POSITIONABLE self +-- @param #string Message The message text +-- @param Dcs.DCSTYpes#Duration Duration The duration of the message. +function POSITIONABLE:MessageToCoalition( Message, Duration, MessageCoalition ) + self:F2( { Message, Duration } ) + + local DCSObject = self:GetDCSObject() + if DCSObject then + self:GetMessage( Message, Duration ):ToCoalition( MessageCoalition ) + end + + return nil +end + + +--- Send a message to the red coalition. +-- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. +-- @param #POSITIONABLE self +-- @param #string Message The message text +-- @param Dcs.DCSTYpes#Duration Duration The duration of the message. +function POSITIONABLE:MessageToRed( Message, Duration ) + self:F2( { Message, Duration } ) + + local DCSObject = self:GetDCSObject() + if DCSObject then + self:GetMessage( Message, Duration ):ToRed() + end + + return nil +end + +--- Send a message to the blue coalition. +-- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. +-- @param #POSITIONABLE self +-- @param #string Message The message text +-- @param Dcs.DCSTypes#Duration Duration The duration of the message. +function POSITIONABLE:MessageToBlue( Message, Duration ) + self:F2( { Message, Duration } ) + + local DCSObject = self:GetDCSObject() + if DCSObject then + self:GetMessage( Message, Duration ):ToBlue() + end + + return nil +end + +--- Send a message to a client. +-- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. +-- @param #POSITIONABLE self +-- @param #string Message The message text +-- @param Dcs.DCSTypes#Duration Duration The duration of the message. +-- @param Wrapper.Client#CLIENT Client The client object receiving the message. +function POSITIONABLE:MessageToClient( Message, Duration, Client ) + self:F2( { Message, Duration } ) + + local DCSObject = self:GetDCSObject() + if DCSObject then + self:GetMessage( Message, Duration ):ToClient( Client ) + end + + return nil +end + +--- Send a message to a @{Group}. +-- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. +-- @param #POSITIONABLE self +-- @param #string Message The message text +-- @param Dcs.DCSTypes#Duration Duration The duration of the message. +-- @param Wrapper.Group#GROUP MessageGroup The GROUP object receiving the message. +function POSITIONABLE:MessageToGroup( Message, Duration, MessageGroup ) + self:F2( { Message, Duration } ) + + local DCSObject = self:GetDCSObject() + if DCSObject then + if DCSObject:isExist() then + self:GetMessage( Message, Duration ):ToGroup( MessageGroup ) + end + end + + return nil +end + +--- Send a message to the players in the @{Group}. +-- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. +-- @param #POSITIONABLE self +-- @param #string Message The message text +-- @param Dcs.DCSTypes#Duration Duration The duration of the message. +function POSITIONABLE:Message( Message, Duration ) + self:F2( { Message, Duration } ) + + local DCSObject = self:GetDCSObject() + if DCSObject then + self:GetMessage( Message, Duration ):ToGroup( self ) + end + + return nil +end + + + + + +--- This module contains the CONTROLLABLE class. +-- +-- 1) @{Wrapper.Controllable#CONTROLLABLE} class, extends @{Wrapper.Positionable#POSITIONABLE} +-- =========================================================== +-- The @{Wrapper.Controllable#CONTROLLABLE} class is a wrapper class to handle the DCS Controllable objects: +-- +-- * Support all DCS Controllable APIs. +-- * Enhance with Controllable specific APIs not in the DCS Controllable API set. +-- * Handle local Controllable Controller. +-- * Manage the "state" of the DCS Controllable. +-- +-- 1.1) CONTROLLABLE constructor +-- ----------------------------- +-- The CONTROLLABLE class provides the following functions to construct a CONTROLLABLE instance: +-- +-- * @{#CONTROLLABLE.New}(): Create a CONTROLLABLE instance. +-- +-- 1.2) CONTROLLABLE task methods +-- ------------------------------ +-- Several controllable task methods are available that help you to prepare tasks. +-- These methods return a string consisting of the task description, which can then be given to either a @{Wrapper.Controllable#CONTROLLABLE.PushTask} or @{Wrapper.Controllable#SetTask} method to assign the task to the CONTROLLABLE. +-- Tasks are specific for the category of the CONTROLLABLE, more specific, for AIR, GROUND or AIR and GROUND. +-- Each task description where applicable indicates for which controllable category the task is valid. +-- There are 2 main subdivisions of tasks: Assigned tasks and EnRoute tasks. +-- +-- ### 1.2.1) Assigned task methods +-- +-- Assigned task methods make the controllable execute the task where the location of the (possible) targets of the task are known before being detected. +-- This is different from the EnRoute tasks, where the targets of the task need to be detected before the task can be executed. +-- +-- Find below a list of the **assigned task** methods: +-- +-- * @{#CONTROLLABLE.TaskAttackControllable}: (AIR) Attack a Controllable. +-- * @{#CONTROLLABLE.TaskAttackMapObject}: (AIR) Attacking the map object (building, structure, e.t.c). +-- * @{#CONTROLLABLE.TaskAttackUnit}: (AIR) Attack the Unit. +-- * @{#CONTROLLABLE.TaskBombing}: (AIR) Delivering weapon at the point on the ground. +-- * @{#CONTROLLABLE.TaskBombingRunway}: (AIR) Delivering weapon on the runway. +-- * @{#CONTROLLABLE.TaskEmbarking}: (AIR) Move the controllable to a Vec2 Point, wait for a defined duration and embark a controllable. +-- * @{#CONTROLLABLE.TaskEmbarkToTransport}: (GROUND) Embark to a Transport landed at a location. +-- * @{#CONTROLLABLE.TaskEscort}: (AIR) Escort another airborne controllable. +-- * @{#CONTROLLABLE.TaskFAC_AttackControllable}: (AIR + GROUND) The task makes the controllable/unit a FAC and orders the FAC to control the target (enemy ground controllable) destruction. +-- * @{#CONTROLLABLE.TaskFireAtPoint}: (GROUND) Fire at a VEC2 point until ammunition is finished. +-- * @{#CONTROLLABLE.TaskFollow}: (AIR) Following another airborne controllable. +-- * @{#CONTROLLABLE.TaskHold}: (GROUND) Hold ground controllable from moving. +-- * @{#CONTROLLABLE.TaskHoldPosition}: (AIR) Hold position at the current position of the first unit of the controllable. +-- * @{#CONTROLLABLE.TaskLand}: (AIR HELICOPTER) Landing at the ground. For helicopters only. +-- * @{#CONTROLLABLE.TaskLandAtZone}: (AIR) Land the controllable at a @{Core.Zone#ZONE_RADIUS). +-- * @{#CONTROLLABLE.TaskOrbitCircle}: (AIR) Orbit at the current position of the first unit of the controllable at a specified alititude. +-- * @{#CONTROLLABLE.TaskOrbitCircleAtVec2}: (AIR) Orbit at a specified position at a specified alititude during a specified duration with a specified speed. +-- * @{#CONTROLLABLE.TaskRefueling}: (AIR) Refueling from the nearest tanker. No parameters. +-- * @{#CONTROLLABLE.TaskRoute}: (AIR + GROUND) Return a Misson task to follow a given route defined by Points. +-- * @{#CONTROLLABLE.TaskRouteToVec2}: (AIR + GROUND) Make the Controllable move to a given point. +-- * @{#CONTROLLABLE.TaskRouteToVec3}: (AIR + GROUND) Make the Controllable move to a given point. +-- * @{#CONTROLLABLE.TaskRouteToZone}: (AIR + GROUND) Route the controllable to a given zone. +-- * @{#CONTROLLABLE.TaskReturnToBase}: (AIR) Route the controllable to an airbase. +-- +-- ### 1.2.2) EnRoute task methods +-- +-- EnRoute tasks require the targets of the task need to be detected by the controllable (using its sensors) before the task can be executed: +-- +-- * @{#CONTROLLABLE.EnRouteTaskAWACS}: (AIR) Aircraft will act as an AWACS for friendly units (will provide them with information about contacts). No parameters. +-- * @{#CONTROLLABLE.EnRouteTaskEngageControllable}: (AIR) Engaging a controllable. The task does not assign the target controllable to the unit/controllable to attack now; it just allows the unit/controllable to engage the target controllable as well as other assigned targets. +-- * @{#CONTROLLABLE.EnRouteTaskEngageTargets}: (AIR) Engaging targets of defined types. +-- * @{#CONTROLLABLE.EnRouteTaskEWR}: (AIR) Attack the Unit. +-- * @{#CONTROLLABLE.EnRouteTaskFAC}: (AIR + GROUND) The task makes the controllable/unit a FAC and lets the FAC to choose a targets (enemy ground controllable) around as well as other assigned targets. +-- * @{#CONTROLLABLE.EnRouteTaskFAC_EngageControllable}: (AIR + GROUND) The task makes the controllable/unit a FAC and lets the FAC to choose the target (enemy ground controllable) as well as other assigned targets. +-- * @{#CONTROLLABLE.EnRouteTaskTanker}: (AIR) Aircraft will act as a tanker for friendly units. No parameters. +-- +-- ### 1.2.3) Preparation task methods +-- +-- There are certain task methods that allow to tailor the task behaviour: +-- +-- * @{#CONTROLLABLE.TaskWrappedAction}: Return a WrappedAction Task taking a Command. +-- * @{#CONTROLLABLE.TaskCombo}: Return a Combo Task taking an array of Tasks. +-- * @{#CONTROLLABLE.TaskCondition}: Return a condition section for a controlled task. +-- * @{#CONTROLLABLE.TaskControlled}: Return a Controlled Task taking a Task and a TaskCondition. +-- +-- ### 1.2.4) Obtain the mission from controllable templates +-- +-- Controllable templates contain complete mission descriptions. Sometimes you want to copy a complete mission from a controllable and assign it to another: +-- +-- * @{#CONTROLLABLE.TaskMission}: (AIR + GROUND) Return a mission task from a mission template. +-- +-- 1.3) CONTROLLABLE Command methods +-- -------------------------- +-- Controllable **command methods** prepare the execution of commands using the @{#CONTROLLABLE.SetCommand} method: +-- +-- * @{#CONTROLLABLE.CommandDoScript}: Do Script command. +-- * @{#CONTROLLABLE.CommandSwitchWayPoint}: Perform a switch waypoint command. +-- +-- 1.4) CONTROLLABLE Option methods +-- ------------------------- +-- Controllable **Option methods** change the behaviour of the Controllable while being alive. +-- +-- ### 1.4.1) Rule of Engagement: +-- +-- * @{#CONTROLLABLE.OptionROEWeaponFree} +-- * @{#CONTROLLABLE.OptionROEOpenFire} +-- * @{#CONTROLLABLE.OptionROEReturnFire} +-- * @{#CONTROLLABLE.OptionROEEvadeFire} +-- +-- To check whether an ROE option is valid for a specific controllable, use: +-- +-- * @{#CONTROLLABLE.OptionROEWeaponFreePossible} +-- * @{#CONTROLLABLE.OptionROEOpenFirePossible} +-- * @{#CONTROLLABLE.OptionROEReturnFirePossible} +-- * @{#CONTROLLABLE.OptionROEEvadeFirePossible} +-- +-- ### 1.4.2) Rule on thread: +-- +-- * @{#CONTROLLABLE.OptionROTNoReaction} +-- * @{#CONTROLLABLE.OptionROTPassiveDefense} +-- * @{#CONTROLLABLE.OptionROTEvadeFire} +-- * @{#CONTROLLABLE.OptionROTVertical} +-- +-- To test whether an ROT option is valid for a specific controllable, use: +-- +-- * @{#CONTROLLABLE.OptionROTNoReactionPossible} +-- * @{#CONTROLLABLE.OptionROTPassiveDefensePossible} +-- * @{#CONTROLLABLE.OptionROTEvadeFirePossible} +-- * @{#CONTROLLABLE.OptionROTVerticalPossible} +-- +-- === +-- +-- @module Controllable +-- @author FlightControl + +--- The CONTROLLABLE class +-- @type CONTROLLABLE +-- @extends Wrapper.Positionable#POSITIONABLE +-- @field Dcs.DCSWrapper.Controllable#Controllable DCSControllable The DCS controllable class. +-- @field #string ControllableName The name of the controllable. +CONTROLLABLE = { + ClassName = "CONTROLLABLE", + ControllableName = "", + WayPointFunctions = {}, +} + +--- Create a new CONTROLLABLE from a DCSControllable +-- @param #CONTROLLABLE self +-- @param Dcs.DCSWrapper.Controllable#Controllable ControllableName The DCS Controllable name +-- @return #CONTROLLABLE self +function CONTROLLABLE:New( ControllableName ) + local self = BASE:Inherit( self, POSITIONABLE:New( ControllableName ) ) + self:F2( ControllableName ) + self.ControllableName = ControllableName + return self +end + +-- DCS Controllable methods support. + +--- Get the controller for the CONTROLLABLE. +-- @param #CONTROLLABLE self +-- @return Dcs.DCSController#Controller +function CONTROLLABLE:_GetController() + self:F2( { self.ControllableName } ) + local DCSControllable = self:GetDCSObject() + + if DCSControllable then + local ControllableController = DCSControllable:getController() + self:T3( ControllableController ) + return ControllableController + end + + return nil +end + + + +-- Tasks + +--- Popping current Task from the controllable. +-- @param #CONTROLLABLE self +-- @return Wrapper.Controllable#CONTROLLABLE self +function CONTROLLABLE:PopCurrentTask() + self:F2() + + local DCSControllable = self:GetDCSObject() + + if DCSControllable then + local Controller = self:_GetController() + Controller:popTask() + return self + end + + return nil +end + +--- Pushing Task on the queue from the controllable. +-- @param #CONTROLLABLE self +-- @return Wrapper.Controllable#CONTROLLABLE self +function CONTROLLABLE:PushTask( DCSTask, WaitTime ) + self:F2() + + local DCSControllable = self:GetDCSObject() + + if DCSControllable then + local Controller = self:_GetController() + + -- When a controllable SPAWNs, it takes about a second to get the controllable in the simulator. Setting tasks to unspawned controllables provides unexpected results. + -- Therefore we schedule the functions to set the mission and options for the Controllable. + -- Controller:pushTask( DCSTask ) + + if WaitTime then + SCHEDULER:New( Controller, Controller.pushTask, { DCSTask }, WaitTime ) + else + Controller:pushTask( DCSTask ) + end + + return self + end + + return nil +end + +--- Clearing the Task Queue and Setting the Task on the queue from the controllable. +-- @param #CONTROLLABLE self +-- @return Wrapper.Controllable#CONTROLLABLE self +function CONTROLLABLE:SetTask( DCSTask, WaitTime ) + self:F2( { DCSTask } ) + + local DCSControllable = self:GetDCSObject() + + if DCSControllable then + + local Controller = self:_GetController() + self:E(Controller) + + -- When a controllable SPAWNs, it takes about a second to get the controllable in the simulator. Setting tasks to unspawned controllables provides unexpected results. + -- Therefore we schedule the functions to set the mission and options for the Controllable. + -- Controller.setTask( Controller, DCSTask ) + + if not WaitTime then + Controller:setTask( DCSTask ) + else + SCHEDULER:New( Controller, Controller.setTask, { DCSTask }, WaitTime ) + end + + return self + end + + return nil +end + + +--- Return a condition section for a controlled task. +-- @param #CONTROLLABLE self +-- @param Dcs.DCSTime#Time time +-- @param #string userFlag +-- @param #boolean userFlagValue +-- @param #string condition +-- @param Dcs.DCSTime#Time duration +-- @param #number lastWayPoint +-- return Dcs.DCSTasking.Task#Task +function CONTROLLABLE:TaskCondition( time, userFlag, userFlagValue, condition, duration, lastWayPoint ) + self:F2( { time, userFlag, userFlagValue, condition, duration, lastWayPoint } ) + + local DCSStopCondition = {} + DCSStopCondition.time = time + DCSStopCondition.userFlag = userFlag + DCSStopCondition.userFlagValue = userFlagValue + DCSStopCondition.condition = condition + DCSStopCondition.duration = duration + DCSStopCondition.lastWayPoint = lastWayPoint + + self:T3( { DCSStopCondition } ) + return DCSStopCondition +end + +--- Return a Controlled Task taking a Task and a TaskCondition. +-- @param #CONTROLLABLE self +-- @param Dcs.DCSTasking.Task#Task DCSTask +-- @param #DCSStopCondition DCSStopCondition +-- @return Dcs.DCSTasking.Task#Task +function CONTROLLABLE:TaskControlled( DCSTask, DCSStopCondition ) + self:F2( { DCSTask, DCSStopCondition } ) + + local DCSTaskControlled + + DCSTaskControlled = { + id = 'ControlledTask', + params = { + task = DCSTask, + stopCondition = DCSStopCondition + } + } + + self:T3( { DCSTaskControlled } ) + return DCSTaskControlled +end + +--- Return a Combo Task taking an array of Tasks. +-- @param #CONTROLLABLE self +-- @param Dcs.DCSTasking.Task#TaskArray DCSTasks Array of @{Dcs.DCSTasking.Task#Task} +-- @return Dcs.DCSTasking.Task#Task +function CONTROLLABLE:TaskCombo( DCSTasks ) + self:F2( { DCSTasks } ) + + local DCSTaskCombo + + DCSTaskCombo = { + id = 'ComboTask', + params = { + tasks = DCSTasks + } + } + + self:T3( { DCSTaskCombo } ) + return DCSTaskCombo +end + +--- Return a WrappedAction Task taking a Command. +-- @param #CONTROLLABLE self +-- @param Dcs.DCSCommand#Command DCSCommand +-- @return Dcs.DCSTasking.Task#Task +function CONTROLLABLE:TaskWrappedAction( DCSCommand, Index ) + self:F2( { DCSCommand } ) + + local DCSTaskWrappedAction + + DCSTaskWrappedAction = { + id = "WrappedAction", + enabled = true, + number = Index, + auto = false, + params = { + action = DCSCommand, + }, + } + + self:T3( { DCSTaskWrappedAction } ) + return DCSTaskWrappedAction +end + +--- Executes a command action +-- @param #CONTROLLABLE self +-- @param Dcs.DCSCommand#Command DCSCommand +-- @return #CONTROLLABLE self +function CONTROLLABLE:SetCommand( DCSCommand ) + self:F2( DCSCommand ) + + local DCSControllable = self:GetDCSObject() + + if DCSControllable then + local Controller = self:_GetController() + Controller:setCommand( DCSCommand ) + return self + end + + return nil +end + +--- Perform a switch waypoint command +-- @param #CONTROLLABLE self +-- @param #number FromWayPoint +-- @param #number ToWayPoint +-- @return Dcs.DCSTasking.Task#Task +-- @usage +-- --- This test demonstrates the use(s) of the SwitchWayPoint method of the GROUP class. +-- HeliGroup = GROUP:FindByName( "Helicopter" ) +-- +-- --- Route the helicopter back to the FARP after 60 seconds. +-- -- We use the SCHEDULER class to do this. +-- SCHEDULER:New( nil, +-- function( HeliGroup ) +-- local CommandRTB = HeliGroup:CommandSwitchWayPoint( 2, 8 ) +-- HeliGroup:SetCommand( CommandRTB ) +-- end, { HeliGroup }, 90 +-- ) +function CONTROLLABLE:CommandSwitchWayPoint( FromWayPoint, ToWayPoint ) + self:F2( { FromWayPoint, ToWayPoint } ) + + local CommandSwitchWayPoint = { + id = 'SwitchWaypoint', + params = { + fromWaypointIndex = FromWayPoint, + goToWaypointIndex = ToWayPoint, + }, + } + + self:T3( { CommandSwitchWayPoint } ) + return CommandSwitchWayPoint +end + +--- Perform stop route command +-- @param #CONTROLLABLE self +-- @param #boolean StopRoute +-- @return Dcs.DCSTasking.Task#Task +function CONTROLLABLE:CommandStopRoute( StopRoute, Index ) + self:F2( { StopRoute, Index } ) + + local CommandStopRoute = { + id = 'StopRoute', + params = { + value = StopRoute, + }, + } + + self:T3( { CommandStopRoute } ) + return CommandStopRoute +end + + +-- TASKS FOR AIR CONTROLLABLES + + +--- (AIR) Attack a Controllable. +-- @param #CONTROLLABLE self +-- @param Wrapper.Controllable#CONTROLLABLE AttackGroup The Controllable to be attacked. +-- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. +-- @param Dcs.DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. +-- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. +-- @param Dcs.DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. +-- @param Dcs.DCSTypes#Distance Altitude (optional) Desired attack start altitude. Controllable/aircraft will make its attacks from the altitude. If the altitude is too low or too high to use weapon aircraft/controllable will choose closest altitude to the desired attack start altitude. If the desired altitude is defined controllable/aircraft will not attack from safe altitude. +-- @param #boolean AttackQtyLimit (optional) The flag determines how to interpret attackQty parameter. If the flag is true then attackQty is a limit on maximal attack quantity for "AttackControllable" and "AttackUnit" tasks. If the flag is false then attackQty is a desired attack quantity for "Bombing" and "BombingRunway" tasks. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. +function CONTROLLABLE:TaskAttackGroup( AttackGroup, WeaponType, WeaponExpend, AttackQty, Direction, Altitude, AttackQtyLimit ) + self:F2( { self.ControllableName, AttackGroup, WeaponType, WeaponExpend, AttackQty, Direction, Altitude, AttackQtyLimit } ) + + -- AttackControllable = { + -- id = 'AttackControllable', + -- params = { + -- groupId = Group.ID, + -- weaponType = number, + -- expend = enum AI.Task.WeaponExpend, + -- attackQty = number, + -- directionEnabled = boolean, + -- direction = Azimuth, + -- altitudeEnabled = boolean, + -- altitude = Distance, + -- attackQtyLimit = boolean, + -- } + -- } + + local DirectionEnabled = nil + if Direction then + DirectionEnabled = true + end + + local AltitudeEnabled = nil + if Altitude then + AltitudeEnabled = true + end + + local DCSTask + DCSTask = { id = 'AttackControllable', + params = { + groupId = AttackGroup:GetID(), + weaponType = WeaponType, + expend = WeaponExpend, + attackQty = AttackQty, + directionEnabled = DirectionEnabled, + direction = Direction, + altitudeEnabled = AltitudeEnabled, + altitude = Altitude, + attackQtyLimit = AttackQtyLimit, + }, + }, + + self:T3( { DCSTask } ) + return DCSTask +end + + +--- (AIR) Attack the Unit. +-- @param #CONTROLLABLE self +-- @param Wrapper.Unit#UNIT AttackUnit The unit. +-- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. +-- @param Dcs.DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. +-- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. +-- @param Dcs.DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. +-- @param #boolean AttackQtyLimit (optional) The flag determines how to interpret attackQty parameter. If the flag is true then attackQty is a limit on maximal attack quantity for "AttackControllable" and "AttackUnit" tasks. If the flag is false then attackQty is a desired attack quantity for "Bombing" and "BombingRunway" tasks. +-- @param #boolean ControllableAttack (optional) Flag indicates that the target must be engaged by all aircrafts of the controllable. Has effect only if the task is assigned to a controllable, not to a single aircraft. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. +function CONTROLLABLE:TaskAttackUnit( AttackUnit, WeaponType, WeaponExpend, AttackQty, Direction, AttackQtyLimit, ControllableAttack ) + self:F2( { self.ControllableName, AttackUnit, WeaponType, WeaponExpend, AttackQty, Direction, AttackQtyLimit, ControllableAttack } ) + + -- AttackUnit = { + -- id = 'AttackUnit', + -- params = { + -- unitId = Unit.ID, + -- weaponType = number, + -- expend = enum AI.Task.WeaponExpend + -- attackQty = number, + -- direction = Azimuth, + -- attackQtyLimit = boolean, + -- controllableAttack = boolean, + -- } + -- } + + local DCSTask + DCSTask = { id = 'AttackUnit', + params = { + unitId = AttackUnit:GetID(), + weaponType = WeaponType, + expend = WeaponExpend, + attackQty = AttackQty, + direction = Direction, + attackQtyLimit = AttackQtyLimit, + controllableAttack = ControllableAttack, + }, + }, + + self:T3( { DCSTask } ) + return DCSTask +end + + +--- (AIR) Delivering weapon at the point on the ground. +-- @param #CONTROLLABLE self +-- @param Dcs.DCSTypes#Vec2 Vec2 2D-coordinates of the point to deliver weapon at. +-- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. +-- @param Dcs.DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. +-- @param #number AttackQty (optional) Desired quantity of passes. The parameter is not the same in AttackControllable and AttackUnit tasks. +-- @param Dcs.DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. +-- @param #boolean ControllableAttack (optional) Flag indicates that the target must be engaged by all aircrafts of the controllable. Has effect only if the task is assigned to a controllable, not to a single aircraft. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. +function CONTROLLABLE:TaskBombing( Vec2, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack ) + self:F2( { self.ControllableName, Vec2, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack } ) + +-- Bombing = { +-- id = 'Bombing', +-- params = { +-- point = Vec2, +-- weaponType = number, +-- expend = enum AI.Task.WeaponExpend, +-- attackQty = number, +-- direction = Azimuth, +-- controllableAttack = boolean, +-- } +-- } + + local DCSTask + DCSTask = { id = 'Bombing', + params = { + point = Vec2, + weaponType = WeaponType, + expend = WeaponExpend, + attackQty = AttackQty, + direction = Direction, + controllableAttack = ControllableAttack, + }, + }, + + self:T3( { DCSTask } ) + return DCSTask +end + +--- (AIR) Orbit at a specified position at a specified alititude during a specified duration with a specified speed. +-- @param #CONTROLLABLE self +-- @param Dcs.DCSTypes#Vec2 Point The point to hold the position. +-- @param #number Altitude The altitude to hold the position. +-- @param #number Speed The speed flying when holding the position. +-- @return #CONTROLLABLE self +function CONTROLLABLE:TaskOrbitCircleAtVec2( Point, Altitude, Speed ) + self:F2( { self.ControllableName, Point, Altitude, Speed } ) + + -- pattern = enum AI.Task.OribtPattern, + -- point = Vec2, + -- point2 = Vec2, + -- speed = Distance, + -- altitude = Distance + + local LandHeight = land.getHeight( Point ) + + self:T3( { LandHeight } ) + + local DCSTask = { id = 'Orbit', + params = { pattern = AI.Task.OrbitPattern.CIRCLE, + point = Point, + speed = Speed, + altitude = Altitude + LandHeight + } + } + + + -- local AITask = { id = 'ControlledTask', + -- params = { task = { id = 'Orbit', + -- params = { pattern = AI.Task.OrbitPattern.CIRCLE, + -- point = Point, + -- speed = Speed, + -- altitude = Altitude + LandHeight + -- } + -- }, + -- stopCondition = { duration = Duration + -- } + -- } + -- } + -- ) + + return DCSTask +end + +--- (AIR) Orbit at the current position of the first unit of the controllable at a specified alititude. +-- @param #CONTROLLABLE self +-- @param #number Altitude The altitude to hold the position. +-- @param #number Speed The speed flying when holding the position. +-- @return #CONTROLLABLE self +function CONTROLLABLE:TaskOrbitCircle( Altitude, Speed ) + self:F2( { self.ControllableName, Altitude, Speed } ) + + local DCSControllable = self:GetDCSObject() + + if DCSControllable then + local ControllablePoint = self:GetVec2() + return self:TaskOrbitCircleAtVec2( ControllablePoint, Altitude, Speed ) + end + + return nil +end + + + +--- (AIR) Hold position at the current position of the first unit of the controllable. +-- @param #CONTROLLABLE self +-- @param #number Duration The maximum duration in seconds to hold the position. +-- @return #CONTROLLABLE self +function CONTROLLABLE:TaskHoldPosition() + self:F2( { self.ControllableName } ) + + return self:TaskOrbitCircle( 30, 10 ) +end + + + + +--- (AIR) Attacking the map object (building, structure, e.t.c). +-- @param #CONTROLLABLE self +-- @param Dcs.DCSTypes#Vec2 Vec2 2D-coordinates of the point the map object is closest to. The distance between the point and the map object must not be greater than 2000 meters. Object id is not used here because Mission Editor doesn't support map object identificators. +-- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. +-- @param Dcs.DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. +-- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. +-- @param Dcs.DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. +-- @param #boolean ControllableAttack (optional) Flag indicates that the target must be engaged by all aircrafts of the controllable. Has effect only if the task is assigned to a controllable, not to a single aircraft. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. +function CONTROLLABLE:TaskAttackMapObject( Vec2, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack ) + self:F2( { self.ControllableName, Vec2, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack } ) + +-- AttackMapObject = { +-- id = 'AttackMapObject', +-- params = { +-- point = Vec2, +-- weaponType = number, +-- expend = enum AI.Task.WeaponExpend, +-- attackQty = number, +-- direction = Azimuth, +-- controllableAttack = boolean, +-- } +-- } + + local DCSTask + DCSTask = { id = 'AttackMapObject', + params = { + point = Vec2, + weaponType = WeaponType, + expend = WeaponExpend, + attackQty = AttackQty, + direction = Direction, + controllableAttack = ControllableAttack, + }, + }, + + self:T3( { DCSTask } ) + return DCSTask +end + + +--- (AIR) Delivering weapon on the runway. +-- @param #CONTROLLABLE self +-- @param Wrapper.Airbase#AIRBASE Airbase Airbase to attack. +-- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. +-- @param Dcs.DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. +-- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. +-- @param Dcs.DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. +-- @param #boolean ControllableAttack (optional) Flag indicates that the target must be engaged by all aircrafts of the controllable. Has effect only if the task is assigned to a controllable, not to a single aircraft. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. +function CONTROLLABLE:TaskBombingRunway( Airbase, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack ) + self:F2( { self.ControllableName, Airbase, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack } ) + +-- BombingRunway = { +-- id = 'BombingRunway', +-- params = { +-- runwayId = AirdromeId, +-- weaponType = number, +-- expend = enum AI.Task.WeaponExpend, +-- attackQty = number, +-- direction = Azimuth, +-- controllableAttack = boolean, +-- } +-- } + + local DCSTask + DCSTask = { id = 'BombingRunway', + params = { + point = Airbase:GetID(), + weaponType = WeaponType, + expend = WeaponExpend, + attackQty = AttackQty, + direction = Direction, + controllableAttack = ControllableAttack, + }, + }, + + self:T3( { DCSTask } ) + return DCSTask +end + + +--- (AIR) Refueling from the nearest tanker. No parameters. +-- @param #CONTROLLABLE self +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. +function CONTROLLABLE:TaskRefueling() + self:F2( { self.ControllableName } ) + +-- Refueling = { +-- id = 'Refueling', +-- params = {} +-- } + + local DCSTask + DCSTask = { id = 'Refueling', + params = { + }, + }, + + self:T3( { DCSTask } ) + return DCSTask +end + + +--- (AIR HELICOPTER) Landing at the ground. For helicopters only. +-- @param #CONTROLLABLE self +-- @param Dcs.DCSTypes#Vec2 Point The point where to land. +-- @param #number Duration The duration in seconds to stay on the ground. +-- @return #CONTROLLABLE self +function CONTROLLABLE:TaskLandAtVec2( Point, Duration ) + self:F2( { self.ControllableName, Point, Duration } ) + +-- Land = { +-- id= 'Land', +-- params = { +-- point = Vec2, +-- durationFlag = boolean, +-- duration = Time +-- } +-- } + + local DCSTask + if Duration and Duration > 0 then + DCSTask = { id = 'Land', + params = { + point = Point, + durationFlag = true, + duration = Duration, + }, + } + else + DCSTask = { id = 'Land', + params = { + point = Point, + durationFlag = false, + }, + } + end + + self:T3( DCSTask ) + return DCSTask +end + +--- (AIR) Land the controllable at a @{Core.Zone#ZONE_RADIUS). +-- @param #CONTROLLABLE self +-- @param Core.Zone#ZONE Zone The zone where to land. +-- @param #number Duration The duration in seconds to stay on the ground. +-- @return #CONTROLLABLE self +function CONTROLLABLE:TaskLandAtZone( Zone, Duration, RandomPoint ) + self:F2( { self.ControllableName, Zone, Duration, RandomPoint } ) + + local Point + if RandomPoint then + Point = Zone:GetRandomVec2() + else + Point = Zone:GetVec2() + end + + local DCSTask = self:TaskLandAtVec2( Point, Duration ) + + self:T3( DCSTask ) + return DCSTask +end + + + +--- (AIR) Following another airborne controllable. +-- The unit / controllable will follow lead unit of another controllable, wingmens of both controllables will continue following their leaders. +-- If another controllable is on land the unit / controllable will orbit around. +-- @param #CONTROLLABLE self +-- @param Wrapper.Controllable#CONTROLLABLE FollowControllable The controllable to be followed. +-- @param Dcs.DCSTypes#Vec3 Vec3 Position of the unit / lead unit of the controllable relative lead unit of another controllable in frame reference oriented by course of lead unit of another controllable. If another controllable is on land the unit / controllable will orbit around. +-- @param #number LastWaypointIndex Detach waypoint of another controllable. Once reached the unit / controllable Follow task is finished. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. +function CONTROLLABLE:TaskFollow( FollowControllable, Vec3, LastWaypointIndex ) + self:F2( { self.ControllableName, FollowControllable, Vec3, LastWaypointIndex } ) + +-- Follow = { +-- id = 'Follow', +-- params = { +-- groupId = Group.ID, +-- pos = Vec3, +-- lastWptIndexFlag = boolean, +-- lastWptIndex = number +-- } +-- } + + local LastWaypointIndexFlag = false + if LastWaypointIndex then + LastWaypointIndexFlag = true + end + + local DCSTask + DCSTask = { + id = 'Follow', + params = { + groupId = FollowControllable:GetID(), + pos = Vec3, + lastWptIndexFlag = LastWaypointIndexFlag, + lastWptIndex = LastWaypointIndex + } + } + + self:T3( { DCSTask } ) + return DCSTask +end + + +--- (AIR) Escort another airborne controllable. +-- The unit / controllable will follow lead unit of another controllable, wingmens of both controllables will continue following their leaders. +-- The unit / controllable will also protect that controllable from threats of specified types. +-- @param #CONTROLLABLE self +-- @param Wrapper.Controllable#CONTROLLABLE EscortControllable The controllable to be escorted. +-- @param Dcs.DCSTypes#Vec3 Vec3 Position of the unit / lead unit of the controllable relative lead unit of another controllable in frame reference oriented by course of lead unit of another controllable. If another controllable is on land the unit / controllable will orbit around. +-- @param #number LastWaypointIndex Detach waypoint of another controllable. Once reached the unit / controllable Follow task is finished. +-- @param #number EngagementDistanceMax Maximal distance from escorted controllable to threat. If the threat is already engaged by escort escort will disengage if the distance becomes greater than 1.5 * engagementDistMax. +-- @param Dcs.DCSTypes#AttributeNameArray TargetTypes Array of AttributeName that is contains threat categories allowed to engage. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. +function CONTROLLABLE:TaskEscort( FollowControllable, Vec3, LastWaypointIndex, EngagementDistance, TargetTypes ) + self:F2( { self.ControllableName, FollowControllable, Vec3, LastWaypointIndex, EngagementDistance, TargetTypes } ) + +-- Escort = { +-- id = 'Escort', +-- params = { +-- groupId = Group.ID, +-- pos = Vec3, +-- lastWptIndexFlag = boolean, +-- lastWptIndex = number, +-- engagementDistMax = Distance, +-- targetTypes = array of AttributeName, +-- } +-- } + + local LastWaypointIndexFlag = false + if LastWaypointIndex then + LastWaypointIndexFlag = true + end + + local DCSTask + DCSTask = { id = 'Escort', + params = { + groupId = FollowControllable:GetID(), + pos = Vec3, + lastWptIndexFlag = LastWaypointIndexFlag, + lastWptIndex = LastWaypointIndex, + engagementDistMax = EngagementDistance, + targetTypes = TargetTypes, + }, + }, + + self:T3( { DCSTask } ) + return DCSTask +end + + +-- GROUND TASKS + +--- (GROUND) Fire at a VEC2 point until ammunition is finished. +-- @param #CONTROLLABLE self +-- @param Dcs.DCSTypes#Vec2 Vec2 The point to fire at. +-- @param Dcs.DCSTypes#Distance Radius The radius of the zone to deploy the fire at. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. +function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius ) + self:F2( { self.ControllableName, Vec2, Radius } ) + + -- FireAtPoint = { + -- id = 'FireAtPoint', + -- params = { + -- point = Vec2, + -- radius = Distance, + -- } + -- } + + local DCSTask + DCSTask = { id = 'FireAtPoint', + params = { + point = Vec2, + radius = Radius, + } + } + + self:T3( { DCSTask } ) + return DCSTask +end + +--- (GROUND) Hold ground controllable from moving. +-- @param #CONTROLLABLE self +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. +function CONTROLLABLE:TaskHold() + self:F2( { self.ControllableName } ) + +-- Hold = { +-- id = 'Hold', +-- params = { +-- } +-- } + + local DCSTask + DCSTask = { id = 'Hold', + params = { + } + } + + self:T3( { DCSTask } ) + return DCSTask +end + + +-- TASKS FOR AIRBORNE AND GROUND UNITS/CONTROLLABLES + +--- (AIR + GROUND) The task makes the controllable/unit a FAC and orders the FAC to control the target (enemy ground controllable) destruction. +-- The killer is player-controlled allied CAS-aircraft that is in contact with the FAC. +-- If the task is assigned to the controllable lead unit will be a FAC. +-- @param #CONTROLLABLE self +-- @param Wrapper.Controllable#CONTROLLABLE AttackGroup Target CONTROLLABLE. +-- @param #number WeaponType Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. +-- @param Dcs.DCSTypes#AI.Task.Designation Designation (optional) Designation type. +-- @param #boolean Datalink (optional) Allows to use datalink to send the target information to attack aircraft. Enabled by default. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. +function CONTROLLABLE:TaskFAC_AttackGroup( AttackGroup, WeaponType, Designation, Datalink ) + self:F2( { self.ControllableName, AttackGroup, WeaponType, Designation, Datalink } ) + +-- FAC_AttackControllable = { +-- id = 'FAC_AttackControllable', +-- params = { +-- groupId = Group.ID, +-- weaponType = number, +-- designation = enum AI.Task.Designation, +-- datalink = boolean +-- } +-- } + + local DCSTask + DCSTask = { id = 'FAC_AttackControllable', + params = { + groupId = AttackGroup:GetID(), + weaponType = WeaponType, + designation = Designation, + datalink = Datalink, + } + } + + self:T3( { DCSTask } ) + return DCSTask +end + +-- EN-ACT_ROUTE TASKS FOR AIRBORNE CONTROLLABLES + +--- (AIR) Engaging targets of defined types. +-- @param #CONTROLLABLE self +-- @param Dcs.DCSTypes#Distance Distance Maximal distance from the target to a route leg. If the target is on a greater distance it will be ignored. +-- @param Dcs.DCSTypes#AttributeNameArray TargetTypes Array of target categories allowed to engage. +-- @param #number Priority All enroute tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. +function CONTROLLABLE:EnRouteTaskEngageTargets( Distance, TargetTypes, Priority ) + self:F2( { self.ControllableName, Distance, TargetTypes, Priority } ) + +-- EngageTargets ={ +-- id = 'EngageTargets', +-- params = { +-- maxDist = Distance, +-- targetTypes = array of AttributeName, +-- priority = number +-- } +-- } + + local DCSTask + DCSTask = { id = 'EngageTargets', + params = { + maxDist = Distance, + targetTypes = TargetTypes, + priority = Priority + } + } + + self:T3( { DCSTask } ) + return DCSTask +end + + + +--- (AIR) Engaging a targets of defined types at circle-shaped zone. +-- @param #CONTROLLABLE self +-- @param Dcs.DCSTypes#Vec2 Vec2 2D-coordinates of the zone. +-- @param Dcs.DCSTypes#Distance Radius Radius of the zone. +-- @param Dcs.DCSTypes#AttributeNameArray TargetTypes Array of target categories allowed to engage. +-- @param #number Priority All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. +function CONTROLLABLE:EnRouteTaskEngageTargets( Vec2, Radius, TargetTypes, Priority ) + self:F2( { self.ControllableName, Vec2, Radius, TargetTypes, Priority } ) + +-- EngageTargetsInZone = { +-- id = 'EngageTargetsInZone', +-- params = { +-- point = Vec2, +-- zoneRadius = Distance, +-- targetTypes = array of AttributeName, +-- priority = number +-- } +-- } + + local DCSTask + DCSTask = { id = 'EngageTargetsInZone', + params = { + point = Vec2, + zoneRadius = Radius, + targetTypes = TargetTypes, + priority = Priority + } + } + + self:T3( { DCSTask } ) + return DCSTask +end + + +--- (AIR) Engaging a controllable. The task does not assign the target controllable to the unit/controllable to attack now; it just allows the unit/controllable to engage the target controllable as well as other assigned targets. +-- @param #CONTROLLABLE self +-- @param Wrapper.Controllable#CONTROLLABLE AttackGroup The Controllable to be attacked. +-- @param #number Priority All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. +-- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. +-- @param Dcs.DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. +-- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. +-- @param Dcs.DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. +-- @param Dcs.DCSTypes#Distance Altitude (optional) Desired attack start altitude. Controllable/aircraft will make its attacks from the altitude. If the altitude is too low or too high to use weapon aircraft/controllable will choose closest altitude to the desired attack start altitude. If the desired altitude is defined controllable/aircraft will not attack from safe altitude. +-- @param #boolean AttackQtyLimit (optional) The flag determines how to interpret attackQty parameter. If the flag is true then attackQty is a limit on maximal attack quantity for "AttackControllable" and "AttackUnit" tasks. If the flag is false then attackQty is a desired attack quantity for "Bombing" and "BombingRunway" tasks. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. +function CONTROLLABLE:EnRouteTaskEngageGroup( AttackGroup, Priority, WeaponType, WeaponExpend, AttackQty, Direction, Altitude, AttackQtyLimit ) + self:F2( { self.ControllableName, AttackGroup, Priority, WeaponType, WeaponExpend, AttackQty, Direction, Altitude, AttackQtyLimit } ) + + -- EngageControllable = { + -- id = 'EngageControllable ', + -- params = { + -- groupId = Group.ID, + -- weaponType = number, + -- expend = enum AI.Task.WeaponExpend, + -- attackQty = number, + -- directionEnabled = boolean, + -- direction = Azimuth, + -- altitudeEnabled = boolean, + -- altitude = Distance, + -- attackQtyLimit = boolean, + -- priority = number, + -- } + -- } + + local DirectionEnabled = nil + if Direction then + DirectionEnabled = true + end + + local AltitudeEnabled = nil + if Altitude then + AltitudeEnabled = true + end + + local DCSTask + DCSTask = { id = 'EngageControllable', + params = { + groupId = AttackGroup:GetID(), + weaponType = WeaponType, + expend = WeaponExpend, + attackQty = AttackQty, + directionEnabled = DirectionEnabled, + direction = Direction, + altitudeEnabled = AltitudeEnabled, + altitude = Altitude, + attackQtyLimit = AttackQtyLimit, + priority = Priority, + }, + }, + + self:T3( { DCSTask } ) + return DCSTask +end + + +--- (AIR) Attack the Unit. +-- @param #CONTROLLABLE self +-- @param Wrapper.Unit#UNIT AttackUnit The UNIT. +-- @param #number Priority All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. +-- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. +-- @param Dcs.DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. +-- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. +-- @param Dcs.DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. +-- @param #boolean AttackQtyLimit (optional) The flag determines how to interpret attackQty parameter. If the flag is true then attackQty is a limit on maximal attack quantity for "AttackControllable" and "AttackUnit" tasks. If the flag is false then attackQty is a desired attack quantity for "Bombing" and "BombingRunway" tasks. +-- @param #boolean ControllableAttack (optional) Flag indicates that the target must be engaged by all aircrafts of the controllable. Has effect only if the task is assigned to a controllable, not to a single aircraft. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. +function CONTROLLABLE:EnRouteTaskEngageUnit( AttackUnit, Priority, WeaponType, WeaponExpend, AttackQty, Direction, AttackQtyLimit, ControllableAttack ) + self:F2( { self.ControllableName, AttackUnit, Priority, WeaponType, WeaponExpend, AttackQty, Direction, AttackQtyLimit, ControllableAttack } ) + + -- EngageUnit = { + -- id = 'EngageUnit', + -- params = { + -- unitId = Unit.ID, + -- weaponType = number, + -- expend = enum AI.Task.WeaponExpend + -- attackQty = number, + -- direction = Azimuth, + -- attackQtyLimit = boolean, + -- controllableAttack = boolean, + -- priority = number, + -- } + -- } + + local DCSTask + DCSTask = { id = 'EngageUnit', + params = { + unitId = AttackUnit:GetID(), + weaponType = WeaponType, + expend = WeaponExpend, + attackQty = AttackQty, + direction = Direction, + attackQtyLimit = AttackQtyLimit, + controllableAttack = ControllableAttack, + priority = Priority, + }, + }, + + self:T3( { DCSTask } ) + return DCSTask +end + + + +--- (AIR) Aircraft will act as an AWACS for friendly units (will provide them with information about contacts). No parameters. +-- @param #CONTROLLABLE self +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. +function CONTROLLABLE:EnRouteTaskAWACS( ) + self:F2( { self.ControllableName } ) + +-- AWACS = { +-- id = 'AWACS', +-- params = { +-- } +-- } + + local DCSTask + DCSTask = { id = 'AWACS', + params = { + } + } + + self:T3( { DCSTask } ) + return DCSTask +end + + +--- (AIR) Aircraft will act as a tanker for friendly units. No parameters. +-- @param #CONTROLLABLE self +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. +function CONTROLLABLE:EnRouteTaskTanker( ) + self:F2( { self.ControllableName } ) + +-- Tanker = { +-- id = 'Tanker', +-- params = { +-- } +-- } + + local DCSTask + DCSTask = { id = 'Tanker', + params = { + } + } + + self:T3( { DCSTask } ) + return DCSTask +end + + +-- En-route tasks for ground units/controllables + +--- (GROUND) Ground unit (EW-radar) will act as an EWR for friendly units (will provide them with information about contacts). No parameters. +-- @param #CONTROLLABLE self +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. +function CONTROLLABLE:EnRouteTaskEWR( ) + self:F2( { self.ControllableName } ) + +-- EWR = { +-- id = 'EWR', +-- params = { +-- } +-- } + + local DCSTask + DCSTask = { id = 'EWR', + params = { + } + } + + self:T3( { DCSTask } ) + return DCSTask +end + + +-- En-route tasks for airborne and ground units/controllables + +--- (AIR + GROUND) The task makes the controllable/unit a FAC and lets the FAC to choose the target (enemy ground controllable) as well as other assigned targets. +-- The killer is player-controlled allied CAS-aircraft that is in contact with the FAC. +-- If the task is assigned to the controllable lead unit will be a FAC. +-- @param #CONTROLLABLE self +-- @param Wrapper.Controllable#CONTROLLABLE AttackGroup Target CONTROLLABLE. +-- @param #number Priority All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. +-- @param #number WeaponType Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. +-- @param Dcs.DCSTypes#AI.Task.Designation Designation (optional) Designation type. +-- @param #boolean Datalink (optional) Allows to use datalink to send the target information to attack aircraft. Enabled by default. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. +function CONTROLLABLE:EnRouteTaskFAC_EngageGroup( AttackGroup, Priority, WeaponType, Designation, Datalink ) + self:F2( { self.ControllableName, AttackGroup, WeaponType, Priority, Designation, Datalink } ) + +-- FAC_EngageControllable = { +-- id = 'FAC_EngageControllable', +-- params = { +-- groupId = Group.ID, +-- weaponType = number, +-- designation = enum AI.Task.Designation, +-- datalink = boolean, +-- priority = number, +-- } +-- } + + local DCSTask + DCSTask = { id = 'FAC_EngageControllable', + params = { + groupId = AttackGroup:GetID(), + weaponType = WeaponType, + designation = Designation, + datalink = Datalink, + priority = Priority, + } + } + + self:T3( { DCSTask } ) + return DCSTask +end + + +--- (AIR + GROUND) The task makes the controllable/unit a FAC and lets the FAC to choose a targets (enemy ground controllable) around as well as other assigned targets. +-- The killer is player-controlled allied CAS-aircraft that is in contact with the FAC. +-- If the task is assigned to the controllable lead unit will be a FAC. +-- @param #CONTROLLABLE self +-- @param Dcs.DCSTypes#Distance Radius The maximal distance from the FAC to a target. +-- @param #number Priority All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. +function CONTROLLABLE:EnRouteTaskFAC( Radius, Priority ) + self:F2( { self.ControllableName, Radius, Priority } ) + +-- FAC = { +-- id = 'FAC', +-- params = { +-- radius = Distance, +-- priority = number +-- } +-- } + + local DCSTask + DCSTask = { id = 'FAC', + params = { + radius = Radius, + priority = Priority + } + } + + self:T3( { DCSTask } ) + return DCSTask +end + + + + +--- (AIR) Move the controllable to a Vec2 Point, wait for a defined duration and embark a controllable. +-- @param #CONTROLLABLE self +-- @param Dcs.DCSTypes#Vec2 Point The point where to wait. +-- @param #number Duration The duration in seconds to wait. +-- @param #CONTROLLABLE EmbarkingControllable The controllable to be embarked. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure +function CONTROLLABLE:TaskEmbarking( Point, Duration, EmbarkingControllable ) + self:F2( { self.ControllableName, Point, Duration, EmbarkingControllable.DCSControllable } ) + + local DCSTask + DCSTask = { id = 'Embarking', + params = { x = Point.x, + y = Point.y, + duration = Duration, + controllablesForEmbarking = { EmbarkingControllable.ControllableID }, + durationFlag = true, + distributionFlag = false, + distribution = {}, + } + } + + self:T3( { DCSTask } ) + return DCSTask +end + +--- (GROUND) Embark to a Transport landed at a location. + +--- Move to a defined Vec2 Point, and embark to a controllable when arrived within a defined Radius. +-- @param #CONTROLLABLE self +-- @param Dcs.DCSTypes#Vec2 Point The point where to wait. +-- @param #number Radius The radius of the embarking zone around the Point. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. +function CONTROLLABLE:TaskEmbarkToTransport( Point, Radius ) + self:F2( { self.ControllableName, Point, Radius } ) + + local DCSTask --Dcs.DCSTasking.Task#Task + DCSTask = { id = 'EmbarkToTransport', + params = { x = Point.x, + y = Point.y, + zoneRadius = Radius, + } + } + + self:T3( { DCSTask } ) + return DCSTask +end + + + +--- (AIR + GROUND) Return a mission task from a mission template. +-- @param #CONTROLLABLE self +-- @param #table TaskMission A table containing the mission task. +-- @return Dcs.DCSTasking.Task#Task +function CONTROLLABLE:TaskMission( TaskMission ) + self:F2( Points ) + + local DCSTask + DCSTask = { id = 'Mission', params = { TaskMission, }, } + + self:T3( { DCSTask } ) + return DCSTask +end + +--- Return a Misson task to follow a given route defined by Points. +-- @param #CONTROLLABLE self +-- @param #table Points A table of route points. +-- @return Dcs.DCSTasking.Task#Task +function CONTROLLABLE:TaskRoute( Points ) + self:F2( Points ) + + local DCSTask + DCSTask = { id = 'Mission', params = { route = { points = Points, }, }, } + + self:T3( { DCSTask } ) + return DCSTask +end + +--- (AIR + GROUND) Make the Controllable move to fly to a given point. +-- @param #CONTROLLABLE self +-- @param Dcs.DCSTypes#Vec3 Point The destination point in Vec3 format. +-- @param #number Speed The speed to travel. +-- @return #CONTROLLABLE self +function CONTROLLABLE:TaskRouteToVec2( Point, Speed ) + self:F2( { Point, Speed } ) + + local ControllablePoint = self:GetUnit( 1 ):GetVec2() + + local PointFrom = {} + PointFrom.x = ControllablePoint.x + PointFrom.y = ControllablePoint.y + PointFrom.type = "Turning Point" + PointFrom.action = "Turning Point" + PointFrom.speed = Speed + PointFrom.speed_locked = true + PointFrom.properties = { + ["vnav"] = 1, + ["scale"] = 0, + ["angle"] = 0, + ["vangle"] = 0, + ["steer"] = 2, + } + + + local PointTo = {} + PointTo.x = Point.x + PointTo.y = Point.y + PointTo.type = "Turning Point" + PointTo.action = "Fly Over Point" + PointTo.speed = Speed + PointTo.speed_locked = true + PointTo.properties = { + ["vnav"] = 1, + ["scale"] = 0, + ["angle"] = 0, + ["vangle"] = 0, + ["steer"] = 2, + } + + + local Points = { PointFrom, PointTo } + + self:T3( Points ) + + self:Route( Points ) + + return self +end + +--- (AIR + GROUND) Make the Controllable move to a given point. +-- @param #CONTROLLABLE self +-- @param Dcs.DCSTypes#Vec3 Point The destination point in Vec3 format. +-- @param #number Speed The speed to travel. +-- @return #CONTROLLABLE self +function CONTROLLABLE:TaskRouteToVec3( Point, Speed ) + self:F2( { Point, Speed } ) + + local ControllableVec3 = self:GetUnit( 1 ):GetVec3() + + local PointFrom = {} + PointFrom.x = ControllableVec3.x + PointFrom.y = ControllableVec3.z + PointFrom.alt = ControllableVec3.y + PointFrom.alt_type = "BARO" + PointFrom.type = "Turning Point" + PointFrom.action = "Turning Point" + PointFrom.speed = Speed + PointFrom.speed_locked = true + PointFrom.properties = { + ["vnav"] = 1, + ["scale"] = 0, + ["angle"] = 0, + ["vangle"] = 0, + ["steer"] = 2, + } + + + local PointTo = {} + PointTo.x = Point.x + PointTo.y = Point.z + PointTo.alt = Point.y + PointTo.alt_type = "BARO" + PointTo.type = "Turning Point" + PointTo.action = "Fly Over Point" + PointTo.speed = Speed + PointTo.speed_locked = true + PointTo.properties = { + ["vnav"] = 1, + ["scale"] = 0, + ["angle"] = 0, + ["vangle"] = 0, + ["steer"] = 2, + } + + + local Points = { PointFrom, PointTo } + + self:T3( Points ) + + self:Route( Points ) + + return self +end + + + +--- Make the controllable to follow a given route. +-- @param #CONTROLLABLE self +-- @param #table GoPoints A table of Route Points. +-- @return #CONTROLLABLE self +function CONTROLLABLE:Route( GoPoints ) + self:F2( GoPoints ) + + local DCSControllable = self:GetDCSObject() + + if DCSControllable then + local Points = routines.utils.deepCopy( GoPoints ) + local MissionTask = { id = 'Mission', params = { route = { points = Points, }, }, } + local Controller = self:_GetController() + --Controller.setTask( Controller, MissionTask ) + SCHEDULER:New( Controller, Controller.setTask, { MissionTask }, 1 ) + return self + end + + return nil +end + + + +--- (AIR + GROUND) Route the controllable to a given zone. +-- The controllable final destination point can be randomized. +-- A speed can be given in km/h. +-- A given formation can be given. +-- @param #CONTROLLABLE self +-- @param Core.Zone#ZONE Zone The zone where to route to. +-- @param #boolean Randomize Defines whether to target point gets randomized within the Zone. +-- @param #number Speed The speed. +-- @param Base#FORMATION Formation The formation string. +function CONTROLLABLE:TaskRouteToZone( Zone, Randomize, Speed, Formation ) + self:F2( Zone ) + + local DCSControllable = self:GetDCSObject() + + if DCSControllable then + + local ControllablePoint = self:GetVec2() + + local PointFrom = {} + PointFrom.x = ControllablePoint.x + PointFrom.y = ControllablePoint.y + PointFrom.type = "Turning Point" + PointFrom.action = "Cone" + PointFrom.speed = 20 / 1.6 + + + local PointTo = {} + local ZonePoint + + if Randomize then + ZonePoint = Zone:GetRandomVec2() + else + ZonePoint = Zone:GetVec2() + end + + PointTo.x = ZonePoint.x + PointTo.y = ZonePoint.y + PointTo.type = "Turning Point" + + if Formation then + PointTo.action = Formation + else + PointTo.action = "Cone" + end + + if Speed then + PointTo.speed = Speed + else + PointTo.speed = 20 / 1.6 + end + + local Points = { PointFrom, PointTo } + + self:T3( Points ) + + self:Route( Points ) + + return self + end + + return nil +end + +--- (AIR) Return the Controllable to an @{Wrapper.Airbase#AIRBASE} +-- A speed can be given in km/h. +-- A given formation can be given. +-- @param #CONTROLLABLE self +-- @param Wrapper.Airbase#AIRBASE ReturnAirbase The @{Wrapper.Airbase#AIRBASE} to return to. +-- @param #number Speed (optional) The speed. +-- @return #string The route +function CONTROLLABLE:RouteReturnToAirbase( ReturnAirbase, Speed ) + self:F2( { ReturnAirbase, Speed } ) + +-- Example +-- [4] = +-- { +-- ["alt"] = 45, +-- ["type"] = "Land", +-- ["action"] = "Landing", +-- ["alt_type"] = "BARO", +-- ["formation_template"] = "", +-- ["properties"] = +-- { +-- ["vnav"] = 1, +-- ["scale"] = 0, +-- ["angle"] = 0, +-- ["vangle"] = 0, +-- ["steer"] = 2, +-- }, -- end of ["properties"] +-- ["ETA"] = 527.81058817743, +-- ["airdromeId"] = 12, +-- ["y"] = 243127.2973737, +-- ["x"] = -5406.2803440839, +-- ["name"] = "DictKey_WptName_53", +-- ["speed"] = 138.88888888889, +-- ["ETA_locked"] = false, +-- ["task"] = +-- { +-- ["id"] = "ComboTask", +-- ["params"] = +-- { +-- ["tasks"] = +-- { +-- }, -- end of ["tasks"] +-- }, -- end of ["params"] +-- }, -- end of ["task"] +-- ["speed_locked"] = true, +-- }, -- end of [4] + + + local DCSControllable = self:GetDCSObject() + + if DCSControllable then + + local ControllablePoint = self:GetVec2() + local ControllableVelocity = self:GetMaxVelocity() + + local PointFrom = {} + PointFrom.x = ControllablePoint.x + PointFrom.y = ControllablePoint.y + PointFrom.type = "Turning Point" + PointFrom.action = "Turning Point" + PointFrom.speed = ControllableVelocity + + + local PointTo = {} + local AirbasePoint = ReturnAirbase:GetVec2() + + PointTo.x = AirbasePoint.x + PointTo.y = AirbasePoint.y + PointTo.type = "Land" + PointTo.action = "Landing" + PointTo.airdromeId = ReturnAirbase:GetID()-- Airdrome ID + self:T(PointTo.airdromeId) + --PointTo.alt = 0 + + local Points = { PointFrom, PointTo } + + self:T3( Points ) + + local Route = { points = Points, } + + return Route + end + + return nil +end + +-- Commands + +--- Do Script command +-- @param #CONTROLLABLE self +-- @param #string DoScript +-- @return #DCSCommand +function CONTROLLABLE:CommandDoScript( DoScript ) + + local DCSDoScript = { + id = "Script", + params = { + command = DoScript, + }, + } + + self:T3( DCSDoScript ) + return DCSDoScript +end + + +--- Return the mission template of the controllable. +-- @param #CONTROLLABLE self +-- @return #table The MissionTemplate +-- TODO: Rework the method how to retrieve a template ... +function CONTROLLABLE:GetTaskMission() + self:F2( self.ControllableName ) + + return routines.utils.deepCopy( _DATABASE.Templates.Controllables[self.ControllableName].Template ) +end + +--- Return the mission route of the controllable. +-- @param #CONTROLLABLE self +-- @return #table The mission route defined by points. +function CONTROLLABLE:GetTaskRoute() + self:F2( self.ControllableName ) + + return routines.utils.deepCopy( _DATABASE.Templates.Controllables[self.ControllableName].Template.route.points ) +end + +--- Return the route of a controllable by using the @{Core.Database#DATABASE} class. +-- @param #CONTROLLABLE self +-- @param #number Begin The route point from where the copy will start. The base route point is 0. +-- @param #number End The route point where the copy will end. The End point is the last point - the End point. The last point has base 0. +-- @param #boolean Randomize Randomization of the route, when true. +-- @param #number Radius When randomization is on, the randomization is within the radius. +function CONTROLLABLE:CopyRoute( Begin, End, Randomize, Radius ) + self:F2( { Begin, End } ) + + local Points = {} + + -- Could be a Spawned Controllable + local ControllableName = string.match( self:GetName(), ".*#" ) + if ControllableName then + ControllableName = ControllableName:sub( 1, -2 ) + else + ControllableName = self:GetName() + end + + self:T3( { ControllableName } ) + + local Template = _DATABASE.Templates.Controllables[ControllableName].Template + + if Template then + if not Begin then + Begin = 0 + end + if not End then + End = 0 + end + + for TPointID = Begin + 1, #Template.route.points - End do + if Template.route.points[TPointID] then + Points[#Points+1] = routines.utils.deepCopy( Template.route.points[TPointID] ) + if Randomize then + if not Radius then + Radius = 500 + end + Points[#Points].x = Points[#Points].x + math.random( Radius * -1, Radius ) + Points[#Points].y = Points[#Points].y + math.random( Radius * -1, Radius ) + end + end + end + return Points + else + error( "Template not found for Controllable : " .. ControllableName ) + end + + return nil +end + + +--- Return the detected targets of the controllable. +-- The optional parametes specify the detection methods that can be applied. +-- If no detection method is given, the detection will use all the available methods by default. +-- @param Wrapper.Controllable#CONTROLLABLE self +-- @param #boolean DetectVisual (optional) +-- @param #boolean DetectOptical (optional) +-- @param #boolean DetectRadar (optional) +-- @param #boolean DetectIRST (optional) +-- @param #boolean DetectRWR (optional) +-- @param #boolean DetectDLINK (optional) +-- @return #table DetectedTargets +function CONTROLLABLE:GetDetectedTargets( DetectVisual, DetectOptical, DetectRadar, DetectIRST, DetectRWR, DetectDLINK ) + self:F2( self.ControllableName ) + + local DCSControllable = self:GetDCSObject() + if DCSControllable then + local DetectionVisual = ( DetectVisual and DetectVisual == true ) and Controller.Detection.VISUAL or nil + local DetectionOptical = ( DetectOptical and DetectOptical == true ) and Controller.Detection.OPTICAL or nil + local DetectionRadar = ( DetectRadar and DetectRadar == true ) and Controller.Detection.RADAR or nil + local DetectionIRST = ( DetectIRST and DetectIRST == true ) and Controller.Detection.IRST or nil + local DetectionRWR = ( DetectRWR and DetectRWR == true ) and Controller.Detection.RWR or nil + local DetectionDLINK = ( DetectDLINK and DetectDLINK == true ) and Controller.Detection.DLINK or nil + + + return self:_GetController():getDetectedTargets( DetectionVisual, DetectionOptical, DetectionRadar, DetectionIRST, DetectionRWR, DetectionDLINK ) + end + + return nil +end + +function CONTROLLABLE:IsTargetDetected( DCSObject ) + self:F2( self.ControllableName ) + + local DCSControllable = self:GetDCSObject() + if DCSControllable then + + local TargetIsDetected, TargetIsVisible, TargetLastTime, TargetKnowType, TargetKnowDistance, TargetLastPos, TargetLastVelocity + = self:_GetController().isTargetDetected( self:_GetController(), DCSObject, + Controller.Detection.VISUAL, + Controller.Detection.OPTIC, + Controller.Detection.RADAR, + Controller.Detection.IRST, + Controller.Detection.RWR, + Controller.Detection.DLINK + ) + return TargetIsDetected, TargetIsVisible, TargetLastTime, TargetKnowType, TargetKnowDistance, TargetLastPos, TargetLastVelocity + end + + return nil +end + +-- Options + +--- Can the CONTROLLABLE hold their weapons? +-- @param #CONTROLLABLE self +-- @return #boolean +function CONTROLLABLE:OptionROEHoldFirePossible() + self:F2( { self.ControllableName } ) + + local DCSControllable = self:GetDCSObject() + if DCSControllable then + if self:IsAir() or self:IsGround() or self:IsShip() then + return true + end + + return false + end + + return nil +end + +--- Holding weapons. +-- @param Wrapper.Controllable#CONTROLLABLE self +-- @return Wrapper.Controllable#CONTROLLABLE self +function CONTROLLABLE:OptionROEHoldFire() + self:F2( { self.ControllableName } ) + + local DCSControllable = self:GetDCSObject() + if DCSControllable then + local Controller = self:_GetController() + + if self:IsAir() then + Controller:setOption( AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.WEAPON_HOLD ) + elseif self:IsGround() then + Controller:setOption( AI.Option.Ground.id.ROE, AI.Option.Ground.val.ROE.WEAPON_HOLD ) + elseif self:IsShip() then + Controller:setOption( AI.Option.Naval.id.ROE, AI.Option.Naval.val.ROE.WEAPON_HOLD ) + end + + return self + end + + return nil +end + +--- Can the CONTROLLABLE attack returning on enemy fire? +-- @param #CONTROLLABLE self +-- @return #boolean +function CONTROLLABLE:OptionROEReturnFirePossible() + self:F2( { self.ControllableName } ) + + local DCSControllable = self:GetDCSObject() + if DCSControllable then + if self:IsAir() or self:IsGround() or self:IsShip() then + return true + end + + return false + end + + return nil +end + +--- Return fire. +-- @param #CONTROLLABLE self +-- @return #CONTROLLABLE self +function CONTROLLABLE:OptionROEReturnFire() + self:F2( { self.ControllableName } ) + + local DCSControllable = self:GetDCSObject() + if DCSControllable then + local Controller = self:_GetController() + + if self:IsAir() then + Controller:setOption( AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.RETURN_FIRE ) + elseif self:IsGround() then + Controller:setOption( AI.Option.Ground.id.ROE, AI.Option.Ground.val.ROE.RETURN_FIRE ) + elseif self:IsShip() then + Controller:setOption( AI.Option.Naval.id.ROE, AI.Option.Naval.val.ROE.RETURN_FIRE ) + end + + return self + end + + return nil +end + +--- Can the CONTROLLABLE attack designated targets? +-- @param #CONTROLLABLE self +-- @return #boolean +function CONTROLLABLE:OptionROEOpenFirePossible() + self:F2( { self.ControllableName } ) + + local DCSControllable = self:GetDCSObject() + if DCSControllable then + if self:IsAir() or self:IsGround() or self:IsShip() then + return true + end + + return false + end + + return nil +end + +--- Openfire. +-- @param #CONTROLLABLE self +-- @return #CONTROLLABLE self +function CONTROLLABLE:OptionROEOpenFire() + self:F2( { self.ControllableName } ) + + local DCSControllable = self:GetDCSObject() + if DCSControllable then + local Controller = self:_GetController() + + if self:IsAir() then + Controller:setOption( AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.OPEN_FIRE ) + elseif self:IsGround() then + Controller:setOption( AI.Option.Ground.id.ROE, AI.Option.Ground.val.ROE.OPEN_FIRE ) + elseif self:IsShip() then + Controller:setOption( AI.Option.Naval.id.ROE, AI.Option.Naval.val.ROE.OPEN_FIRE ) + end + + return self + end + + return nil +end + +--- Can the CONTROLLABLE attack targets of opportunity? +-- @param #CONTROLLABLE self +-- @return #boolean +function CONTROLLABLE:OptionROEWeaponFreePossible() + self:F2( { self.ControllableName } ) + + local DCSControllable = self:GetDCSObject() + if DCSControllable then + if self:IsAir() then + return true + end + + return false + end + + return nil +end + +--- Weapon free. +-- @param #CONTROLLABLE self +-- @return #CONTROLLABLE self +function CONTROLLABLE:OptionROEWeaponFree() + self:F2( { self.ControllableName } ) + + local DCSControllable = self:GetDCSObject() + if DCSControllable then + local Controller = self:_GetController() + + if self:IsAir() then + Controller:setOption( AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.WEAPON_FREE ) + end + + return self + end + + return nil +end + +--- Can the CONTROLLABLE ignore enemy fire? +-- @param #CONTROLLABLE self +-- @return #boolean +function CONTROLLABLE:OptionROTNoReactionPossible() + self:F2( { self.ControllableName } ) + + local DCSControllable = self:GetDCSObject() + if DCSControllable then + if self:IsAir() then + return true + end + + return false + end + + return nil +end + + +--- No evasion on enemy threats. +-- @param #CONTROLLABLE self +-- @return #CONTROLLABLE self +function CONTROLLABLE:OptionROTNoReaction() + self:F2( { self.ControllableName } ) + + local DCSControllable = self:GetDCSObject() + if DCSControllable then + local Controller = self:_GetController() + + if self:IsAir() then + Controller:setOption( AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.NO_REACTION ) + end + + return self + end + + return nil +end + +--- Can the CONTROLLABLE evade using passive defenses? +-- @param #CONTROLLABLE self +-- @return #boolean +function CONTROLLABLE:OptionROTPassiveDefensePossible() + self:F2( { self.ControllableName } ) + + local DCSControllable = self:GetDCSObject() + if DCSControllable then + if self:IsAir() then + return true + end + + return false + end + + return nil +end + +--- Evasion passive defense. +-- @param #CONTROLLABLE self +-- @return #CONTROLLABLE self +function CONTROLLABLE:OptionROTPassiveDefense() + self:F2( { self.ControllableName } ) + + local DCSControllable = self:GetDCSObject() + if DCSControllable then + local Controller = self:_GetController() + + if self:IsAir() then + Controller:setOption( AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.PASSIVE_DEFENCE ) + end + + return self + end + + return nil +end + +--- Can the CONTROLLABLE evade on enemy fire? +-- @param #CONTROLLABLE self +-- @return #boolean +function CONTROLLABLE:OptionROTEvadeFirePossible() + self:F2( { self.ControllableName } ) + + local DCSControllable = self:GetDCSObject() + if DCSControllable then + if self:IsAir() then + return true + end + + return false + end + + return nil +end + + +--- Evade on fire. +-- @param #CONTROLLABLE self +-- @return #CONTROLLABLE self +function CONTROLLABLE:OptionROTEvadeFire() + self:F2( { self.ControllableName } ) + + local DCSControllable = self:GetDCSObject() + if DCSControllable then + local Controller = self:_GetController() + + if self:IsAir() then + Controller:setOption( AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.EVADE_FIRE ) + end + + return self + end + + return nil +end + +--- Can the CONTROLLABLE evade on fire using vertical manoeuvres? +-- @param #CONTROLLABLE self +-- @return #boolean +function CONTROLLABLE:OptionROTVerticalPossible() + self:F2( { self.ControllableName } ) + + local DCSControllable = self:GetDCSObject() + if DCSControllable then + if self:IsAir() then + return true + end + + return false + end + + return nil +end + + +--- Evade on fire using vertical manoeuvres. +-- @param #CONTROLLABLE self +-- @return #CONTROLLABLE self +function CONTROLLABLE:OptionROTVertical() + self:F2( { self.ControllableName } ) + + local DCSControllable = self:GetDCSObject() + if DCSControllable then + local Controller = self:_GetController() + + if self:IsAir() then + Controller:setOption( AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.BYPASS_AND_ESCAPE ) + end + + return self + end + + return nil +end + +--- Retrieve the controllable mission and allow to place function hooks within the mission waypoint plan. +-- Use the method @{Wrapper.Controllable#CONTROLLABLE:WayPointFunction} to define the hook functions for specific waypoints. +-- Use the method @{Controllable@CONTROLLABLE:WayPointExecute) to start the execution of the new mission plan. +-- Note that when WayPointInitialize is called, the Mission of the controllable is RESTARTED! +-- @param #CONTROLLABLE self +-- @param #table WayPoints If WayPoints is given, then use the route. +-- @return #CONTROLLABLE +function CONTROLLABLE:WayPointInitialize( WayPoints ) + self:F( { WayPoint, WayPointIndex, WayPointFunction } ) + + if WayPoints then + self.WayPoints = WayPoints + else + self.WayPoints = self:GetTaskRoute() + end + + return self +end + + +--- Registers a waypoint function that will be executed when the controllable moves over the WayPoint. +-- @param #CONTROLLABLE self +-- @param #number WayPoint The waypoint number. Note that the start waypoint on the route is WayPoint 1! +-- @param #number WayPointIndex When defining multiple WayPoint functions for one WayPoint, use WayPointIndex to set the sequence of actions. +-- @param #function WayPointFunction The waypoint function to be called when the controllable moves over the waypoint. The waypoint function takes variable parameters. +-- @return #CONTROLLABLE +function CONTROLLABLE:WayPointFunction( WayPoint, WayPointIndex, WayPointFunction, ... ) + self:F2( { WayPoint, WayPointIndex, WayPointFunction } ) + + table.insert( self.WayPoints[WayPoint].task.params.tasks, WayPointIndex ) + self.WayPoints[WayPoint].task.params.tasks[WayPointIndex] = self:TaskFunction( WayPoint, WayPointIndex, WayPointFunction, arg ) + return self +end + + +function CONTROLLABLE:TaskFunction( WayPoint, WayPointIndex, FunctionString, FunctionArguments ) + self:F2( { WayPoint, WayPointIndex, FunctionString, FunctionArguments } ) + + local DCSTask + + local DCSScript = {} + DCSScript[#DCSScript+1] = "local MissionControllable = GROUP:Find( ... ) " + + if FunctionArguments and #FunctionArguments > 0 then + DCSScript[#DCSScript+1] = FunctionString .. "( MissionControllable, " .. table.concat( FunctionArguments, "," ) .. ")" + else + DCSScript[#DCSScript+1] = FunctionString .. "( MissionControllable )" + end + + DCSTask = self:TaskWrappedAction( + self:CommandDoScript( + table.concat( DCSScript ) + ), WayPointIndex + ) + + self:T3( DCSTask ) + + return DCSTask + +end + +--- Executes the WayPoint plan. +-- The function gets a WayPoint parameter, that you can use to restart the mission at a specific WayPoint. +-- Note that when the WayPoint parameter is used, the new start mission waypoint of the controllable will be 1! +-- @param #CONTROLLABLE self +-- @param #number WayPoint The WayPoint from where to execute the mission. +-- @param #number WaitTime The amount seconds to wait before initiating the mission. +-- @return #CONTROLLABLE +function CONTROLLABLE:WayPointExecute( WayPoint, WaitTime ) + self:F( { WayPoint, WaitTime } ) + + if not WayPoint then + WayPoint = 1 + end + + -- When starting the mission from a certain point, the TaskPoints need to be deleted before the given WayPoint. + for TaskPointID = 1, WayPoint - 1 do + table.remove( self.WayPoints, 1 ) + end + + self:T3( self.WayPoints ) + + self:SetTask( self:TaskRoute( self.WayPoints ), WaitTime ) + + return self +end + +-- Message APIs + + +--- This module contains the GROUP class. +-- +-- 1) @{Wrapper.Group#GROUP} class, extends @{Wrapper.Controllable#CONTROLLABLE} +-- ============================================================= +-- The @{Wrapper.Group#GROUP} class is a wrapper class to handle the DCS Group objects: +-- +-- * Support all DCS Group APIs. +-- * Enhance with Group specific APIs not in the DCS Group API set. +-- * Handle local Group Controller. +-- * Manage the "state" of the DCS Group. +-- +-- **IMPORTANT: ONE SHOULD NEVER SANATIZE these GROUP OBJECT REFERENCES! (make the GROUP object references nil).** +-- +-- 1.1) GROUP reference methods +-- ----------------------- +-- For each DCS Group object alive within a running mission, a GROUP wrapper object (instance) will be created within the _@{DATABASE} object. +-- This is done at the beginning of the mission (when the mission starts), and dynamically when new DCS Group objects are spawned (using the @{SPAWN} class). +-- +-- The GROUP class does not contain a :New() method, rather it provides :Find() methods to retrieve the object reference +-- using the DCS Group or the DCS GroupName. +-- +-- Another thing to know is that GROUP objects do not "contain" the DCS Group object. +-- The GROUP methods will reference the DCS Group object by name when it is needed during API execution. +-- If the DCS Group object does not exist or is nil, the GROUP methods will return nil and log an exception in the DCS.log file. +-- +-- The GROUP class provides the following functions to retrieve quickly the relevant GROUP instance: +-- +-- * @{#GROUP.Find}(): Find a GROUP instance from the _DATABASE object using a DCS Group object. +-- * @{#GROUP.FindByName}(): Find a GROUP instance from the _DATABASE object using a DCS Group name. +-- +-- 1.2) GROUP task methods +-- ----------------------- +-- Several group task methods are available that help you to prepare tasks. +-- These methods return a string consisting of the task description, which can then be given to either a +-- @{Wrapper.Controllable#CONTROLLABLE.PushTask} or @{Wrapper.Controllable#CONTROLLABLE.SetTask} method to assign the task to the GROUP. +-- Tasks are specific for the category of the GROUP, more specific, for AIR, GROUND or AIR and GROUND. +-- Each task description where applicable indicates for which group category the task is valid. +-- There are 2 main subdivisions of tasks: Assigned tasks and EnRoute tasks. +-- +-- ### 1.2.1) Assigned task methods +-- +-- Assigned task methods make the group execute the task where the location of the (possible) targets of the task are known before being detected. +-- This is different from the EnRoute tasks, where the targets of the task need to be detected before the task can be executed. +-- +-- Find below a list of the **assigned task** methods: +-- +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskAttackGroup}: (AIR) Attack a Group. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskAttackMapObject}: (AIR) Attacking the map object (building, structure, e.t.c). +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskAttackUnit}: (AIR) Attack the Unit. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskBombing}: (Wrapper.Controllable#CONTROLLABLEDelivering weapon at the point on the ground. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskBombingRunway}: (AIR) Delivering weapon on the runway. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskEmbarking}: (AIR) Move the group to a Vec2 Point, wait for a defined duration and embark a group. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskEmbarkToTransport}: (GROUND) Embark to a Transport landed at a location. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskEscort}: (AIR) Escort another airborne group. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskFAC_AttackGroup}: (AIR + GROUND) The task makes the group/unit a FAC and orders the FAC to control the target (enemy ground group) destruction. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskFireAtPoint}: (GROUND) Fire at a VEC2 point until ammunition is finished. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskFollow}: (AIR) Following another airborne group. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskHold}: (GROUND) Hold ground group from moving. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskHoldPosition}: (AIR) Hold position at the current position of the first unit of the group. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskLand}: (AIR HELICOPTER) Landing at the ground. For helicopters only. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskLandAtZone}: (AIR) Land the group at a @{Core.Zone#ZONE_RADIUS). +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskOrbitCircle}: (AIR) Orbit at the current position of the first unit of the group at a specified alititude. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskOrbitCircleAtVec2}: (AIR) Orbit at a specified position at a specified alititude during a specified duration with a specified speed. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskRefueling}: (AIR) Refueling from the nearest tanker. No parameters. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskRoute}: (AIR + GROUND) Return a Misson task to follow a given route defined by Points. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskRouteToVec2}: (AIR + GROUND) Make the Group move to a given point. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskRouteToVec3}: (AIR + GROUND) Make the Group move to a given point. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskRouteToZone}: (AIR + GROUND) Route the group to a given zone. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskReturnToBase}: (AIR) Route the group to an airbase. +-- +-- ### 1.2.2) EnRoute task methods +-- +-- EnRoute tasks require the targets of the task need to be detected by the group (using its sensors) before the task can be executed: +-- +-- * @{Wrapper.Controllable#CONTROLLABLE.EnRouteTaskAWACS}: (AIR) Aircraft will act as an AWACS for friendly units (will provide them with information about contacts). No parameters. +-- * @{Wrapper.Controllable#CONTROLLABLE.EnRouteTaskEngageGroup}: (AIR) Engaging a group. The task does not assign the target group to the unit/group to attack now; it just allows the unit/group to engage the target group as well as other assigned targets. +-- * @{Wrapper.Controllable#CONTROLLABLE.EnRouteTaskEngageTargets}: (AIR) Engaging targets of defined types. +-- * @{Wrapper.Controllable#CONTROLLABLE.EnRouteTaskEWR}: (AIR) Attack the Unit. +-- * @{Wrapper.Controllable#CONTROLLABLE.EnRouteTaskFAC}: (AIR + GROUND) The task makes the group/unit a FAC and lets the FAC to choose a targets (enemy ground group) around as well as other assigned targets. +-- * @{Wrapper.Controllable#CONTROLLABLE.EnRouteTaskFAC_EngageGroup}: (AIR + GROUND) The task makes the group/unit a FAC and lets the FAC to choose the target (enemy ground group) as well as other assigned targets. +-- * @{Wrapper.Controllable#CONTROLLABLE.EnRouteTaskTanker}: (AIR) Aircraft will act as a tanker for friendly units. No parameters. +-- +-- ### 1.2.3) Preparation task methods +-- +-- There are certain task methods that allow to tailor the task behaviour: +-- +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskWrappedAction}: Return a WrappedAction Task taking a Command. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskCombo}: Return a Combo Task taking an array of Tasks. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskCondition}: Return a condition section for a controlled task. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskControlled}: Return a Controlled Task taking a Task and a TaskCondition. +-- +-- ### 1.2.4) Obtain the mission from group templates +-- +-- Group templates contain complete mission descriptions. Sometimes you want to copy a complete mission from a group and assign it to another: +-- +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskMission}: (AIR + GROUND) Return a mission task from a mission template. +-- +-- 1.3) GROUP Command methods +-- -------------------------- +-- Group **command methods** prepare the execution of commands using the @{Wrapper.Controllable#CONTROLLABLE.SetCommand} method: +-- +-- * @{Wrapper.Controllable#CONTROLLABLE.CommandDoScript}: Do Script command. +-- * @{Wrapper.Controllable#CONTROLLABLE.CommandSwitchWayPoint}: Perform a switch waypoint command. +-- +-- 1.4) GROUP Option methods +-- ------------------------- +-- Group **Option methods** change the behaviour of the Group while being alive. +-- +-- ### 1.4.1) Rule of Engagement: +-- +-- * @{Wrapper.Controllable#CONTROLLABLE.OptionROEWeaponFree} +-- * @{Wrapper.Controllable#CONTROLLABLE.OptionROEOpenFire} +-- * @{Wrapper.Controllable#CONTROLLABLE.OptionROEReturnFire} +-- * @{Wrapper.Controllable#CONTROLLABLE.OptionROEEvadeFire} +-- +-- To check whether an ROE option is valid for a specific group, use: +-- +-- * @{Wrapper.Controllable#CONTROLLABLE.OptionROEWeaponFreePossible} +-- * @{Wrapper.Controllable#CONTROLLABLE.OptionROEOpenFirePossible} +-- * @{Wrapper.Controllable#CONTROLLABLE.OptionROEReturnFirePossible} +-- * @{Wrapper.Controllable#CONTROLLABLE.OptionROEEvadeFirePossible} +-- +-- ### 1.4.2) Rule on thread: +-- +-- * @{Wrapper.Controllable#CONTROLLABLE.OptionROTNoReaction} +-- * @{Wrapper.Controllable#CONTROLLABLE.OptionROTPassiveDefense} +-- * @{Wrapper.Controllable#CONTROLLABLE.OptionROTEvadeFire} +-- * @{Wrapper.Controllable#CONTROLLABLE.OptionROTVertical} +-- +-- To test whether an ROT option is valid for a specific group, use: +-- +-- * @{Wrapper.Controllable#CONTROLLABLE.OptionROTNoReactionPossible} +-- * @{Wrapper.Controllable#CONTROLLABLE.OptionROTPassiveDefensePossible} +-- * @{Wrapper.Controllable#CONTROLLABLE.OptionROTEvadeFirePossible} +-- * @{Wrapper.Controllable#CONTROLLABLE.OptionROTVerticalPossible} +-- +-- 1.5) GROUP Zone validation methods +-- ---------------------------------- +-- The group can be validated whether it is completely, partly or not within a @{Zone}. +-- Use the following Zone validation methods on the group: +-- +-- * @{#GROUP.IsCompletelyInZone}: Returns true if all units of the group are within a @{Zone}. +-- * @{#GROUP.IsPartlyInZone}: Returns true if some units of the group are within a @{Zone}. +-- * @{#GROUP.IsNotInZone}: Returns true if none of the group units of the group are within a @{Zone}. +-- +-- The zone can be of any @{Zone} class derived from @{Core.Zone#ZONE_BASE}. So, these methods are polymorphic to the zones tested on. +-- +-- @module Group +-- @author FlightControl + +--- The GROUP class +-- @type GROUP +-- @extends Wrapper.Controllable#CONTROLLABLE +-- @field #string GroupName The name of the group. +GROUP = { + ClassName = "GROUP", +} + +--- Create a new GROUP from a DCSGroup +-- @param #GROUP self +-- @param Dcs.DCSWrapper.Group#Group GroupName The DCS Group name +-- @return #GROUP self +function GROUP:Register( GroupName ) + local self = BASE:Inherit( self, CONTROLLABLE:New( GroupName ) ) + self:F2( GroupName ) + self.GroupName = GroupName + return self +end + +-- Reference methods. + +--- Find the GROUP wrapper class instance using the DCS Group. +-- @param #GROUP self +-- @param Dcs.DCSWrapper.Group#Group DCSGroup The DCS Group. +-- @return #GROUP The GROUP. +function GROUP:Find( DCSGroup ) + + local GroupName = DCSGroup:getName() -- Wrapper.Group#GROUP + local GroupFound = _DATABASE:FindGroup( GroupName ) + return GroupFound +end + +--- Find the created GROUP using the DCS Group Name. +-- @param #GROUP self +-- @param #string GroupName The DCS Group Name. +-- @return #GROUP The GROUP. +function GROUP:FindByName( GroupName ) + + local GroupFound = _DATABASE:FindGroup( GroupName ) + return GroupFound +end + +-- DCS Group methods support. + +--- Returns the DCS Group. +-- @param #GROUP self +-- @return Dcs.DCSWrapper.Group#Group The DCS Group. +function GROUP:GetDCSObject() + local DCSGroup = Group.getByName( self.GroupName ) + + if DCSGroup then + return DCSGroup + end + + return nil +end + + +--- Returns if the DCS Group is alive. +-- When the group exists at run-time, this method will return true, otherwise false. +-- @param #GROUP self +-- @return #boolean true if the DCS Group is alive. +function GROUP:IsAlive() + self:F2( self.GroupName ) + + local DCSGroup = self:GetDCSObject() + + if DCSGroup then + local GroupIsAlive = DCSGroup:isExist() + self:T3( GroupIsAlive ) + return GroupIsAlive + end + + return nil +end + +--- Destroys the DCS Group and all of its DCS Units. +-- Note that this destroy method also raises a destroy event at run-time. +-- So all event listeners will catch the destroy event of this DCS Group. +-- @param #GROUP self +function GROUP:Destroy() + self:F2( self.GroupName ) + + local DCSGroup = self:GetDCSObject() + + if DCSGroup then + for Index, UnitData in pairs( DCSGroup:getUnits() ) do + self:CreateEventCrash( timer.getTime(), UnitData ) + end + DCSGroup:destroy() + DCSGroup = nil + end + + return nil +end + +--- Returns category of the DCS Group. +-- @param #GROUP self +-- @return Dcs.DCSWrapper.Group#Group.Category The category ID +function GROUP:GetCategory() + self:F2( self.GroupName ) + + local DCSGroup = self:GetDCSObject() + if DCSGroup then + local GroupCategory = DCSGroup:getCategory() + self:T3( GroupCategory ) + return GroupCategory + end + + return nil +end + +--- Returns the category name of the DCS Group. +-- @param #GROUP self +-- @return #string Category name = Helicopter, Airplane, Ground Unit, Ship +function GROUP:GetCategoryName() + self:F2( self.GroupName ) + + local DCSGroup = self:GetDCSObject() + if DCSGroup then + local CategoryNames = { + [Group.Category.AIRPLANE] = "Airplane", + [Group.Category.HELICOPTER] = "Helicopter", + [Group.Category.GROUND] = "Ground Unit", + [Group.Category.SHIP] = "Ship", + } + local GroupCategory = DCSGroup:getCategory() + self:T3( GroupCategory ) + + return CategoryNames[GroupCategory] + end + + return nil +end + + +--- Returns the coalition of the DCS Group. +-- @param #GROUP self +-- @return Dcs.DCSCoalitionWrapper.Object#coalition.side The coalition side of the DCS Group. +function GROUP:GetCoalition() + self:F2( self.GroupName ) + + local DCSGroup = self:GetDCSObject() + if DCSGroup then + local GroupCoalition = DCSGroup:getCoalition() + self:T3( GroupCoalition ) + return GroupCoalition + end + + return nil +end + +--- Returns the country of the DCS Group. +-- @param #GROUP self +-- @return Dcs.DCScountry#country.id The country identifier. +-- @return #nil The DCS Group is not existing or alive. +function GROUP:GetCountry() + self:F2( self.GroupName ) + + local DCSGroup = self:GetDCSObject() + if DCSGroup then + local GroupCountry = DCSGroup:getUnit(1):getCountry() + self:T3( GroupCountry ) + return GroupCountry + end + + return nil +end + +--- Returns the UNIT wrapper class with number UnitNumber. +-- If the underlying DCS Unit does not exist, the method will return nil. . +-- @param #GROUP self +-- @param #number UnitNumber The number of the UNIT wrapper class to be returned. +-- @return Wrapper.Unit#UNIT The UNIT wrapper class. +function GROUP:GetUnit( UnitNumber ) + self:F2( { self.GroupName, UnitNumber } ) + + local DCSGroup = self:GetDCSObject() + + if DCSGroup then + local UnitFound = UNIT:Find( DCSGroup:getUnit( UnitNumber ) ) + self:T3( UnitFound.UnitName ) + self:T2( UnitFound ) + return UnitFound + end + + return nil +end + +--- Returns the DCS Unit with number UnitNumber. +-- If the underlying DCS Unit does not exist, the method will return nil. . +-- @param #GROUP self +-- @param #number UnitNumber The number of the DCS Unit to be returned. +-- @return Dcs.DCSWrapper.Unit#Unit The DCS Unit. +function GROUP:GetDCSUnit( UnitNumber ) + self:F2( { self.GroupName, UnitNumber } ) + + local DCSGroup = self:GetDCSObject() + + if DCSGroup then + local DCSUnitFound = DCSGroup:getUnit( UnitNumber ) + self:T3( DCSUnitFound ) + return DCSUnitFound + end + + return nil +end + +--- Returns current size of the DCS Group. +-- If some of the DCS Units of the DCS Group are destroyed the size of the DCS Group is changed. +-- @param #GROUP self +-- @return #number The DCS Group size. +function GROUP:GetSize() + self:F2( { self.GroupName } ) + local DCSGroup = self:GetDCSObject() + + if DCSGroup then + local GroupSize = DCSGroup:getSize() + self:T3( GroupSize ) + return GroupSize + end + + return nil +end + +--- +--- Returns the initial size of the DCS Group. +-- If some of the DCS Units of the DCS Group are destroyed, the initial size of the DCS Group is unchanged. +-- @param #GROUP self +-- @return #number The DCS Group initial size. +function GROUP:GetInitialSize() + self:F2( { self.GroupName } ) + local DCSGroup = self:GetDCSObject() + + if DCSGroup then + local GroupInitialSize = DCSGroup:getInitialSize() + self:T3( GroupInitialSize ) + return GroupInitialSize + end + + return nil +end + +--- Returns the UNITs wrappers of the DCS Units of the DCS Group. +-- @param #GROUP self +-- @return #table The UNITs wrappers. +function GROUP:GetUnits() + self:F2( { self.GroupName } ) + local DCSGroup = self:GetDCSObject() + + if DCSGroup then + local DCSUnits = DCSGroup:getUnits() + local Units = {} + for Index, UnitData in pairs( DCSUnits ) do + Units[#Units+1] = UNIT:Find( UnitData ) + end + self:T3( Units ) + return Units + end + + return nil +end + + +--- Returns the DCS Units of the DCS Group. +-- @param #GROUP self +-- @return #table The DCS Units. +function GROUP:GetDCSUnits() + self:F2( { self.GroupName } ) + local DCSGroup = self:GetDCSObject() + + if DCSGroup then + local DCSUnits = DCSGroup:getUnits() + self:T3( DCSUnits ) + return DCSUnits + end + + return nil +end + + +--- Activates a GROUP. +-- @param #GROUP self +function GROUP:Activate() + self:F2( { self.GroupName } ) + trigger.action.activateGroup( self:GetDCSObject() ) + return self:GetDCSObject() +end + + +--- Gets the type name of the group. +-- @param #GROUP self +-- @return #string The type name of the group. +function GROUP:GetTypeName() + self:F2( self.GroupName ) + + local DCSGroup = self:GetDCSObject() + + if DCSGroup then + local GroupTypeName = DCSGroup:getUnit(1):getTypeName() + self:T3( GroupTypeName ) + return( GroupTypeName ) + end + + return nil +end + +--- Gets the CallSign of the first DCS Unit of the DCS Group. +-- @param #GROUP self +-- @return #string The CallSign of the first DCS Unit of the DCS Group. +function GROUP:GetCallsign() + self:F2( self.GroupName ) + + local DCSGroup = self:GetDCSObject() + + if DCSGroup then + local GroupCallSign = DCSGroup:getUnit(1):getCallsign() + self:T3( GroupCallSign ) + return GroupCallSign + end + + return nil +end + +--- Returns the current point (Vec2 vector) of the first DCS Unit in the DCS Group. +-- @param #GROUP self +-- @return Dcs.DCSTypes#Vec2 Current Vec2 point of the first DCS Unit of the DCS Group. +function GROUP:GetVec2() + self:F2( self.GroupName ) + + local UnitPoint = self:GetUnit(1) + UnitPoint:GetVec2() + local GroupPointVec2 = UnitPoint:GetVec2() + self:T3( GroupPointVec2 ) + return GroupPointVec2 +end + +--- Returns the current Vec3 vector of the first DCS Unit in the GROUP. +-- @return Dcs.DCSTypes#Vec3 Current Vec3 of the first DCS Unit of the GROUP. +function GROUP:GetVec3() + self:F2( self.GroupName ) + + local GroupVec3 = self:GetUnit(1):GetVec3() + self:T3( GroupVec3 ) + return GroupVec3 +end + + + +-- Is Zone Functions + +--- Returns true if all units of the group are within a @{Zone}. +-- @param #GROUP self +-- @param Core.Zone#ZONE_BASE Zone The zone to test. +-- @return #boolean Returns true if the Group is completely within the @{Core.Zone#ZONE_BASE} +function GROUP:IsCompletelyInZone( Zone ) + self:F2( { self.GroupName, Zone } ) + + for UnitID, UnitData in pairs( self:GetUnits() ) do + local Unit = UnitData -- Wrapper.Unit#UNIT + -- TODO: Rename IsPointVec3InZone to IsVec3InZone + if Zone:IsPointVec3InZone( Unit:GetVec3() ) then + else + return false + end + end + + return true +end + +--- Returns true if some units of the group are within a @{Zone}. +-- @param #GROUP self +-- @param Core.Zone#ZONE_BASE Zone The zone to test. +-- @return #boolean Returns true if the Group is completely within the @{Core.Zone#ZONE_BASE} +function GROUP:IsPartlyInZone( Zone ) + self:F2( { self.GroupName, Zone } ) + + for UnitID, UnitData in pairs( self:GetUnits() ) do + local Unit = UnitData -- Wrapper.Unit#UNIT + if Zone:IsPointVec3InZone( Unit:GetVec3() ) then + return true + end + end + + return false +end + +--- Returns true if none of the group units of the group are within a @{Zone}. +-- @param #GROUP self +-- @param Core.Zone#ZONE_BASE Zone The zone to test. +-- @return #boolean Returns true if the Group is completely within the @{Core.Zone#ZONE_BASE} +function GROUP:IsNotInZone( Zone ) + self:F2( { self.GroupName, Zone } ) + + for UnitID, UnitData in pairs( self:GetUnits() ) do + local Unit = UnitData -- Wrapper.Unit#UNIT + if Zone:IsPointVec3InZone( Unit:GetVec3() ) then + return false + end + end + + return true +end + +--- Returns if the group is of an air category. +-- If the group is a helicopter or a plane, then this method will return true, otherwise false. +-- @param #GROUP self +-- @return #boolean Air category evaluation result. +function GROUP:IsAir() + self:F2( self.GroupName ) + + local DCSGroup = self:GetDCSObject() + + if DCSGroup then + local IsAirResult = DCSGroup:getCategory() == Group.Category.AIRPLANE or DCSGroup:getCategory() == Group.Category.HELICOPTER + self:T3( IsAirResult ) + return IsAirResult + end + + return nil +end + +--- Returns if the DCS Group contains Helicopters. +-- @param #GROUP self +-- @return #boolean true if DCS Group contains Helicopters. +function GROUP:IsHelicopter() + self:F2( self.GroupName ) + + local DCSGroup = self:GetDCSObject() + + if DCSGroup then + local GroupCategory = DCSGroup:getCategory() + self:T2( GroupCategory ) + return GroupCategory == Group.Category.HELICOPTER + end + + return nil +end + +--- Returns if the DCS Group contains AirPlanes. +-- @param #GROUP self +-- @return #boolean true if DCS Group contains AirPlanes. +function GROUP:IsAirPlane() + self:F2() + + local DCSGroup = self:GetDCSObject() + + if DCSGroup then + local GroupCategory = DCSGroup:getCategory() + self:T2( GroupCategory ) + return GroupCategory == Group.Category.AIRPLANE + end + + return nil +end + +--- Returns if the DCS Group contains Ground troops. +-- @param #GROUP self +-- @return #boolean true if DCS Group contains Ground troops. +function GROUP:IsGround() + self:F2() + + local DCSGroup = self:GetDCSObject() + + if DCSGroup then + local GroupCategory = DCSGroup:getCategory() + self:T2( GroupCategory ) + return GroupCategory == Group.Category.GROUND + end + + return nil +end + +--- Returns if the DCS Group contains Ships. +-- @param #GROUP self +-- @return #boolean true if DCS Group contains Ships. +function GROUP:IsShip() + self:F2() + + local DCSGroup = self:GetDCSObject() + + if DCSGroup then + local GroupCategory = DCSGroup:getCategory() + self:T2( GroupCategory ) + return GroupCategory == Group.Category.SHIP + end + + return nil +end + +--- Returns if all units of the group are on the ground or landed. +-- If all units of this group are on the ground, this function will return true, otherwise false. +-- @param #GROUP self +-- @return #boolean All units on the ground result. +function GROUP:AllOnGround() + self:F2() + + local DCSGroup = self:GetDCSObject() + + if DCSGroup then + local AllOnGroundResult = true + + for Index, UnitData in pairs( DCSGroup:getUnits() ) do + if UnitData:inAir() then + AllOnGroundResult = false + end + end + + self:T3( AllOnGroundResult ) + return AllOnGroundResult + end + + return nil +end + +--- Returns the current maximum velocity of the group. +-- Each unit within the group gets evaluated, and the maximum velocity (= the unit which is going the fastest) is returned. +-- @param #GROUP self +-- @return #number Maximum velocity found. +function GROUP:GetMaxVelocity() + self:F2() + + local DCSGroup = self:GetDCSObject() + + if DCSGroup then + local GroupVelocityMax = 0 + + for Index, UnitData in pairs( DCSGroup:getUnits() ) do + + local UnitVelocityVec3 = UnitData:getVelocity() + local UnitVelocity = math.abs( UnitVelocityVec3.x ) + math.abs( UnitVelocityVec3.y ) + math.abs( UnitVelocityVec3.z ) + + if UnitVelocity > GroupVelocityMax then + GroupVelocityMax = UnitVelocity + end + end + + return GroupVelocityMax + end + + return nil +end + +--- Returns the current minimum height of the group. +-- Each unit within the group gets evaluated, and the minimum height (= the unit which is the lowest elevated) is returned. +-- @param #GROUP self +-- @return #number Minimum height found. +function GROUP:GetMinHeight() + self:F2() + +end + +--- Returns the current maximum height of the group. +-- Each unit within the group gets evaluated, and the maximum height (= the unit which is the highest elevated) is returned. +-- @param #GROUP self +-- @return #number Maximum height found. +function GROUP:GetMaxHeight() + self:F2() + +end + +-- SPAWNING + +--- Respawn the @{GROUP} using a (tweaked) template of the Group. +-- The template must be retrieved with the @{Wrapper.Group#GROUP.GetTemplate}() function. +-- The template contains all the definitions as declared within the mission file. +-- To understand templates, do the following: +-- +-- * unpack your .miz file into a directory using 7-zip. +-- * browse in the directory created to the file **mission**. +-- * open the file and search for the country group definitions. +-- +-- Your group template will contain the fields as described within the mission file. +-- +-- This function will: +-- +-- * Get the current position and heading of the group. +-- * When the group is alive, it will tweak the template x, y and heading coordinates of the group and the embedded units to the current units positions. +-- * Then it will destroy the current alive group. +-- * And it will respawn the group using your new template definition. +-- @param Wrapper.Group#GROUP self +-- @param #table Template The template of the Group retrieved with GROUP:GetTemplate() +function GROUP:Respawn( Template ) + + local Vec3 = self:GetVec3() + Template.x = Vec3.x + Template.y = Vec3.z + --Template.x = nil + --Template.y = nil + + self:E( #Template.units ) + for UnitID, UnitData in pairs( self:GetUnits() ) do + local GroupUnit = UnitData -- Wrapper.Unit#UNIT + self:E( GroupUnit:GetName() ) + if GroupUnit:IsAlive() then + local GroupUnitVec3 = GroupUnit:GetVec3() + local GroupUnitHeading = GroupUnit:GetHeading() + Template.units[UnitID].alt = GroupUnitVec3.y + Template.units[UnitID].x = GroupUnitVec3.x + Template.units[UnitID].y = GroupUnitVec3.z + Template.units[UnitID].heading = GroupUnitHeading + self:E( { UnitID, Template.units[UnitID], Template.units[UnitID] } ) + end + end + + self:Destroy() + _DATABASE:Spawn( Template ) +end + +--- Returns the group template from the @{DATABASE} (_DATABASE object). +-- @param #GROUP self +-- @return #table +function GROUP:GetTemplate() + local GroupName = self:GetName() + self:E( GroupName ) + return _DATABASE:GetGroupTemplate( GroupName ) +end + +--- Sets the controlled status in a Template. +-- @param #GROUP self +-- @param #boolean Controlled true is controlled, false is uncontrolled. +-- @return #table +function GROUP:SetTemplateControlled( Template, Controlled ) + Template.uncontrolled = not Controlled + return Template +end + +--- Sets the CountryID of the group in a Template. +-- @param #GROUP self +-- @param Dcs.DCScountry#country.id CountryID The country ID. +-- @return #table +function GROUP:SetTemplateCountry( Template, CountryID ) + Template.CountryID = CountryID + return Template +end + +--- Sets the CoalitionID of the group in a Template. +-- @param #GROUP self +-- @param Dcs.DCSCoalitionWrapper.Object#coalition.side CoalitionID The coalition ID. +-- @return #table +function GROUP:SetTemplateCoalition( Template, CoalitionID ) + Template.CoalitionID = CoalitionID + return Template +end + + + + +--- Return the mission template of the group. +-- @param #GROUP self +-- @return #table The MissionTemplate +function GROUP:GetTaskMission() + self:F2( self.GroupName ) + + return routines.utils.deepCopy( _DATABASE.Templates.Groups[self.GroupName].Template ) +end + +--- Return the mission route of the group. +-- @param #GROUP self +-- @return #table The mission route defined by points. +function GROUP:GetTaskRoute() + self:F2( self.GroupName ) + + return routines.utils.deepCopy( _DATABASE.Templates.Groups[self.GroupName].Template.route.points ) +end + +--- Return the route of a group by using the @{Core.Database#DATABASE} class. +-- @param #GROUP self +-- @param #number Begin The route point from where the copy will start. The base route point is 0. +-- @param #number End The route point where the copy will end. The End point is the last point - the End point. The last point has base 0. +-- @param #boolean Randomize Randomization of the route, when true. +-- @param #number Radius When randomization is on, the randomization is within the radius. +function GROUP:CopyRoute( Begin, End, Randomize, Radius ) + self:F2( { Begin, End } ) + + local Points = {} + + -- Could be a Spawned Group + local GroupName = string.match( self:GetName(), ".*#" ) + if GroupName then + GroupName = GroupName:sub( 1, -2 ) + else + GroupName = self:GetName() + end + + self:T3( { GroupName } ) + + local Template = _DATABASE.Templates.Groups[GroupName].Template + + if Template then + if not Begin then + Begin = 0 + end + if not End then + End = 0 + end + + for TPointID = Begin + 1, #Template.route.points - End do + if Template.route.points[TPointID] then + Points[#Points+1] = routines.utils.deepCopy( Template.route.points[TPointID] ) + if Randomize then + if not Radius then + Radius = 500 + end + Points[#Points].x = Points[#Points].x + math.random( Radius * -1, Radius ) + Points[#Points].y = Points[#Points].y + math.random( Radius * -1, Radius ) + end + end + end + return Points + else + error( "Template not found for Group : " .. GroupName ) + end + + return nil +end + + +--- This module contains the UNIT class. +-- +-- 1) @{#UNIT} class, extends @{Wrapper.Controllable#CONTROLLABLE} +-- =========================================================== +-- The @{#UNIT} class is a wrapper class to handle the DCS Unit objects: +-- +-- * Support all DCS Unit APIs. +-- * Enhance with Unit specific APIs not in the DCS Unit API set. +-- * Handle local Unit Controller. +-- * Manage the "state" of the DCS Unit. +-- +-- +-- 1.1) UNIT reference methods +-- ---------------------- +-- For each DCS Unit object alive within a running mission, a UNIT wrapper object (instance) will be created within the _@{DATABASE} object. +-- This is done at the beginning of the mission (when the mission starts), and dynamically when new DCS Unit objects are spawned (using the @{SPAWN} class). +-- +-- The UNIT class **does not contain a :New()** method, rather it provides **:Find()** methods to retrieve the object reference +-- using the DCS Unit or the DCS UnitName. +-- +-- Another thing to know is that UNIT objects do not "contain" the DCS Unit object. +-- The UNIT methods will reference the DCS Unit object by name when it is needed during API execution. +-- If the DCS Unit object does not exist or is nil, the UNIT methods will return nil and log an exception in the DCS.log file. +-- +-- The UNIT class provides the following functions to retrieve quickly the relevant UNIT instance: +-- +-- * @{#UNIT.Find}(): Find a UNIT instance from the _DATABASE object using a DCS Unit object. +-- * @{#UNIT.FindByName}(): Find a UNIT instance from the _DATABASE object using a DCS Unit name. +-- +-- IMPORTANT: ONE SHOULD NEVER SANATIZE these UNIT OBJECT REFERENCES! (make the UNIT object references nil). +-- +-- 1.2) DCS UNIT APIs +-- ------------------ +-- The DCS Unit APIs are used extensively within MOOSE. The UNIT class has for each DCS Unit API a corresponding method. +-- To be able to distinguish easily in your code the difference between a UNIT API call and a DCS Unit API call, +-- the first letter of the method is also capitalized. So, by example, the DCS Unit method @{Dcs.DCSWrapper.Unit#Unit.getName}() +-- is implemented in the UNIT class as @{#UNIT.GetName}(). +-- +-- 1.3) Smoke, Flare Units +-- ----------------------- +-- The UNIT class provides methods to smoke or flare units easily. +-- The @{#UNIT.SmokeBlue}(), @{#UNIT.SmokeGreen}(),@{#UNIT.SmokeOrange}(), @{#UNIT.SmokeRed}(), @{#UNIT.SmokeRed}() methods +-- will smoke the unit in the corresponding color. Note that smoking a unit is done at the current position of the DCS Unit. +-- When the DCS Unit moves for whatever reason, the smoking will still continue! +-- The @{#UNIT.FlareGreen}(), @{#UNIT.FlareRed}(), @{#UNIT.FlareWhite}(), @{#UNIT.FlareYellow}() +-- methods will fire off a flare in the air with the corresponding color. Note that a flare is a one-off shot and its effect is of very short duration. +-- +-- 1.4) Location Position, Point +-- ----------------------------- +-- The UNIT class provides methods to obtain the current point or position of the DCS Unit. +-- The @{#UNIT.GetPointVec2}(), @{#UNIT.GetVec3}() will obtain the current **location** of the DCS Unit in a Vec2 (2D) or a **point** in a Vec3 (3D) vector respectively. +-- If you want to obtain the complete **3D position** including ori�ntation and direction vectors, consult the @{#UNIT.GetPositionVec3}() method respectively. +-- +-- 1.5) Test if alive +-- ------------------ +-- The @{#UNIT.IsAlive}(), @{#UNIT.IsActive}() methods determines if the DCS Unit is alive, meaning, it is existing and active. +-- +-- 1.6) Test for proximity +-- ----------------------- +-- The UNIT class contains methods to test the location or proximity against zones or other objects. +-- +-- ### 1.6.1) Zones +-- To test whether the Unit is within a **zone**, use the @{#UNIT.IsInZone}() or the @{#UNIT.IsNotInZone}() methods. Any zone can be tested on, but the zone must be derived from @{Core.Zone#ZONE_BASE}. +-- +-- ### 1.6.2) Units +-- Test if another DCS Unit is within a given radius of the current DCS Unit, use the @{#UNIT.OtherUnitInRadius}() method. +-- +-- @module Unit +-- @author FlightControl + + + + + +--- The UNIT class +-- @type UNIT +-- @extends Wrapper.Controllable#CONTROLLABLE +UNIT = { + ClassName="UNIT", +} + + +--- Unit.SensorType +-- @type Unit.SensorType +-- @field OPTIC +-- @field RADAR +-- @field IRST +-- @field RWR + + +-- Registration. + +--- Create a new UNIT from DCSUnit. +-- @param #UNIT self +-- @param #string UnitName The name of the DCS unit. +-- @return #UNIT +function UNIT:Register( UnitName ) + local self = BASE:Inherit( self, CONTROLLABLE:New( UnitName ) ) + self.UnitName = UnitName + return self +end + +-- Reference methods. + +--- Finds a UNIT from the _DATABASE using a DCSUnit object. +-- @param #UNIT self +-- @param Dcs.DCSWrapper.Unit#Unit DCSUnit An existing DCS Unit object reference. +-- @return #UNIT self +function UNIT:Find( DCSUnit ) + + local UnitName = DCSUnit:getName() + local UnitFound = _DATABASE:FindUnit( UnitName ) + return UnitFound +end + +--- Find a UNIT in the _DATABASE using the name of an existing DCS Unit. +-- @param #UNIT self +-- @param #string UnitName The Unit Name. +-- @return #UNIT self +function UNIT:FindByName( UnitName ) + + local UnitFound = _DATABASE:FindUnit( UnitName ) + return UnitFound +end + +--- Return the name of the UNIT. +-- @param #UNIT self +-- @return #string The UNIT name. +function UNIT:Name() + + return self.UnitName +end + + +--- @param #UNIT self +-- @return Dcs.DCSWrapper.Unit#Unit +function UNIT:GetDCSObject() + + local DCSUnit = Unit.getByName( self.UnitName ) + + if DCSUnit then + return DCSUnit + end + + return nil +end + +--- Respawn the @{Unit} using a (tweaked) template of the parent Group. +-- +-- This function will: +-- +-- * Get the current position and heading of the group. +-- * When the unit is alive, it will tweak the template x, y and heading coordinates of the group and the embedded units to the current units positions. +-- * Then it will respawn the re-modelled group. +-- +-- @param #UNIT self +-- @param Dcs.DCSTypes#Vec3 SpawnVec3 The position where to Spawn the new Unit at. +-- @param #number Heading The heading of the unit respawn. +function UNIT:ReSpawn( SpawnVec3, Heading ) + + local SpawnGroupTemplate = UTILS.DeepCopy( _DATABASE:GetGroupTemplateFromUnitName( self:Name() ) ) + self:T( SpawnGroupTemplate ) + + local SpawnGroup = self:GetGroup() + + if SpawnGroup then + + local Vec3 = SpawnGroup:GetVec3() + SpawnGroupTemplate.x = SpawnVec3.x + SpawnGroupTemplate.y = SpawnVec3.z + + self:E( #SpawnGroupTemplate.units ) + for UnitID, UnitData in pairs( SpawnGroup:GetUnits() ) do + local GroupUnit = UnitData -- #UNIT + self:E( GroupUnit:GetName() ) + if GroupUnit:IsAlive() then + local GroupUnitVec3 = GroupUnit:GetVec3() + local GroupUnitHeading = GroupUnit:GetHeading() + SpawnGroupTemplate.units[UnitID].alt = GroupUnitVec3.y + SpawnGroupTemplate.units[UnitID].x = GroupUnitVec3.x + SpawnGroupTemplate.units[UnitID].y = GroupUnitVec3.z + SpawnGroupTemplate.units[UnitID].heading = GroupUnitHeading + self:E( { UnitID, SpawnGroupTemplate.units[UnitID], SpawnGroupTemplate.units[UnitID] } ) + end + end + end + + for UnitTemplateID, UnitTemplateData in pairs( SpawnGroupTemplate.units ) do + self:T( UnitTemplateData.name ) + if UnitTemplateData.name == self:Name() then + self:T("Adjusting") + SpawnGroupTemplate.units[UnitTemplateID].alt = SpawnVec3.y + SpawnGroupTemplate.units[UnitTemplateID].x = SpawnVec3.x + SpawnGroupTemplate.units[UnitTemplateID].y = SpawnVec3.z + SpawnGroupTemplate.units[UnitTemplateID].heading = Heading + self:E( { UnitTemplateID, SpawnGroupTemplate.units[UnitTemplateID], SpawnGroupTemplate.units[UnitTemplateID] } ) + else + self:E( SpawnGroupTemplate.units[UnitTemplateID].name ) + local GroupUnit = UNIT:FindByName( SpawnGroupTemplate.units[UnitTemplateID].name ) -- #UNIT + if GroupUnit and GroupUnit:IsAlive() then + local GroupUnitVec3 = GroupUnit:GetVec3() + local GroupUnitHeading = GroupUnit:GetHeading() + UnitTemplateData.alt = GroupUnitVec3.y + UnitTemplateData.x = GroupUnitVec3.x + UnitTemplateData.y = GroupUnitVec3.z + UnitTemplateData.heading = GroupUnitHeading + else + if SpawnGroupTemplate.units[UnitTemplateID].name ~= self:Name() then + self:T("nilling") + SpawnGroupTemplate.units[UnitTemplateID].delete = true + end + end + end + end + + -- Remove obscolete units from the group structure + i = 1 + while i <= #SpawnGroupTemplate.units do + + local UnitTemplateData = SpawnGroupTemplate.units[i] + self:T( UnitTemplateData.name ) + + if UnitTemplateData.delete then + table.remove( SpawnGroupTemplate.units, i ) + else + i = i + 1 + end + end + + _DATABASE:Spawn( SpawnGroupTemplate ) +end + + + +--- Returns if the unit is activated. +-- @param #UNIT self +-- @return #boolean true if Unit is activated. +-- @return #nil The DCS Unit is not existing or alive. +function UNIT:IsActive() + self:F2( self.UnitName ) + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + + local UnitIsActive = DCSUnit:isActive() + return UnitIsActive + end + + return nil +end + + + +--- Returns the Unit's callsign - the localized string. +-- @param #UNIT self +-- @return #string The Callsign of the Unit. +-- @return #nil The DCS Unit is not existing or alive. +function UNIT:GetCallsign() + self:F2( self.UnitName ) + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitCallSign = DCSUnit:getCallsign() + return UnitCallSign + end + + self:E( self.ClassName .. " " .. self.UnitName .. " not found!" ) + return nil +end + + +--- Returns name of the player that control the unit or nil if the unit is controlled by A.I. +-- @param #UNIT self +-- @return #string Player Name +-- @return #nil The DCS Unit is not existing or alive. +function UNIT:GetPlayerName() + self:F2( self.UnitName ) + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + + local PlayerName = DCSUnit:getPlayerName() + if PlayerName == nil then + PlayerName = "" + end + return PlayerName + end + + return nil +end + +--- Returns the unit's number in the group. +-- The number is the same number the unit has in ME. +-- It may not be changed during the mission. +-- If any unit in the group is destroyed, the numbers of another units will not be changed. +-- @param #UNIT self +-- @return #number The Unit number. +-- @return #nil The DCS Unit is not existing or alive. +function UNIT:GetNumber() + self:F2( self.UnitName ) + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitNumber = DCSUnit:getNumber() + return UnitNumber + end + + return nil +end + +--- Returns the unit's group if it exist and nil otherwise. +-- @param Wrapper.Unit#UNIT self +-- @return Wrapper.Group#GROUP The Group of the Unit. +-- @return #nil The DCS Unit is not existing or alive. +function UNIT:GetGroup() + self:F2( self.UnitName ) + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitGroup = GROUP:Find( DCSUnit:getGroup() ) + return UnitGroup + end + + return nil +end + + +-- Need to add here functions to check if radar is on and which object etc. + +--- Returns the prefix name of the DCS Unit. A prefix name is a part of the name before a '#'-sign. +-- DCS Units spawned with the @{SPAWN} class contain a '#'-sign to indicate the end of the (base) DCS Unit name. +-- The spawn sequence number and unit number are contained within the name after the '#' sign. +-- @param #UNIT self +-- @return #string The name of the DCS Unit. +-- @return #nil The DCS Unit is not existing or alive. +function UNIT:GetPrefix() + self:F2( self.UnitName ) + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitPrefix = string.match( self.UnitName, ".*#" ):sub( 1, -2 ) + self:T3( UnitPrefix ) + return UnitPrefix + end + + return nil +end + +--- Returns the Unit's ammunition. +-- @param #UNIT self +-- @return Dcs.DCSWrapper.Unit#Unit.Ammo +-- @return #nil The DCS Unit is not existing or alive. +function UNIT:GetAmmo() + self:F2( self.UnitName ) + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitAmmo = DCSUnit:getAmmo() + return UnitAmmo + end + + return nil +end + +--- Returns the unit sensors. +-- @param #UNIT self +-- @return Dcs.DCSWrapper.Unit#Unit.Sensors +-- @return #nil The DCS Unit is not existing or alive. +function UNIT:GetSensors() + self:F2( self.UnitName ) + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitSensors = DCSUnit:getSensors() + return UnitSensors + end + + return nil +end + +-- Need to add here a function per sensortype +-- unit:hasSensors(Unit.SensorType.RADAR, Unit.RadarType.AS) + +--- Returns if the unit has sensors of a certain type. +-- @param #UNIT self +-- @return #boolean returns true if the unit has specified types of sensors. This function is more preferable than Unit.getSensors() if you don't want to get information about all the unit's sensors, and just want to check if the unit has specified types of sensors. +-- @return #nil The DCS Unit is not existing or alive. +function UNIT:HasSensors( ... ) + self:F2( arg ) + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local HasSensors = DCSUnit:hasSensors( unpack( arg ) ) + return HasSensors + end + + return nil +end + +--- Returns if the unit is SEADable. +-- @param #UNIT self +-- @return #boolean returns true if the unit is SEADable. +-- @return #nil The DCS Unit is not existing or alive. +function UNIT:HasSEAD() + self:F2() + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitSEADAttributes = DCSUnit:getDesc().attributes + + local HasSEAD = false + if UnitSEADAttributes["RADAR_BAND1_FOR_ARM"] and UnitSEADAttributes["RADAR_BAND1_FOR_ARM"] == true or + UnitSEADAttributes["RADAR_BAND2_FOR_ARM"] and UnitSEADAttributes["RADAR_BAND2_FOR_ARM"] == true then + HasSEAD = true + end + return HasSEAD + end + + return nil +end + +--- Returns two values: +-- +-- * First value indicates if at least one of the unit's radar(s) is on. +-- * Second value is the object of the radar's interest. Not nil only if at least one radar of the unit is tracking a target. +-- @param #UNIT self +-- @return #boolean Indicates if at least one of the unit's radar(s) is on. +-- @return Dcs.DCSWrapper.Object#Object The object of the radar's interest. Not nil only if at least one radar of the unit is tracking a target. +-- @return #nil The DCS Unit is not existing or alive. +function UNIT:GetRadar() + self:F2( self.UnitName ) + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitRadarOn, UnitRadarObject = DCSUnit:getRadar() + return UnitRadarOn, UnitRadarObject + end + + return nil, nil +end + +--- Returns relative amount of fuel (from 0.0 to 1.0) the unit has in its internal tanks. If there are additional fuel tanks the value may be greater than 1.0. +-- @param #UNIT self +-- @return #number The relative amount of fuel (from 0.0 to 1.0). +-- @return #nil The DCS Unit is not existing or alive. +function UNIT:GetFuel() + self:F2( self.UnitName ) + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitFuel = DCSUnit:getFuel() + return UnitFuel + end + + return nil +end + +--- Returns the unit's health. Dead units has health <= 1.0. +-- @param #UNIT self +-- @return #number The Unit's health value. +-- @return #nil The DCS Unit is not existing or alive. +function UNIT:GetLife() + self:F2( self.UnitName ) + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitLife = DCSUnit:getLife() + return UnitLife + end + + return nil +end + +--- Returns the Unit's initial health. +-- @param #UNIT self +-- @return #number The Unit's initial health value. +-- @return #nil The DCS Unit is not existing or alive. +function UNIT:GetLife0() + self:F2( self.UnitName ) + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitLife0 = DCSUnit:getLife0() + return UnitLife0 + end + + return nil +end + +--- Returns the Unit's A2G threat level on a scale from 1 to 10 ... +-- The following threat levels are foreseen: +-- +-- * Threat level 0: Unit is unarmed. +-- * Threat level 1: Unit is infantry. +-- * Threat level 2: Unit is an infantry vehicle. +-- * Threat level 3: Unit is ground artillery. +-- * Threat level 4: Unit is a tank. +-- * Threat level 5: Unit is a modern tank or ifv with ATGM. +-- * Threat level 6: Unit is a AAA. +-- * Threat level 7: Unit is a SAM or manpad, IR guided. +-- * Threat level 8: Unit is a Short Range SAM, radar guided. +-- * Threat level 9: Unit is a Medium Range SAM, radar guided. +-- * Threat level 10: Unit is a Long Range SAM, radar guided. +function UNIT:GetThreatLevel() + + local Attributes = self:GetDesc().attributes + local ThreatLevel = 0 + + local ThreatLevels = { + "Unarmed", + "Infantry", + "Old Tanks & APCs", + "Tanks & IFVs without ATGM", + "Tanks & IFV with ATGM", + "Modern Tanks", + "AAA", + "IR Guided SAMs", + "SR SAMs", + "MR SAMs", + "LR SAMs" + } + + self:T2( Attributes ) + + if Attributes["LR SAM"] then ThreatLevel = 10 + elseif Attributes["MR SAM"] then ThreatLevel = 9 + elseif Attributes["SR SAM"] and + not Attributes["IR Guided SAM"] then ThreatLevel = 8 + elseif ( Attributes["SR SAM"] or Attributes["MANPADS"] ) and + Attributes["IR Guided SAM"] then ThreatLevel = 7 + elseif Attributes["AAA"] then ThreatLevel = 6 + elseif Attributes["Modern Tanks"] then ThreatLevel = 5 + elseif ( Attributes["Tanks"] or Attributes["IFV"] ) and + Attributes["ATGM"] then ThreatLevel = 4 + elseif ( Attributes["Tanks"] or Attributes["IFV"] ) and + not Attributes["ATGM"] then ThreatLevel = 3 + elseif Attributes["Old Tanks"] or Attributes["APC"] then ThreatLevel = 2 + elseif Attributes["Infantry"] then ThreatLevel = 1 + end + + self:T2( ThreatLevel ) + return ThreatLevel, ThreatLevels[ThreatLevel+1] + +end + + +-- Is functions + +--- Returns true if the unit is within a @{Zone}. +-- @param #UNIT self +-- @param Core.Zone#ZONE_BASE Zone The zone to test. +-- @return #boolean Returns true if the unit is within the @{Core.Zone#ZONE_BASE} +function UNIT:IsInZone( Zone ) + self:F2( { self.UnitName, Zone } ) + + if self:IsAlive() then + local IsInZone = Zone:IsPointVec3InZone( self:GetVec3() ) + + self:T( { IsInZone } ) + return IsInZone + end + + return false +end + +--- Returns true if the unit is not within a @{Zone}. +-- @param #UNIT self +-- @param Core.Zone#ZONE_BASE Zone The zone to test. +-- @return #boolean Returns true if the unit is not within the @{Core.Zone#ZONE_BASE} +function UNIT:IsNotInZone( Zone ) + self:F2( { self.UnitName, Zone } ) + + if self:IsAlive() then + local IsInZone = not Zone:IsPointVec3InZone( self:GetVec3() ) + + self:T( { IsInZone } ) + return IsInZone + else + return false + end +end + + +--- Returns true if there is an **other** DCS Unit within a radius of the current 2D point of the DCS Unit. +-- @param #UNIT self +-- @param #UNIT AwaitUnit The other UNIT wrapper object. +-- @param Radius The radius in meters with the DCS Unit in the centre. +-- @return true If the other DCS Unit is within the radius of the 2D point of the DCS Unit. +-- @return #nil The DCS Unit is not existing or alive. +function UNIT:OtherUnitInRadius( AwaitUnit, Radius ) + self:F2( { self.UnitName, AwaitUnit.UnitName, Radius } ) + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitVec3 = self:GetVec3() + local AwaitUnitVec3 = AwaitUnit:GetVec3() + + if (((UnitVec3.x - AwaitUnitVec3.x)^2 + (UnitVec3.z - AwaitUnitVec3.z)^2)^0.5 <= Radius) then + self:T3( "true" ) + return true + else + self:T3( "false" ) + return false + end + end + + return nil +end + + + +--- Signal a flare at the position of the UNIT. +-- @param #UNIT self +-- @param Utilities.Utils#FLARECOLOR FlareColor +function UNIT:Flare( FlareColor ) + self:F2() + trigger.action.signalFlare( self:GetVec3(), FlareColor , 0 ) +end + +--- Signal a white flare at the position of the UNIT. +-- @param #UNIT self +function UNIT:FlareWhite() + self:F2() + trigger.action.signalFlare( self:GetVec3(), trigger.flareColor.White , 0 ) +end + +--- Signal a yellow flare at the position of the UNIT. +-- @param #UNIT self +function UNIT:FlareYellow() + self:F2() + trigger.action.signalFlare( self:GetVec3(), trigger.flareColor.Yellow , 0 ) +end + +--- Signal a green flare at the position of the UNIT. +-- @param #UNIT self +function UNIT:FlareGreen() + self:F2() + trigger.action.signalFlare( self:GetVec3(), trigger.flareColor.Green , 0 ) +end + +--- Signal a red flare at the position of the UNIT. +-- @param #UNIT self +function UNIT:FlareRed() + self:F2() + local Vec3 = self:GetVec3() + if Vec3 then + trigger.action.signalFlare( Vec3, trigger.flareColor.Red, 0 ) + end +end + +--- Smoke the UNIT. +-- @param #UNIT self +function UNIT:Smoke( SmokeColor, Range ) + self:F2() + if Range then + trigger.action.smoke( self:GetRandomVec3( Range ), SmokeColor ) + else + trigger.action.smoke( self:GetVec3(), SmokeColor ) + end + +end + +--- Smoke the UNIT Green. +-- @param #UNIT self +function UNIT:SmokeGreen() + self:F2() + trigger.action.smoke( self:GetVec3(), trigger.smokeColor.Green ) +end + +--- Smoke the UNIT Red. +-- @param #UNIT self +function UNIT:SmokeRed() + self:F2() + trigger.action.smoke( self:GetVec3(), trigger.smokeColor.Red ) +end + +--- Smoke the UNIT White. +-- @param #UNIT self +function UNIT:SmokeWhite() + self:F2() + trigger.action.smoke( self:GetVec3(), trigger.smokeColor.White ) +end + +--- Smoke the UNIT Orange. +-- @param #UNIT self +function UNIT:SmokeOrange() + self:F2() + trigger.action.smoke( self:GetVec3(), trigger.smokeColor.Orange ) +end + +--- Smoke the UNIT Blue. +-- @param #UNIT self +function UNIT:SmokeBlue() + self:F2() + trigger.action.smoke( self:GetVec3(), trigger.smokeColor.Blue ) +end + +-- Is methods + +--- Returns if the unit is of an air category. +-- If the unit is a helicopter or a plane, then this method will return true, otherwise false. +-- @param #UNIT self +-- @return #boolean Air category evaluation result. +function UNIT:IsAir() + self:F2() + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitDescriptor = DCSUnit:getDesc() + self:T3( { UnitDescriptor.category, Unit.Category.AIRPLANE, Unit.Category.HELICOPTER } ) + + local IsAirResult = ( UnitDescriptor.category == Unit.Category.AIRPLANE ) or ( UnitDescriptor.category == Unit.Category.HELICOPTER ) + + self:T3( IsAirResult ) + return IsAirResult + end + + return nil +end + +--- Returns if the unit is of an ground category. +-- If the unit is a ground vehicle or infantry, this method will return true, otherwise false. +-- @param #UNIT self +-- @return #boolean Ground category evaluation result. +function UNIT:IsGround() + self:F2() + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitDescriptor = DCSUnit:getDesc() + self:T3( { UnitDescriptor.category, Unit.Category.GROUND_UNIT } ) + + local IsGroundResult = ( UnitDescriptor.category == Unit.Category.GROUND_UNIT ) + + self:T3( IsGroundResult ) + return IsGroundResult + end + + return nil +end + +--- Returns if the unit is a friendly unit. +-- @param #UNIT self +-- @return #boolean IsFriendly evaluation result. +function UNIT:IsFriendly( FriendlyCoalition ) + self:F2() + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitCoalition = DCSUnit:getCoalition() + self:T3( { UnitCoalition, FriendlyCoalition } ) + + local IsFriendlyResult = ( UnitCoalition == FriendlyCoalition ) + + self:E( IsFriendlyResult ) + return IsFriendlyResult + end + + return nil +end + +--- Returns if the unit is of a ship category. +-- If the unit is a ship, this method will return true, otherwise false. +-- @param #UNIT self +-- @return #boolean Ship category evaluation result. +function UNIT:IsShip() + self:F2() + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitDescriptor = DCSUnit:getDesc() + self:T3( { UnitDescriptor.category, Unit.Category.SHIP } ) + + local IsShipResult = ( UnitDescriptor.category == Unit.Category.SHIP ) + + self:T3( IsShipResult ) + return IsShipResult + end + + return nil +end + +--- This module contains the CLIENT class. +-- +-- 1) @{Wrapper.Client#CLIENT} class, extends @{Wrapper.Unit#UNIT} +-- =============================================== +-- Clients are those **Units** defined within the Mission Editor that have the skillset defined as __Client__ or __Player__. +-- Note that clients are NOT the same as Units, they are NOT necessarily alive. +-- The @{Wrapper.Client#CLIENT} class is a wrapper class to handle the DCS Unit objects that have the skillset defined as __Client__ or __Player__: +-- +-- * Wraps the DCS Unit objects with skill level set to Player or Client. +-- * Support all DCS Unit APIs. +-- * Enhance with Unit specific APIs not in the DCS Group API set. +-- * When player joins Unit, execute alive init logic. +-- * Handles messages to players. +-- * Manage the "state" of the DCS Unit. +-- +-- Clients are being used by the @{MISSION} class to follow players and register their successes. +-- +-- 1.1) CLIENT reference methods +-- ----------------------------- +-- For each DCS Unit having skill level Player or Client, a CLIENT wrapper object (instance) will be created within the _@{DATABASE} object. +-- This is done at the beginning of the mission (when the mission starts). +-- +-- The CLIENT class does not contain a :New() method, rather it provides :Find() methods to retrieve the object reference +-- using the DCS Unit or the DCS UnitName. +-- +-- Another thing to know is that CLIENT objects do not "contain" the DCS Unit object. +-- The CLIENT methods will reference the DCS Unit object by name when it is needed during API execution. +-- If the DCS Unit object does not exist or is nil, the CLIENT methods will return nil and log an exception in the DCS.log file. +-- +-- The CLIENT class provides the following functions to retrieve quickly the relevant CLIENT instance: +-- +-- * @{#CLIENT.Find}(): Find a CLIENT instance from the _DATABASE object using a DCS Unit object. +-- * @{#CLIENT.FindByName}(): Find a CLIENT instance from the _DATABASE object using a DCS Unit name. +-- +-- IMPORTANT: ONE SHOULD NEVER SANATIZE these CLIENT OBJECT REFERENCES! (make the CLIENT object references nil). +-- +-- @module Client +-- @author FlightControl + +--- The CLIENT class +-- @type CLIENT +-- @extends Wrapper.Unit#UNIT +CLIENT = { + ONBOARDSIDE = { + NONE = 0, + LEFT = 1, + RIGHT = 2, + BACK = 3, + FRONT = 4 + }, + ClassName = "CLIENT", + ClientName = nil, + ClientAlive = false, + ClientTransport = false, + ClientBriefingShown = false, + _Menus = {}, + _Tasks = {}, + Messages = { + } +} + + +--- Finds a CLIENT from the _DATABASE using the relevant DCS Unit. +-- @param #CLIENT self +-- @param #string ClientName Name of the DCS **Unit** as defined within the Mission Editor. +-- @param #string ClientBriefing Text that describes the briefing of the mission when a Player logs into the Client. +-- @return #CLIENT +-- @usage +-- -- Create new Clients. +-- local Mission = MISSIONSCHEDULER.AddMission( 'Russia Transport Troops SA-6', 'Operational', 'Transport troops from the control center to one of the SA-6 SAM sites to activate their operation.', 'Russia' ) +-- Mission:AddGoal( DeploySA6TroopsGoal ) +-- +-- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*HOT-Deploy Troops 1' ):Transport() ) +-- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*RAMP-Deploy Troops 3' ):Transport() ) +-- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*HOT-Deploy Troops 2' ):Transport() ) +-- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*RAMP-Deploy Troops 4' ):Transport() ) +function CLIENT:Find( DCSUnit ) + local ClientName = DCSUnit:getName() + local ClientFound = _DATABASE:FindClient( ClientName ) + + if ClientFound then + ClientFound:F( ClientName ) + return ClientFound + end + + error( "CLIENT not found for: " .. ClientName ) +end + + +--- Finds a CLIENT from the _DATABASE using the relevant Client Unit Name. +-- As an optional parameter, a briefing text can be given also. +-- @param #CLIENT self +-- @param #string ClientName Name of the DCS **Unit** as defined within the Mission Editor. +-- @param #string ClientBriefing Text that describes the briefing of the mission when a Player logs into the Client. +-- @param #boolean Error A flag that indicates whether an error should be raised if the CLIENT cannot be found. By default an error will be raised. +-- @return #CLIENT +-- @usage +-- -- Create new Clients. +-- local Mission = MISSIONSCHEDULER.AddMission( 'Russia Transport Troops SA-6', 'Operational', 'Transport troops from the control center to one of the SA-6 SAM sites to activate their operation.', 'Russia' ) +-- Mission:AddGoal( DeploySA6TroopsGoal ) +-- +-- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*HOT-Deploy Troops 1' ):Transport() ) +-- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*RAMP-Deploy Troops 3' ):Transport() ) +-- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*HOT-Deploy Troops 2' ):Transport() ) +-- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*RAMP-Deploy Troops 4' ):Transport() ) +function CLIENT:FindByName( ClientName, ClientBriefing, Error ) + local ClientFound = _DATABASE:FindClient( ClientName ) + + if ClientFound then + ClientFound:F( { ClientName, ClientBriefing } ) + ClientFound:AddBriefing( ClientBriefing ) + ClientFound.MessageSwitch = true + + return ClientFound + end + + if not Error then + error( "CLIENT not found for: " .. ClientName ) + end +end + +function CLIENT:Register( ClientName ) + local self = BASE:Inherit( self, UNIT:Register( ClientName ) ) + + self:F( ClientName ) + self.ClientName = ClientName + self.MessageSwitch = true + self.ClientAlive2 = false + + --self.AliveCheckScheduler = routines.scheduleFunction( self._AliveCheckScheduler, { self }, timer.getTime() + 1, 5 ) + self.AliveCheckScheduler = SCHEDULER:New( self, self._AliveCheckScheduler, { "Client Alive " .. ClientName }, 1, 5 ) + + self:E( self ) + return self +end + + +--- Transport defines that the Client is a Transport. Transports show cargo. +-- @param #CLIENT self +-- @return #CLIENT +function CLIENT:Transport() + self:F() + + self.ClientTransport = true + return self +end + +--- AddBriefing adds a briefing to a CLIENT when a player joins a mission. +-- @param #CLIENT self +-- @param #string ClientBriefing is the text defining the Mission briefing. +-- @return #CLIENT self +function CLIENT:AddBriefing( ClientBriefing ) + self:F( ClientBriefing ) + self.ClientBriefing = ClientBriefing + self.ClientBriefingShown = false + + return self +end + +--- Show the briefing of a CLIENT. +-- @param #CLIENT self +-- @return #CLIENT self +function CLIENT:ShowBriefing() + self:F( { self.ClientName, self.ClientBriefingShown } ) + + if not self.ClientBriefingShown then + self.ClientBriefingShown = true + local Briefing = "" + if self.ClientBriefing then + Briefing = Briefing .. self.ClientBriefing + end + Briefing = Briefing .. " Press [LEFT ALT]+[B] to view the complete mission briefing." + self:Message( Briefing, 60, "Briefing" ) + end + + return self +end + +--- Show the mission briefing of a MISSION to the CLIENT. +-- @param #CLIENT self +-- @param #string MissionBriefing +-- @return #CLIENT self +function CLIENT:ShowMissionBriefing( MissionBriefing ) + self:F( { self.ClientName } ) + + if MissionBriefing then + self:Message( MissionBriefing, 60, "Mission Briefing" ) + end + + return self +end + + + +--- Resets a CLIENT. +-- @param #CLIENT self +-- @param #string ClientName Name of the Group as defined within the Mission Editor. The Group must have a Unit with the type Client. +function CLIENT:Reset( ClientName ) + self:F() + self._Menus = {} +end + +-- Is Functions + +--- Checks if the CLIENT is a multi-seated UNIT. +-- @param #CLIENT self +-- @return #boolean true if multi-seated. +function CLIENT:IsMultiSeated() + self:F( self.ClientName ) + + local ClientMultiSeatedTypes = { + ["Mi-8MT"] = "Mi-8MT", + ["UH-1H"] = "UH-1H", + ["P-51B"] = "P-51B" + } + + if self:IsAlive() then + local ClientTypeName = self:GetClientGroupUnit():GetTypeName() + if ClientMultiSeatedTypes[ClientTypeName] then + return true + end + end + + return false +end + +--- Checks for a client alive event and calls a function on a continuous basis. +-- @param #CLIENT self +-- @param #function CallBack Function. +-- @return #CLIENT +function CLIENT:Alive( CallBackFunction, ... ) + self:F() + + self.ClientCallBack = CallBackFunction + self.ClientParameters = arg + + return self +end + +--- @param #CLIENT self +function CLIENT:_AliveCheckScheduler( SchedulerName ) + self:F3( { SchedulerName, self.ClientName, self.ClientAlive2, self.ClientBriefingShown, self.ClientCallBack } ) + + if self:IsAlive() then + if self.ClientAlive2 == false then + self:ShowBriefing() + if self.ClientCallBack then + self:T("Calling Callback function") + self.ClientCallBack( self, unpack( self.ClientParameters ) ) + end + self.ClientAlive2 = true + end + else + if self.ClientAlive2 == true then + self.ClientAlive2 = false + end + end + + return true +end + +--- Return the DCSGroup of a Client. +-- This function is modified to deal with a couple of bugs in DCS 1.5.3 +-- @param #CLIENT self +-- @return Dcs.DCSWrapper.Group#Group +function CLIENT:GetDCSGroup() + self:F3() + +-- local ClientData = Group.getByName( self.ClientName ) +-- if ClientData and ClientData:isExist() then +-- self:T( self.ClientName .. " : group found!" ) +-- return ClientData +-- else +-- return nil +-- end + + local ClientUnit = Unit.getByName( self.ClientName ) + + local CoalitionsData = { AlivePlayersRed = coalition.getPlayers( coalition.side.RED ), AlivePlayersBlue = coalition.getPlayers( coalition.side.BLUE ) } + for CoalitionId, CoalitionData in pairs( CoalitionsData ) do + self:T3( { "CoalitionData:", CoalitionData } ) + for UnitId, UnitData in pairs( CoalitionData ) do + self:T3( { "UnitData:", UnitData } ) + if UnitData and UnitData:isExist() then + + --self:E(self.ClientName) + if ClientUnit then + local ClientGroup = ClientUnit:getGroup() + if ClientGroup then + self:T3( "ClientGroup = " .. self.ClientName ) + if ClientGroup:isExist() and UnitData:getGroup():isExist() then + if ClientGroup:getID() == UnitData:getGroup():getID() then + self:T3( "Normal logic" ) + self:T3( self.ClientName .. " : group found!" ) + self.ClientGroupID = ClientGroup:getID() + self.ClientGroupName = ClientGroup:getName() + return ClientGroup + end + else + -- Now we need to resolve the bugs in DCS 1.5 ... + -- Consult the database for the units of the Client Group. (ClientGroup:getUnits() returns nil) + self:T3( "Bug 1.5 logic" ) + local ClientGroupTemplate = _DATABASE.Templates.Units[self.ClientName].GroupTemplate + self.ClientGroupID = ClientGroupTemplate.groupId + self.ClientGroupName = _DATABASE.Templates.Units[self.ClientName].GroupName + self:T3( self.ClientName .. " : group found in bug 1.5 resolvement logic!" ) + return ClientGroup + end + -- else + -- error( "Client " .. self.ClientName .. " not found!" ) + end + else + --self:E( { "Client not found!", self.ClientName } ) + end + end + end + end + + -- For non player clients + if ClientUnit then + local ClientGroup = ClientUnit:getGroup() + if ClientGroup then + self:T3( "ClientGroup = " .. self.ClientName ) + if ClientGroup:isExist() then + self:T3( "Normal logic" ) + self:T3( self.ClientName .. " : group found!" ) + return ClientGroup + end + end + end + + self.ClientGroupID = nil + self.ClientGroupUnit = nil + + return nil +end + + +-- TODO: Check Dcs.DCSTypes#Group.ID +--- Get the group ID of the client. +-- @param #CLIENT self +-- @return Dcs.DCSTypes#Group.ID +function CLIENT:GetClientGroupID() + + local ClientGroup = self:GetDCSGroup() + + --self:E( self.ClientGroupID ) -- Determined in GetDCSGroup() + return self.ClientGroupID +end + + +--- Get the name of the group of the client. +-- @param #CLIENT self +-- @return #string +function CLIENT:GetClientGroupName() + + local ClientGroup = self:GetDCSGroup() + + self:T( self.ClientGroupName ) -- Determined in GetDCSGroup() + return self.ClientGroupName +end + +--- Returns the UNIT of the CLIENT. +-- @param #CLIENT self +-- @return Wrapper.Unit#UNIT +function CLIENT:GetClientGroupUnit() + self:F2() + + local ClientDCSUnit = Unit.getByName( self.ClientName ) + + self:T( self.ClientDCSUnit ) + if ClientDCSUnit and ClientDCSUnit:isExist() then + local ClientUnit = _DATABASE:FindUnit( self.ClientName ) + self:T2( ClientUnit ) + return ClientUnit + end +end + +--- Returns the DCSUnit of the CLIENT. +-- @param #CLIENT self +-- @return Dcs.DCSTypes#Unit +function CLIENT:GetClientGroupDCSUnit() + self:F2() + + local ClientDCSUnit = Unit.getByName( self.ClientName ) + + if ClientDCSUnit and ClientDCSUnit:isExist() then + self:T2( ClientDCSUnit ) + return ClientDCSUnit + end +end + + +--- Evaluates if the CLIENT is a transport. +-- @param #CLIENT self +-- @return #boolean true is a transport. +function CLIENT:IsTransport() + self:F() + return self.ClientTransport +end + +--- Shows the @{AI.AI_Cargo#CARGO} contained within the CLIENT to the player as a message. +-- The @{AI.AI_Cargo#CARGO} is shown using the @{Core.Message#MESSAGE} distribution system. +-- @param #CLIENT self +function CLIENT:ShowCargo() + self:F() + + local CargoMsg = "" + + for CargoName, Cargo in pairs( CARGOS ) do + if self == Cargo:IsLoadedInClient() then + CargoMsg = CargoMsg .. Cargo.CargoName .. " Type:" .. Cargo.CargoType .. " Weight: " .. Cargo.CargoWeight .. "\n" + end + end + + if CargoMsg == "" then + CargoMsg = "empty" + end + + self:Message( CargoMsg, 15, "Co-Pilot: Cargo Status", 30 ) + +end + +-- TODO (1) I urgently need to revise this. +--- A local function called by the DCS World Menu system to switch off messages. +function CLIENT.SwitchMessages( PrmTable ) + PrmTable[1].MessageSwitch = PrmTable[2] +end + +--- The main message driver for the CLIENT. +-- This function displays various messages to the Player logged into the CLIENT through the DCS World Messaging system. +-- @param #CLIENT self +-- @param #string Message is the text describing the message. +-- @param #number MessageDuration is the duration in seconds that the Message should be displayed. +-- @param #string MessageCategory is the category of the message (the title). +-- @param #number MessageInterval is the interval in seconds between the display of the @{Core.Message#MESSAGE} when the CLIENT is in the air. +-- @param #string MessageID is the identifier of the message when displayed with intervals. +function CLIENT:Message( Message, MessageDuration, MessageCategory, MessageInterval, MessageID ) + self:F( { Message, MessageDuration, MessageCategory, MessageInterval } ) + + if self.MessageSwitch == true then + if MessageCategory == nil then + MessageCategory = "Messages" + end + if MessageID ~= nil then + if self.Messages[MessageID] == nil then + self.Messages[MessageID] = {} + self.Messages[MessageID].MessageId = MessageID + self.Messages[MessageID].MessageTime = timer.getTime() + self.Messages[MessageID].MessageDuration = MessageDuration + if MessageInterval == nil then + self.Messages[MessageID].MessageInterval = 600 + else + self.Messages[MessageID].MessageInterval = MessageInterval + end + MESSAGE:New( Message, MessageDuration, MessageCategory ):ToClient( self ) + else + if self:GetClientGroupDCSUnit() and not self:GetClientGroupDCSUnit():inAir() then + if timer.getTime() - self.Messages[MessageID].MessageTime >= self.Messages[MessageID].MessageDuration + 10 then + MESSAGE:New( Message, MessageDuration , MessageCategory):ToClient( self ) + self.Messages[MessageID].MessageTime = timer.getTime() + end + else + if timer.getTime() - self.Messages[MessageID].MessageTime >= self.Messages[MessageID].MessageDuration + self.Messages[MessageID].MessageInterval then + MESSAGE:New( Message, MessageDuration, MessageCategory ):ToClient( self ) + self.Messages[MessageID].MessageTime = timer.getTime() + end + end + end + else + MESSAGE:New( Message, MessageDuration, MessageCategory ):ToClient( self ) + end + end +end +--- This module contains the STATIC class. +-- +-- 1) @{Wrapper.Static#STATIC} class, extends @{Wrapper.Positionable#POSITIONABLE} +-- =============================================================== +-- Statics are **Static Units** defined within the Mission Editor. +-- Note that Statics are almost the same as Units, but they don't have a controller. +-- The @{Wrapper.Static#STATIC} class is a wrapper class to handle the DCS Static objects: +-- +-- * Wraps the DCS Static objects. +-- * Support all DCS Static APIs. +-- * Enhance with Static specific APIs not in the DCS API set. +-- +-- 1.1) STATIC reference methods +-- ----------------------------- +-- For each DCS Static will have a STATIC wrapper object (instance) within the _@{DATABASE} object. +-- This is done at the beginning of the mission (when the mission starts). +-- +-- The STATIC class does not contain a :New() method, rather it provides :Find() methods to retrieve the object reference +-- using the Static Name. +-- +-- Another thing to know is that STATIC objects do not "contain" the DCS Static object. +-- The STATIc methods will reference the DCS Static object by name when it is needed during API execution. +-- If the DCS Static object does not exist or is nil, the STATIC methods will return nil and log an exception in the DCS.log file. +-- +-- The STATIc class provides the following functions to retrieve quickly the relevant STATIC instance: +-- +-- * @{#STATIC.FindByName}(): Find a STATIC instance from the _DATABASE object using a DCS Static name. +-- +-- IMPORTANT: ONE SHOULD NEVER SANATIZE these STATIC OBJECT REFERENCES! (make the STATIC object references nil). +-- +-- @module Static +-- @author FlightControl + + + + + + +--- The STATIC class +-- @type STATIC +-- @extends Wrapper.Positionable#POSITIONABLE +STATIC = { + ClassName = "STATIC", +} + + +--- Finds a STATIC from the _DATABASE using the relevant Static Name. +-- As an optional parameter, a briefing text can be given also. +-- @param #STATIC self +-- @param #string StaticName Name of the DCS **Static** as defined within the Mission Editor. +-- @return #STATIC +function STATIC:FindByName( StaticName ) + local StaticFound = _DATABASE:FindStatic( StaticName ) + + self.StaticName = StaticName + + if StaticFound then + StaticFound:F( { StaticName } ) + + return StaticFound + end + + error( "STATIC not found for: " .. StaticName ) +end + +function STATIC:Register( StaticName ) + local self = BASE:Inherit( self, POSITIONABLE:New( StaticName ) ) + self.StaticName = StaticName + return self +end + + +function STATIC:GetDCSObject() + local DCSStatic = StaticObject.getByName( self.StaticName ) + + if DCSStatic then + return DCSStatic + end + + return nil +end +--- This module contains the AIRBASE classes. +-- +-- === +-- +-- 1) @{Wrapper.Airbase#AIRBASE} class, extends @{Wrapper.Positionable#POSITIONABLE} +-- ================================================================= +-- The @{AIRBASE} class is a wrapper class to handle the DCS Airbase objects: +-- +-- * Support all DCS Airbase APIs. +-- * Enhance with Airbase specific APIs not in the DCS Airbase API set. +-- +-- +-- 1.1) AIRBASE reference methods +-- ------------------------------ +-- For each DCS Airbase object alive within a running mission, a AIRBASE wrapper object (instance) will be created within the _@{DATABASE} object. +-- This is done at the beginning of the mission (when the mission starts). +-- +-- The AIRBASE class **does not contain a :New()** method, rather it provides **:Find()** methods to retrieve the object reference +-- using the DCS Airbase or the DCS AirbaseName. +-- +-- Another thing to know is that AIRBASE objects do not "contain" the DCS Airbase object. +-- The AIRBASE methods will reference the DCS Airbase object by name when it is needed during API execution. +-- If the DCS Airbase object does not exist or is nil, the AIRBASE methods will return nil and log an exception in the DCS.log file. +-- +-- The AIRBASE class provides the following functions to retrieve quickly the relevant AIRBASE instance: +-- +-- * @{#AIRBASE.Find}(): Find a AIRBASE instance from the _DATABASE object using a DCS Airbase object. +-- * @{#AIRBASE.FindByName}(): Find a AIRBASE instance from the _DATABASE object using a DCS Airbase name. +-- +-- IMPORTANT: ONE SHOULD NEVER SANATIZE these AIRBASE OBJECT REFERENCES! (make the AIRBASE object references nil). +-- +-- 1.2) DCS AIRBASE APIs +-- --------------------- +-- The DCS Airbase APIs are used extensively within MOOSE. The AIRBASE class has for each DCS Airbase API a corresponding method. +-- To be able to distinguish easily in your code the difference between a AIRBASE API call and a DCS Airbase API call, +-- the first letter of the method is also capitalized. So, by example, the DCS Airbase method @{Dcs.DCSWrapper.Airbase#Airbase.getName}() +-- is implemented in the AIRBASE class as @{#AIRBASE.GetName}(). +-- +-- More functions will be added +-- ---------------------------- +-- During the MOOSE development, more functions will be added. +-- +-- @module Airbase +-- @author FlightControl + + + + + +--- The AIRBASE class +-- @type AIRBASE +-- @extends Wrapper.Positionable#POSITIONABLE +AIRBASE = { + ClassName="AIRBASE", + CategoryName = { + [Airbase.Category.AIRDROME] = "Airdrome", + [Airbase.Category.HELIPAD] = "Helipad", + [Airbase.Category.SHIP] = "Ship", + }, + } + +-- Registration. + +--- Create a new AIRBASE from DCSAirbase. +-- @param #AIRBASE self +-- @param #string AirbaseName The name of the airbase. +-- @return Wrapper.Airbase#AIRBASE +function AIRBASE:Register( AirbaseName ) + + local self = BASE:Inherit( self, POSITIONABLE:New( AirbaseName ) ) + self.AirbaseName = AirbaseName + return self +end + +-- Reference methods. + +--- Finds a AIRBASE from the _DATABASE using a DCSAirbase object. +-- @param #AIRBASE self +-- @param Dcs.DCSWrapper.Airbase#Airbase DCSAirbase An existing DCS Airbase object reference. +-- @return Wrapper.Airbase#AIRBASE self +function AIRBASE:Find( DCSAirbase ) + + local AirbaseName = DCSAirbase:getName() + local AirbaseFound = _DATABASE:FindAirbase( AirbaseName ) + return AirbaseFound +end + +--- Find a AIRBASE in the _DATABASE using the name of an existing DCS Airbase. +-- @param #AIRBASE self +-- @param #string AirbaseName The Airbase Name. +-- @return Wrapper.Airbase#AIRBASE self +function AIRBASE:FindByName( AirbaseName ) + + local AirbaseFound = _DATABASE:FindAirbase( AirbaseName ) + return AirbaseFound +end + +function AIRBASE:GetDCSObject() + local DCSAirbase = Airbase.getByName( self.AirbaseName ) + + if DCSAirbase then + return DCSAirbase + end + + return nil +end ---- Declare the event dispatcher based on the EVENT class -_EVENTDISPATCHER = EVENT:New() -- Event#EVENT ---- Declare the main database object, which is used internally by the MOOSE classes. -_DATABASE = DATABASE:New() -- Database#DATABASE --- Scoring system for MOOSE. -- This scoring class calculates the hits and kills that players make within a simulation session. @@ -14981,7 +16592,7 @@ _DATABASE = DATABASE:New() -- Database#DATABASE --- The Scoring class -- @type SCORING -- @field Players A collection of the current players that have joined the game. --- @extends Base#BASE +-- @extends Core.Base#BASE SCORING = { ClassName = "SCORING", ClassID = 0, @@ -15071,7 +16682,7 @@ end --- Track DEAD or CRASH events for the scoring. -- @param #SCORING self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event function SCORING:_EventOnDeadOrCrash( Event ) self:F( { Event } ) @@ -15236,8 +16847,8 @@ end --- Registers Scores the players completing a Mission Task. -- @param #SCORING self --- @param Mission#MISSION Mission --- @param Unit#UNIT PlayerUnit +-- @param Tasking.Mission#MISSION Mission +-- @param Wrapper.Unit#UNIT PlayerUnit -- @param #string Text -- @param #number Score function SCORING:_AddMissionTaskScore( Mission, PlayerUnit, Text, Score ) @@ -15246,18 +16857,20 @@ function SCORING:_AddMissionTaskScore( Mission, PlayerUnit, Text, Score ) local MissionName = Mission:GetName() self:F( { Mission:GetName(), PlayerUnit.UnitName, PlayerName, Text, Score } ) + + local PlayerData = self.Players[PlayerName] - if not self.Players[PlayerName].Mission[MissionName] then - self.Players[PlayerName].Mission[MissionName] = {} - self.Players[PlayerName].Mission[MissionName].ScoreTask = 0 - self.Players[PlayerName].Mission[MissionName].ScoreMission = 0 + if not PlayerData.Mission[MissionName] then + PlayerData.Mission[MissionName] = {} + PlayerData.Mission[MissionName].ScoreTask = 0 + PlayerData.Mission[MissionName].ScoreMission = 0 end self:T( PlayerName ) - self:T( self.Players[PlayerName].Mission[MissionName] ) + self:T( PlayerData.Mission[MissionName] ) - self.Players[PlayerName].Score = self.Players[PlayerName].Score + Score - self.Players[PlayerName].Mission[MissionName].ScoreTask = self.Players[PlayerName].Mission[MissionName].ScoreTask + Score + PlayerData.Score = self.Players[PlayerName].Score + Score + PlayerData.Mission[MissionName].ScoreTask = self.Players[PlayerName].Mission[MissionName].ScoreTask + Score MESSAGE:New( "Player '" .. PlayerName .. "' has " .. Text .. " in Mission '" .. MissionName .. "'. " .. Score .. " task score!", @@ -15269,18 +16882,20 @@ end --- Registers Mission Scores for possible multiple players that contributed in the Mission. -- @param #SCORING self --- @param Mission#MISSION Mission --- @param Unit#UNIT PlayerUnit +-- @param Tasking.Mission#MISSION Mission +-- @param Wrapper.Unit#UNIT PlayerUnit -- @param #string Text -- @param #number Score function SCORING:_AddMissionScore( Mission, Text, Score ) local MissionName = Mission:GetName() - self:F( { Mission, Text, Score } ) + self:E( { Mission, Text, Score } ) + self:E( self.Players ) for PlayerName, PlayerData in pairs( self.Players ) do + self:E( PlayerData ) if PlayerData.Mission[MissionName] then PlayerData.Score = PlayerData.Score + Score @@ -15297,7 +16912,7 @@ end --- Handles the OnHit event for the scoring. -- @param #SCORING self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event function SCORING:_EventOnHit( Event ) self:F( { Event } ) @@ -15790,4776 +17405,6 @@ function SCORING:CloseCSV() end end ---- This module contains the CARGO classes. --- --- === --- --- 1) @{Cargo#CARGO_BASE} class, extends @{Base#BASE} --- ================================================== --- The @{#CARGO_BASE} class defines the core functions that defines a cargo object within MOOSE. --- A cargo is a logical object defined within a @{Mission}, that is available for transport, and has a life status within a simulation. --- --- Cargo can be of various forms: --- --- * CARGO_UNIT, represented by a @{Unit} in a @{Group}: Cargo can be represented by a Unit in a Group. Destruction of the Unit will mean that the cargo is lost. --- * CARGO_STATIC, represented by a @{Static}: Cargo can be represented by a Static. Destruction of the Static will mean that the cargo is lost. --- * CARGO_PACKAGE, contained in a @{Unit} of a @{Group}: Cargo can be contained within a Unit of a Group. The cargo can be **delivered** by the @{Unit}. If the Unit is destroyed, the cargo will be destroyed also. --- * CARGO_PACKAGE, Contained in a @{Static}: Cargo can be contained within a Static. The cargo can be **collected** from the @Static. If the @{Static} is destroyed, the cargo will be destroyed. --- * CARGO_SLINGLOAD, represented by a @{Cargo} that is transportable: Cargo can be represented by a Cargo object that is transportable. Destruction of the Cargo will mean that the cargo is lost. --- --- @module Cargo - - - -CARGOS = {} - -do -- CARGO - - --- @type CARGO - -- @extends Base#BASE - -- @field #string Type A string defining the type of the cargo. eg. Engineers, Equipment, Screwdrivers. - -- @field #string Name A string defining the name of the cargo. The name is the unique identifier of the cargo. - -- @field #number Weight A number defining the weight of the cargo. The weight is expressed in kg. - -- @field #number ReportRadius (optional) A number defining the radius in meters when the cargo is signalling or reporting to a Carrier. - -- @field #number NearRadius (optional) A number defining the radius in meters when the cargo is near to a Carrier, so that it can be loaded. - -- @field Controllable#CONTROLLABLE CargoObject The alive DCS object representing the cargo. This value can be nil, meaning, that the cargo is not represented anywhere... - -- @field Positionable#POSITIONABLE CargoCarrier The alive DCS object carrying the cargo. This value can be nil, meaning, that the cargo is not contained anywhere... - -- @field #boolean Slingloadable This flag defines if the cargo can be slingloaded. - -- @field #boolean Moveable This flag defines if the cargo is moveable. - -- @field #boolean Representable This flag defines if the cargo can be represented by a DCS Unit. - -- @field #boolean Containable This flag defines if the cargo can be contained within a DCS Unit. - CARGO = { - ClassName = "CARGO", - Type = nil, - Name = nil, - Weight = nil, - CargoObject = nil, - CargoCarrier = nil, - Representable = false, - Slingloadable = false, - Moveable = false, - Containable = false, - } - ---- @type CARGO.CargoObjects --- @map < #string, Positionable#POSITIONABLE > The alive POSITIONABLE objects representing the the cargo. - - ---- CARGO Constructor. --- @param #CARGO self --- @param Mission#MISSION Mission --- @param #string Type --- @param #string Name --- @param #number Weight --- @param #number ReportRadius (optional) --- @param #number NearRadius (optional) --- @return #CARGO -function CARGO:New( Mission, Type, Name, Weight, ReportRadius, NearRadius ) - local self = BASE:Inherit( self, BASE:New() ) -- #CARGO - self:F( { Type, Name, Weight, ReportRadius, NearRadius } ) - - - self.Type = Type - self.Name = Name - self.Weight = Weight - self.ReportRadius = ReportRadius - self.NearRadius = NearRadius - self.CargoObject = nil - self.CargoCarrier = nil - self.Representable = false - self.Slingloadable = false - self.Moveable = false - self.Containable = false - - - self.CargoScheduler = SCHEDULER:New() - - CARGOS[self.Name] = self - - return self -end - - ---- Template method to spawn a new representation of the CARGO in the simulator. --- @param #CARGO self --- @return #CARGO -function CARGO:Spawn( PointVec2 ) - self:F() - -end - ---- Load Cargo to a Carrier. --- @param #CARGO self --- @param Unit#UNIT CargoCarrier -function CARGO:Load( CargoCarrier ) - self:F() - - self:_NextEvent( self.FsmP.Load, CargoCarrier ) -end - ---- UnLoad Cargo from a Carrier with a UnLoadDistance and an Angle. --- @param #CARGO self --- @param #number UnLoadDistance --- @param #number Angle -function CARGO:UnLoad( CargoCarrier ) - self:F() - - self:_NextEvent( self.FsmP.Board, CargoCarrier ) -end - ---- Board Cargo to a Carrier with a defined Speed. --- @param #CARGO self --- @param Unit#UNIT CargoCarrier -function CARGO:Board( CargoCarrier ) - self:F() - - self:_NextEvent( self.FsmP.Board, CargoCarrier ) -end - ---- UnLoad Cargo from a Carrier. --- @param #CARGO self -function CARGO:UnLoad() - self:F() - - self:_NextEvent( self.FsmP.UnLoad ) -end - ---- Check if CargoCarrier is near the Cargo to be Loaded. --- @param #CARGO self --- @param Point#POINT_VEC2 PointVec2 --- @return #boolean -function CARGO:IsNear( PointVec2 ) - self:F() - - local Distance = PointVec2:DistanceFromPointVec2( self.CargoObject:GetPointVec2() ) - self:T( Distance ) - - if Distance <= self.NearRadius then - return true - else - return false - end -end - - ---- On Loaded callback function. -function CARGO:OnLoaded( CallBackFunction, ... ) - self:F() - - self.OnLoadedCallBack = CallBackFunction - self.OnLoadedParameters = arg - -end - ---- On UnLoaded callback function. -function CARGO:OnUnLoaded( CallBackFunction, ... ) - self:F() - - self.OnUnLoadedCallBack = CallBackFunction - self.OnUnLoadedParameters = arg -end - ---- @param #CARGO self -function CARGO:_NextEvent( NextEvent, ... ) - self:F( self.Name ) - SCHEDULER:New( self.FsmP, NextEvent, arg, 1 ) -- This schedules the next event, but only if scheduling is activated. -end - ---- @param #CARGO self -function CARGO:_Next( NextEvent, ... ) - self:F( self.Name ) - self.FsmP.NextEvent( self, unpack(arg) ) -- This calls the next event... -end - -end - -do -- CARGO_REPRESENTABLE - - --- @type CARGO_REPRESENTABLE - -- @extends #CARGO - CARGO_REPRESENTABLE = { - ClassName = "CARGO_REPRESENTABLE" - } - ---- CARGO_REPRESENTABLE Constructor. --- @param #CARGO_REPRESENTABLE self --- @param Mission#MISSION Mission --- @param Controllable#Controllable CargoObject --- @param #string Type --- @param #string Name --- @param #number Weight --- @param #number ReportRadius (optional) --- @param #number NearRadius (optional) --- @return #CARGO_REPRESENTABLE -function CARGO_REPRESENTABLE:New( Mission, CargoObject, Type, Name, Weight, ReportRadius, NearRadius ) - local self = BASE:Inherit( self, CARGO:New( Mission, Type, Name, Weight, ReportRadius, NearRadius ) ) -- #CARGO - self:F( { Type, Name, Weight, ReportRadius, NearRadius } ) - - - - - return self -end - - - -end - -do -- CARGO_UNIT - - --- @type CARGO_UNIT - -- @extends #CARGO_REPRESENTABLE - CARGO_UNIT = { - ClassName = "CARGO_UNIT" - } - ---- CARGO_UNIT Constructor. --- @param #CARGO_UNIT self --- @param Mission#MISSION Mission --- @param Unit#UNIT CargoUnit --- @param #string Type --- @param #string Name --- @param #number Weight --- @param #number ReportRadius (optional) --- @param #number NearRadius (optional) --- @return #CARGO_UNIT -function CARGO_UNIT:New( Mission, CargoUnit, Type, Name, Weight, ReportRadius, NearRadius ) - local self = BASE:Inherit( self, CARGO_REPRESENTABLE:New( Mission, CargoUnit, Type, Name, Weight, ReportRadius, NearRadius ) ) -- #CARGO - self:F( { Type, Name, Weight, ReportRadius, NearRadius } ) - - self:T( CargoUnit ) - self.CargoObject = CargoUnit - - self.FsmP = STATEMACHINE_PROCESS:New( self, { - initial = 'UnLoaded', - events = { - { name = 'Board', from = 'UnLoaded', to = 'Boarding' }, - { name = 'Load', from = 'Boarding', to = 'Loaded' }, - { name = 'UnLoad', from = 'Loaded', to = 'UnBoarding' }, - { name = 'UnBoard', from = 'UnBoarding', to = 'UnLoaded' }, - { name = 'Load', from = 'UnLoaded', to = 'Loaded' }, - }, - callbacks = { - onafterBoard = self.EventBoard, - onafterLoad = self.EventLoad, - onafterUnBoard = self.EventUnBoard, - onafterUnLoad = self.EventUnLoad, - onenterBoarding = self.EnterStateBoarding, - onleaveBoarding = self.LeaveStateBoarding, - onenterLoaded = self.EnterStateLoaded, - onenterUnBoarding = self.EnterStateUnBoarding, - onleaveUnBoarding = self.LeaveStateUnBoarding, - onenterUnLoaded = self.EnterStateUnLoaded, - }, - } ) - - self:T( self.ClassName ) - - return self -end - ---- Enter UnBoarding State. --- @param #CARGO_UNIT self --- @param StateMachine#STATEMACHINE_PROCESS FsmP --- @param #string Event --- @param #string From --- @param #string To --- @param Point#POINT_VEC2 ToPointVec2 -function CARGO_UNIT:EnterStateUnBoarding( FsmP, Event, From, To, ToPointVec2 ) - self:F() - - local Angle = 180 - local Speed = 10 - local DeployDistance = 5 - local RouteDistance = 60 - - if From == "Loaded" then - - local CargoCarrierPointVec2 = self.CargoCarrier:GetPointVec2() - local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - local CargoDeployPointVec2 = CargoCarrierPointVec2:Translate( DeployDistance, CargoDeployHeading ) - local CargoRoutePointVec2 = CargoCarrierPointVec2:Translate( RouteDistance, CargoDeployHeading ) - - if not ToPointVec2 then - ToPointVec2 = CargoRoutePointVec2 - end - - local FromPointVec2 = CargoCarrierPointVec2 - - -- Respawn the group... - if self.CargoObject then - self.CargoObject:ReSpawn( CargoDeployPointVec2:GetVec3(), CargoDeployHeading ) - self.CargoCarrier = nil - - local Points = {} - Points[#Points+1] = FromPointVec2:RoutePointGround( Speed ) - Points[#Points+1] = ToPointVec2:RoutePointGround( Speed ) - - local TaskRoute = self.CargoObject:TaskRoute( Points ) - self.CargoObject:SetTask( TaskRoute, 1 ) - - self:_NextEvent( FsmP.UnBoard, ToPointVec2 ) - end - end - -end - ---- Leave UnBoarding State. --- @param #CARGO_UNIT self --- @param StateMachine#STATEMACHINE_PROCESS FsmP --- @param #string Event --- @param #string From --- @param #string To --- @param Point#POINT_VEC2 ToPointVec2 -function CARGO_UNIT:LeaveStateUnBoarding( FsmP, Event, From, To, ToPointVec2 ) - self:F() - - local Angle = 180 - local Speed = 10 - local Distance = 5 - - if From == "UnBoarding" then - if self:IsNear( ToPointVec2 ) then - return true - else - self:_NextEvent( FsmP.UnBoard, ToPointVec2 ) - end - return false - end - -end - ---- Enter UnLoaded State. --- @param #CARGO_UNIT self --- @param StateMachine#STATEMACHINE_PROCESS FsmP --- @param #string Event --- @param #string From --- @param #string To -function CARGO_UNIT:EnterStateUnLoaded( FsmP, Event, From, To, ToPointVec2 ) - self:F() - - local Angle = 180 - local Speed = 10 - local Distance = 5 - - if From == "Loaded" then - local StartPointVec2 = self.CargoCarrier:GetPointVec2() - local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - local CargoDeployPointVec2 = StartPointVec2:Translate( Distance, CargoDeployHeading ) - - -- Respawn the group... - if self.CargoObject then - self.CargoObject:ReSpawn( ToPointVec2:GetVec3(), 0 ) - self.CargoCarrier = nil - end - - end - - if self.OnUnLoadedCallBack then - self.OnUnLoadedCallBack( self, unpack( self.OnUnLoadedParameters ) ) - self.OnUnLoadedCallBack = nil - end - -end - - - ---- Enter Boarding State. --- @param #CARGO_UNIT self --- @param StateMachine#STATEMACHINE_PROCESS FsmP --- @param #string Event --- @param #string From --- @param #string To --- @param Unit#UNIT CargoCarrier -function CARGO_UNIT:EnterStateBoarding( FsmP, Event, From, To, CargoCarrier ) - self:F() - - local Speed = 10 - local Angle = 180 - local Distance = 5 - - if From == "UnLoaded" then - local CargoCarrierPointVec2 = CargoCarrier:GetPointVec2() - local CargoCarrierHeading = CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - local CargoDeployPointVec2 = CargoCarrierPointVec2:Translate( Distance, CargoDeployHeading ) - - local Points = {} - - local PointStartVec2 = self.CargoObject:GetPointVec2() - - Points[#Points+1] = PointStartVec2:RoutePointGround( Speed ) - Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) - - local TaskRoute = self.CargoObject:TaskRoute( Points ) - self.CargoObject:SetTask( TaskRoute, 2 ) - end -end - ---- Leave Boarding State. --- @param #CARGO_UNIT self --- @param StateMachine#STATEMACHINE_PROCESS FsmP --- @param #string Event --- @param #string From --- @param #string To --- @param Unit#UNIT CargoCarrier -function CARGO_UNIT:LeaveStateBoarding( FsmP, Event, From, To, CargoCarrier ) - self:F() - - if self:IsNear( CargoCarrier:GetPointVec2() ) then - return true - else - self:_NextEvent( FsmP.Load, CargoCarrier ) - end - return false -end - ---- Loaded State. --- @param #CARGO_UNIT self --- @param StateMachine#STATEMACHINE_PROCESS FsmP --- @param #string Event --- @param #string From --- @param #string To --- @param Unit#UNIT CargoCarrier -function CARGO_UNIT:EnterStateLoaded( FsmP, Event, From, To, CargoCarrier ) - self:F() - - self.CargoCarrier = CargoCarrier - - -- Only destroy the CargoObject is if there is a CargoObject (packages don't have CargoObjects). - if self.CargoObject then - self.CargoObject:Destroy() - end - - if self.OnLoadedCallBack then - self.OnLoadedCallBack( self, unpack( self.OnLoadedParameters ) ) - self.OnLoadedCallBack = nil - end - -end - - ---- Board Event. --- @param #CARGO_UNIT self --- @param StateMachine#STATEMACHINE_PROCESS FsmP --- @param #string Event --- @param #string From --- @param #string To -function CARGO_UNIT:EventBoard( FsmP, Event, From, To, CargoCarrier ) - self:F() - - self.CargoInAir = self.CargoObject:InAir() - - self:T( self.CargoInAir ) - - -- Only move the group to the carrier when the cargo is not in the air - -- (eg. cargo can be on a oil derrick, moving the cargo on the oil derrick will drop the cargo on the sea). - if not self.CargoInAir then - self:_NextEvent( FsmP.Load, CargoCarrier ) - end - - -end - ---- UnBoard Event. --- @param #CARGO_UNIT self --- @param StateMachine#STATEMACHINE_PROCESS FsmP --- @param #string Event --- @param #string From --- @param #string To -function CARGO_UNIT:EventUnBoard( FsmP, Event, From, To ) - self:F() - - self.CargoInAir = self.CargoObject:InAir() - - self:T( self.CargoInAir ) - - -- Only unboard the cargo when the carrier is not in the air. - -- (eg. cargo can be on a oil derrick, moving the cargo on the oil derrick will drop the cargo on the sea). - if not self.CargoInAir then - - end - - self:_NextEvent( FsmP.UnLoad ) - -end - ---- Load Event. --- @param #CARGO_UNIT self --- @param StateMachine#STATEMACHINE_PROCESS FsmP --- @param #string Event --- @param #string From --- @param #string To --- @param Unit#UNIT CargoCarrier -function CARGO_UNIT:EventLoad( FsmP, Event, From, To, CargoCarrier ) - self:F() - - self:T( self.ClassName ) - -end - ---- UnLoad Event. --- @param #CARGO_UNIT self --- @param StateMachine#STATEMACHINE_PROCESS FsmP --- @param #string Event --- @param #string From --- @param #string To -function CARGO_UNIT:EventUnLoad( FsmP, Event, From, To ) - self:F() - -end - -end - -do -- CARGO_PACKAGE - - --- @type CARGO_PACKAGE - -- @extends #CARGO_REPRESENTABLE - CARGO_PACKAGE = { - ClassName = "CARGO_PACKAGE" - } - ---- CARGO_PACKAGE Constructor. --- @param #CARGO_PACKAGE self --- @param Mission#MISSION Mission --- @param Unit#UNIT CargoCarrier The UNIT carrying the package. --- @param #string Type --- @param #string Name --- @param #number Weight --- @param #number ReportRadius (optional) --- @param #number NearRadius (optional) --- @return #CARGO_PACKAGE -function CARGO_PACKAGE:New( Mission, CargoCarrier, Type, Name, Weight, ReportRadius, NearRadius ) - local self = BASE:Inherit( self, CARGO_REPRESENTABLE:New( Mission, CargoCarrier, Type, Name, Weight, ReportRadius, NearRadius ) ) -- #CARGO - self:F( { Type, Name, Weight, ReportRadius, NearRadius } ) - - self:T( CargoCarrier ) - self.CargoCarrier = CargoCarrier - - self.FsmP = STATEMACHINE_PROCESS:New( self, { - initial = 'UnLoaded', - events = { - { name = 'Board', from = 'UnLoaded', to = 'Boarding' }, - { name = 'Boarded', from = 'Boarding', to = 'Boarding' }, - { name = 'Load', from = 'Boarding', to = 'Loaded' }, - { name = 'Load', from = 'UnLoaded', to = 'Loaded' }, - { name = 'UnBoard', from = 'Loaded', to = 'UnBoarding' }, - { name = 'UnBoarded', from = 'UnBoarding', to = 'UnBoarding' }, - { name = 'UnLoad', from = 'UnBoarding', to = 'UnLoaded' }, - { name = 'UnLoad', from = 'Loaded', to = 'UnLoaded' }, - }, - callbacks = { - onBoard = self.OnBoard, - onBoarded = self.OnBoarded, - onLoad = self.OnLoad, - onUnBoard = self.OnUnBoard, - onUnBoarded = self.OnUnBoarded, - onUnLoad = self.OnUnLoad, - onLoaded = self.OnLoaded, - onUnLoaded = self.OnUnLoaded, - }, - } ) - - return self -end - ---- Board Event. --- @param #CARGO_PACKAGE self --- @param StateMachine#STATEMACHINE_PROCESS FsmP --- @param #string Event --- @param #string From --- @param #string To --- @param Unit#UNIT CargoCarrier --- @param #number Speed --- @param #number BoardDistance --- @param #number Angle -function CARGO_PACKAGE:OnBoard( FsmP, Event, From, To, CargoCarrier, Speed, BoardDistance, LoadDistance, Angle ) - self:F() - - self.CargoInAir = self.CargoCarrier:InAir() - - self:T( self.CargoInAir ) - - -- Only move the CargoCarrier to the New CargoCarrier when the New CargoCarrier is not in the air. - if not self.CargoInAir then - - local Points = {} - - local StartPointVec2 = self.CargoCarrier:GetPointVec2() - local CargoCarrierHeading = CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - self:T( { CargoCarrierHeading, CargoDeployHeading } ) - local CargoDeployPointVec2 = CargoCarrier:GetPointVec2():Translate( BoardDistance, CargoDeployHeading ) - - Points[#Points+1] = StartPointVec2:RoutePointGround( Speed ) - Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) - - local TaskRoute = self.CargoCarrier:TaskRoute( Points ) - self.CargoCarrier:SetTask( TaskRoute, 1 ) - end - - self:_NextEvent( FsmP.Boarded, CargoCarrier, Speed, BoardDistance, LoadDistance, Angle ) - -end - ---- Check if CargoCarrier is near the Cargo to be Loaded. --- @param #CARGO_PACKAGE self --- @param Unit#UNIT CargoCarrier --- @return #boolean -function CARGO_PACKAGE:IsNear( CargoCarrier ) - self:F() - - local CargoCarrierPoint = CargoCarrier:GetPointVec2() - - local Distance = CargoCarrierPoint:DistanceFromPointVec2( self.CargoCarrier:GetPointVec2() ) - self:T( Distance ) - - if Distance <= self.NearRadius then - return true - else - return false - end -end - ---- Boarded Event. --- @param #CARGO_PACKAGE self --- @param StateMachine#STATEMACHINE_PROCESS FsmP --- @param #string Event --- @param #string From --- @param #string To --- @param Unit#UNIT CargoCarrier -function CARGO_PACKAGE:OnBoarded( FsmP, Event, From, To, CargoCarrier, Speed, BoardDistance, LoadDistance, Angle ) - self:F() - - if self:IsNear( CargoCarrier ) then - self:_NextEvent( FsmP.Load, CargoCarrier, Speed, LoadDistance, Angle ) - else - self:_NextEvent( FsmP.Boarded, CargoCarrier, Speed, BoardDistance, LoadDistance, Angle ) - end -end - ---- UnBoard Event. --- @param #CARGO_PACKAGE self --- @param StateMachine#STATEMACHINE_PROCESS FsmP --- @param #string Event --- @param #string From --- @param #string To --- @param #number Speed --- @param #number UnLoadDistance --- @param #number UnBoardDistance --- @param #number Radius --- @param #number Angle -function CARGO_PACKAGE:OnUnBoard( FsmP, Event, From, To, CargoCarrier, Speed, UnLoadDistance, UnBoardDistance, Radius, Angle ) - self:F() - - self.CargoInAir = self.CargoCarrier:InAir() - - self:T( self.CargoInAir ) - - -- Only unboard the cargo when the carrier is not in the air. - -- (eg. cargo can be on a oil derrick, moving the cargo on the oil derrick will drop the cargo on the sea). - if not self.CargoInAir then - - self:_Next( self.FsmP.UnLoad, UnLoadDistance, Angle ) - - local Points = {} - - local StartPointVec2 = CargoCarrier:GetPointVec2() - local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - self:T( { CargoCarrierHeading, CargoDeployHeading } ) - local CargoDeployPointVec2 = StartPointVec2:Translate( UnBoardDistance, CargoDeployHeading ) - - Points[#Points+1] = StartPointVec2:RoutePointGround( Speed ) - Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) - - local TaskRoute = CargoCarrier:TaskRoute( Points ) - CargoCarrier:SetTask( TaskRoute, 1 ) - end - - self:_NextEvent( FsmP.UnBoarded, CargoCarrier, Speed ) - -end - ---- UnBoarded Event. --- @param #CARGO_PACKAGE self --- @param StateMachine#STATEMACHINE_PROCESS FsmP --- @param #string Event --- @param #string From --- @param #string To --- @param Unit#UNIT CargoCarrier -function CARGO_PACKAGE:OnUnBoarded( FsmP, Event, From, To, CargoCarrier, Speed ) - self:F() - - if self:IsNear( CargoCarrier ) then - self:_NextEvent( FsmP.UnLoad, CargoCarrier, Speed ) - else - self:_NextEvent( FsmP.UnBoarded, CargoCarrier, Speed ) - end -end - ---- Load Event. --- @param #CARGO_PACKAGE self --- @param StateMachine#STATEMACHINE_PROCESS FsmP --- @param #string Event --- @param #string From --- @param #string To --- @param Unit#UNIT CargoCarrier --- @param #number Speed --- @param #number LoadDistance --- @param #number Angle -function CARGO_PACKAGE:OnLoad( FsmP, Event, From, To, CargoCarrier, Speed, LoadDistance, Angle ) - self:F() - - self.CargoCarrier = CargoCarrier - - local StartPointVec2 = self.CargoCarrier:GetPointVec2() - local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - local CargoDeployPointVec2 = StartPointVec2:Translate( LoadDistance, CargoDeployHeading ) - - local Points = {} - Points[#Points+1] = StartPointVec2:RoutePointGround( Speed ) - Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) - - local TaskRoute = self.CargoCarrier:TaskRoute( Points ) - self.CargoCarrier:SetTask( TaskRoute, 1 ) - -end - ---- UnLoad Event. --- @param #CARGO_PACKAGE self --- @param StateMachine#STATEMACHINE_PROCESS FsmP --- @param #string Event --- @param #string From --- @param #string To --- @param #number Distance --- @param #number Angle -function CARGO_PACKAGE:OnUnLoad( FsmP, Event, From, To, CargoCarrier, Speed, Distance, Angle ) - self:F() - - local StartPointVec2 = self.CargoCarrier:GetPointVec2() - local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - local CargoDeployPointVec2 = StartPointVec2:Translate( Distance, CargoDeployHeading ) - - self.CargoCarrier = CargoCarrier - - local Points = {} - Points[#Points+1] = StartPointVec2:RoutePointGround( Speed ) - Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) - - local TaskRoute = self.CargoCarrier:TaskRoute( Points ) - self.CargoCarrier:SetTask( TaskRoute, 1 ) - -end - - -end - - - -CARGO_SLINGLOAD = { - ClassName = "CARGO_SLINGLOAD" -} - - -function CARGO_SLINGLOAD:New( CargoType, CargoName, CargoWeight, CargoZone, CargoHostName, CargoCountryID ) - local self = BASE:Inherit( self, CARGO:New( CargoType, CargoName, CargoWeight ) ) - self:F( { CargoType, CargoName, CargoWeight, CargoZone, CargoHostName, CargoCountryID } ) - - self.CargoHostName = CargoHostName - - -- Cargo will be initialized around the CargoZone position. - self.CargoZone = CargoZone - - self.CargoCount = 0 - self.CargoStaticName = string.format( "%s#%03d", self.CargoName, self.CargoCount ) - - -- The country ID needs to be correctly set. - self.CargoCountryID = CargoCountryID - - CARGOS[self.CargoName] = self - - return self - -end - - -function CARGO_SLINGLOAD:IsLandingRequired() - self:F() - return false -end - - -function CARGO_SLINGLOAD:IsSlingLoad() - self:F() - return true -end - - -function CARGO_SLINGLOAD:Spawn( Client ) - self:F( { self, Client } ) - - local Zone = trigger.misc.getZone( self.CargoZone ) - - local ZonePos = {} - ZonePos.x = Zone.point.x + math.random( Zone.radius / 2 * -1, Zone.radius / 2 ) - ZonePos.y = Zone.point.z + math.random( Zone.radius / 2 * -1, Zone.radius / 2 ) - - self:T( "Cargo Location = " .. ZonePos.x .. ", " .. ZonePos.y ) - - --[[ - - - - - - - - -- This does not work in 1.5.2. - - - - - - - - CargoStatic = StaticObject.getByName( self.CargoName ) - - - - - - - - if CargoStatic then - - - - - - - - CargoStatic:destroy() - - - - - - - - end - - - - - - - - --]] - - CargoStatic = StaticObject.getByName( self.CargoStaticName ) - - if CargoStatic and CargoStatic:isExist() then - CargoStatic:destroy() - end - - -- I need to make every time a new cargo due to bugs in 1.5.2. - - self.CargoCount = self.CargoCount + 1 - self.CargoStaticName = string.format( "%s#%03d", self.CargoName, self.CargoCount ) - - local CargoTemplate = { - ["category"] = "Cargo", - ["shape_name"] = "ab-212_cargo", - ["type"] = "Cargo1", - ["x"] = ZonePos.x, - ["y"] = ZonePos.y, - ["mass"] = self.CargoWeight, - ["name"] = self.CargoStaticName, - ["canCargo"] = true, - ["heading"] = 0, - } - - coalition.addStaticObject( self.CargoCountryID, CargoTemplate ) - - -- end - - return self -end - - -function CARGO_SLINGLOAD:IsNear( Client, LandingZone ) - self:F() - - local Near = false - - return Near -end - - -function CARGO_SLINGLOAD:IsInLandingZone( Client, LandingZone ) - self:F() - - local Near = false - - local CargoStaticUnit = StaticObject.getByName( self.CargoName ) - if CargoStaticUnit then - if routines.IsStaticInZones( CargoStaticUnit, LandingZone ) then - Near = true - end - end - - return Near -end - - -function CARGO_SLINGLOAD:OnBoard( Client, LandingZone, OnBoardSide ) - self:F() - - local Valid = true - - - return Valid -end - - -function CARGO_SLINGLOAD:OnBoarded( Client, LandingZone ) - self:F() - - local OnBoarded = false - - local CargoStaticUnit = StaticObject.getByName( self.CargoName ) - if CargoStaticUnit then - if not routines.IsStaticInZones( CargoStaticUnit, LandingZone ) then - OnBoarded = true - end - end - - return OnBoarded -end - - -function CARGO_SLINGLOAD:UnLoad( Client, TargetZoneName ) - self:F() - - self:T( 'self.CargoName = ' .. self.CargoName ) - self:T( 'self.CargoGroupName = ' .. self.CargoGroupName ) - - self:StatusUnLoaded() - - return Cargo -end - -CARGO_ZONE = { - ClassName="CARGO_ZONE", - CargoZoneName = '', - CargoHostUnitName = '', - SIGNAL = { - TYPE = { - SMOKE = { ID = 1, TEXT = "smoke" }, - FLARE = { ID = 2, TEXT = "flare" } - }, - COLOR = { - GREEN = { ID = 1, TRIGGERCOLOR = trigger.smokeColor.Green, TEXT = "A green" }, - RED = { ID = 2, TRIGGERCOLOR = trigger.smokeColor.Red, TEXT = "A red" }, - WHITE = { ID = 3, TRIGGERCOLOR = trigger.smokeColor.White, TEXT = "A white" }, - ORANGE = { ID = 4, TRIGGERCOLOR = trigger.smokeColor.Orange, TEXT = "An orange" }, - BLUE = { ID = 5, TRIGGERCOLOR = trigger.smokeColor.Blue, TEXT = "A blue" }, - YELLOW = { ID = 6, TRIGGERCOLOR = trigger.flareColor.Yellow, TEXT = "A yellow" } - } - } -} - ---- Creates a new zone where cargo can be collected or deployed. --- The zone functionality is useful to smoke or indicate routes for cargo pickups or deployments. --- Provide the zone name as declared in the mission file into the CargoZoneName in the :New method. --- An optional parameter is the CargoHostName, which is a Group declared with Late Activation switched on in the mission file. --- The CargoHostName is the "host" of the cargo zone: --- --- * It will smoke the zone position when a client is approaching the zone. --- * Depending on the cargo type, it will assist in the delivery of the cargo by driving to and from the client. --- --- @param #CARGO_ZONE self --- @param #string CargoZoneName The name of the zone as declared within the mission editor. --- @param #string CargoHostName The name of the Group "hosting" the zone. The Group MUST NOT be a static, and must be a "mobile" unit. -function CARGO_ZONE:New( CargoZoneName, CargoHostName ) local self = BASE:Inherit( self, ZONE:New( CargoZoneName ) ) - self:F( { CargoZoneName, CargoHostName } ) - - self.CargoZoneName = CargoZoneName - self.SignalHeight = 2 - --self.CargoZone = trigger.misc.getZone( CargoZoneName ) - - - if CargoHostName then - self.CargoHostName = CargoHostName - end - - self:T( self.CargoZoneName ) - - return self -end - -function CARGO_ZONE:Spawn() - self:F( self.CargoHostName ) - - if self.CargoHostName then -- Only spawn a host in the zone when there is one given as a parameter in the New function. - if self.CargoHostSpawn then - local CargoHostGroup = self.CargoHostSpawn:GetGroupFromIndex() - if CargoHostGroup and CargoHostGroup:IsAlive() then - else - self.CargoHostSpawn:ReSpawn( 1 ) - end - else - self:T( "Initialize CargoHostSpawn" ) - self.CargoHostSpawn = SPAWN:New( self.CargoHostName ):Limit( 1, 1 ) - self.CargoHostSpawn:ReSpawn( 1 ) - end - end - - return self -end - -function CARGO_ZONE:GetHostUnit() - self:F( self ) - - if self.CargoHostName then - - -- A Host has been given, signal the host - local CargoHostGroup = self.CargoHostSpawn:GetGroupFromIndex() - local CargoHostUnit - if CargoHostGroup and CargoHostGroup:IsAlive() then - CargoHostUnit = CargoHostGroup:GetUnit(1) - else - CargoHostUnit = StaticObject.getByName( self.CargoHostName ) - end - - return CargoHostUnit - end - - return nil -end - -function CARGO_ZONE:ReportCargosToClient( Client, CargoType ) - self:F() - - local SignalUnit = self:GetHostUnit() - - if SignalUnit then - - local SignalUnitTypeName = SignalUnit:getTypeName() - - local HostMessage = "" - - local IsCargo = false - for CargoID, Cargo in pairs( CARGOS ) do - if Cargo.CargoType == Task.CargoType then - if Cargo:IsStatusNone() then - HostMessage = HostMessage .. " - " .. Cargo.CargoName .. " - " .. Cargo.CargoType .. " (" .. Cargo.Weight .. "kg)" .. "\n" - IsCargo = true - end - end - end - - if not IsCargo then - HostMessage = "No Cargo Available." - end - - Client:Message( HostMessage, 20, SignalUnitTypeName .. ": Reporting Cargo", 10 ) - end -end - - -function CARGO_ZONE:Signal() - self:F() - - local Signalled = false - - if self.SignalType then - - if self.CargoHostName then - - -- A Host has been given, signal the host - - local SignalUnit = self:GetHostUnit() - - if SignalUnit then - - self:T( 'Signalling Unit' ) - local SignalVehicleVec3 = SignalUnit:GetVec3() - SignalVehicleVec3.y = SignalVehicleVec3.y + 2 - - if self.SignalType.ID == CARGO_ZONE.SIGNAL.TYPE.SMOKE.ID then - - trigger.action.smoke( SignalVehicleVec3, self.SignalColor.TRIGGERCOLOR ) - Signalled = true - - elseif self.SignalType.ID == CARGO_ZONE.SIGNAL.TYPE.FLARE.ID then - - trigger.action.signalFlare( SignalVehicleVec3, self.SignalColor.TRIGGERCOLOR , 0 ) - Signalled = false - - end - end - - else - - local ZoneVec3 = self:GetPointVec3( self.SignalHeight ) -- Get the zone position + the landheight + 2 meters - - if self.SignalType.ID == CARGO_ZONE.SIGNAL.TYPE.SMOKE.ID then - - trigger.action.smoke( ZoneVec3, self.SignalColor.TRIGGERCOLOR ) - Signalled = true - - elseif self.SignalType.ID == CARGO_ZONE.SIGNAL.TYPE.FLARE.ID then - trigger.action.signalFlare( ZoneVec3, self.SignalColor.TRIGGERCOLOR, 0 ) - Signalled = false - - end - end - end - - return Signalled - -end - -function CARGO_ZONE:WhiteSmoke( SignalHeight ) - self:F() - - self.SignalType = CARGO_ZONE.SIGNAL.TYPE.SMOKE - self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.WHITE - - if SignalHeight then - self.SignalHeight = SignalHeight - end - - return self -end - -function CARGO_ZONE:BlueSmoke( SignalHeight ) - self:F() - - self.SignalType = CARGO_ZONE.SIGNAL.TYPE.SMOKE - self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.BLUE - - if SignalHeight then - self.SignalHeight = SignalHeight - end - - return self -end - -function CARGO_ZONE:RedSmoke( SignalHeight ) - self:F() - - self.SignalType = CARGO_ZONE.SIGNAL.TYPE.SMOKE - self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.RED - - if SignalHeight then - self.SignalHeight = SignalHeight - end - - return self -end - -function CARGO_ZONE:OrangeSmoke( SignalHeight ) - self:F() - - self.SignalType = CARGO_ZONE.SIGNAL.TYPE.SMOKE - self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.ORANGE - - if SignalHeight then - self.SignalHeight = SignalHeight - end - - return self -end - -function CARGO_ZONE:GreenSmoke( SignalHeight ) - self:F() - - self.SignalType = CARGO_ZONE.SIGNAL.TYPE.SMOKE - self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.GREEN - - if SignalHeight then - self.SignalHeight = SignalHeight - end - - return self -end - - -function CARGO_ZONE:WhiteFlare( SignalHeight ) - self:F() - - self.SignalType = CARGO_ZONE.SIGNAL.TYPE.FLARE - self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.WHITE - - if SignalHeight then - self.SignalHeight = SignalHeight - end - - return self -end - -function CARGO_ZONE:RedFlare( SignalHeight ) - self:F() - - self.SignalType = CARGO_ZONE.SIGNAL.TYPE.FLARE - self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.RED - - if SignalHeight then - self.SignalHeight = SignalHeight - end - - return self -end - -function CARGO_ZONE:GreenFlare( SignalHeight ) - self:F() - - self.SignalType = CARGO_ZONE.SIGNAL.TYPE.FLARE - self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.GREEN - - if SignalHeight then - self.SignalHeight = SignalHeight - end - - return self -end - -function CARGO_ZONE:YellowFlare( SignalHeight ) - self:F() - - self.SignalType = CARGO_ZONE.SIGNAL.TYPE.FLARE - self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.YELLOW - - if SignalHeight then - self.SignalHeight = SignalHeight - end - - return self -end - - -function CARGO_ZONE:GetCargoHostUnit() - self:F( self ) - - if self.CargoHostSpawn then - local CargoHostGroup = self.CargoHostSpawn:GetGroupFromIndex(1) - if CargoHostGroup and CargoHostGroup:IsAlive() then - local CargoHostUnit = CargoHostGroup:GetUnit(1) - if CargoHostUnit and CargoHostUnit:IsAlive() then - return CargoHostUnit - end - end - end - - return nil -end - -function CARGO_ZONE:GetCargoZoneName() - self:F() - - return self.CargoZoneName -end - - - - - - - - ---- This module contains the MESSAGE class. --- --- 1) @{Message#MESSAGE} class, extends @{Base#BASE} --- ================================================= --- Message System to display Messages to Clients, Coalitions or All. --- Messages are shown on the display panel for an amount of seconds, and will then disappear. --- Messages can contain a category which is indicating the category of the message. --- --- 1.1) MESSAGE construction methods --- --------------------------------- --- Messages are created with @{Message#MESSAGE.New}. Note that when the MESSAGE object is created, no message is sent yet. --- To send messages, you need to use the To functions. --- --- 1.2) Send messages with MESSAGE To methods --- ------------------------------------------ --- Messages are sent to: --- --- * Clients with @{Message#MESSAGE.ToClient}. --- * Coalitions with @{Message#MESSAGE.ToCoalition}. --- * All Players with @{Message#MESSAGE.ToAll}. --- --- @module Message --- @author FlightControl - ---- The MESSAGE class --- @type MESSAGE --- @extends Base#BASE -MESSAGE = { - ClassName = "MESSAGE", - MessageCategory = 0, - MessageID = 0, -} - - ---- Creates a new MESSAGE object. Note that these MESSAGE objects are not yet displayed on the display panel. You must use the functions @{ToClient} or @{ToCoalition} or @{ToAll} to send these Messages to the respective recipients. --- @param self --- @param #string MessageText is the text of the Message. --- @param #number MessageDuration is a number in seconds of how long the MESSAGE should be shown on the display panel. --- @param #string MessageCategory (optional) is a string expressing the "category" of the Message. The category will be shown as the first text in the message followed by a ": ". --- @return #MESSAGE --- @usage --- -- Create a series of new Messages. --- -- MessageAll is meant to be sent to all players, for 25 seconds, and is classified as "Score". --- -- MessageRED is meant to be sent to the RED players only, for 10 seconds, and is classified as "End of Mission", with ID "Win". --- -- MessageClient1 is meant to be sent to a Client, for 25 seconds, and is classified as "Score", with ID "Score". --- -- MessageClient1 is meant to be sent to a Client, for 25 seconds, and is classified as "Score", with ID "Score". --- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", 25, "End of Mission" ) --- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", 25, "Penalty" ) --- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", 25, "Score" ) --- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", 25, "Score") -function MESSAGE:New( MessageText, MessageDuration, MessageCategory ) - local self = BASE:Inherit( self, BASE:New() ) - self:F( { MessageText, MessageDuration, MessageCategory } ) - - -- When no MessageCategory is given, we don't show it as a title... - if MessageCategory and MessageCategory ~= "" then - self.MessageCategory = MessageCategory .. ": " - else - self.MessageCategory = "" - end - - self.MessageDuration = MessageDuration or 5 - self.MessageTime = timer.getTime() - self.MessageText = MessageText - - self.MessageSent = false - self.MessageGroup = false - self.MessageCoalition = false - - return self -end - ---- Sends a MESSAGE to a Client Group. Note that the Group needs to be defined within the ME with the skillset "Client" or "Player". --- @param #MESSAGE self --- @param Client#CLIENT Client is the Group of the Client. --- @return #MESSAGE --- @usage --- -- Send the 2 messages created with the @{New} method to the Client Group. --- -- Note that the Message of MessageClient2 is overwriting the Message of MessageClient1. --- ClientGroup = Group.getByName( "ClientGroup" ) --- --- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ):ToClient( ClientGroup ) --- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ):ToClient( ClientGroup ) --- or --- MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ):ToClient( ClientGroup ) --- MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ):ToClient( ClientGroup ) --- or --- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ) --- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ) --- MessageClient1:ToClient( ClientGroup ) --- MessageClient2:ToClient( ClientGroup ) -function MESSAGE:ToClient( Client ) - self:F( Client ) - - if Client and Client:GetClientGroupID() then - - local ClientGroupID = Client:GetClientGroupID() - self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration ) - trigger.action.outTextForGroup( ClientGroupID, self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration ) - end - - return self -end - ---- Sends a MESSAGE to a Group. --- @param #MESSAGE self --- @param Group#GROUP Group is the Group. --- @return #MESSAGE -function MESSAGE:ToGroup( Group ) - self:F( Group.GroupName ) - - if Group then - - self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration ) - trigger.action.outTextForGroup( Group:GetID(), self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration ) - end - - return self -end ---- Sends a MESSAGE to the Blue coalition. --- @param #MESSAGE self --- @return #MESSAGE --- @usage --- -- Send a message created with the @{New} method to the BLUE coalition. --- MessageBLUE = MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToBlue() --- or --- MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToBlue() --- or --- MessageBLUE = MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ) --- MessageBLUE:ToBlue() -function MESSAGE:ToBlue() - self:F() - - self:ToCoalition( coalition.side.BLUE ) - - return self -end - ---- Sends a MESSAGE to the Red Coalition. --- @param #MESSAGE self --- @return #MESSAGE --- @usage --- -- Send a message created with the @{New} method to the RED coalition. --- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToRed() --- or --- MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToRed() --- or --- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ) --- MessageRED:ToRed() -function MESSAGE:ToRed( ) - self:F() - - self:ToCoalition( coalition.side.RED ) - - return self -end - ---- Sends a MESSAGE to a Coalition. --- @param #MESSAGE self --- @param CoalitionSide needs to be filled out by the defined structure of the standard scripting engine @{coalition.side}. --- @return #MESSAGE --- @usage --- -- Send a message created with the @{New} method to the RED coalition. --- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToCoalition( coalition.side.RED ) --- or --- MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToCoalition( coalition.side.RED ) --- or --- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ) --- MessageRED:ToCoalition( coalition.side.RED ) -function MESSAGE:ToCoalition( CoalitionSide ) - self:F( CoalitionSide ) - - if CoalitionSide then - self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration ) - trigger.action.outTextForCoalition( CoalitionSide, self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration ) - end - - return self -end - ---- Sends a MESSAGE to all players. --- @param #MESSAGE self --- @return #MESSAGE --- @usage --- -- Send a message created to all players. --- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ):ToAll() --- or --- MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ):ToAll() --- or --- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ) --- MessageAll:ToAll() -function MESSAGE:ToAll() - self:F() - - self:ToCoalition( coalition.side.RED ) - self:ToCoalition( coalition.side.BLUE ) - - return self -end - - - ------ The MESSAGEQUEUE class ----- @type MESSAGEQUEUE ---MESSAGEQUEUE = { --- ClientGroups = {}, --- CoalitionSides = {} ---} --- ---function MESSAGEQUEUE:New( RefreshInterval ) --- local self = BASE:Inherit( self, BASE:New() ) --- self:F( { RefreshInterval } ) --- --- self.RefreshInterval = RefreshInterval --- --- --self.DisplayFunction = routines.scheduleFunction( self._DisplayMessages, { self }, 0, RefreshInterval ) --- self.DisplayFunction = SCHEDULER:New( self, self._DisplayMessages, {}, 0, RefreshInterval ) --- --- return self ---end --- ------ This function is called automatically by the MESSAGEQUEUE scheduler. ---function MESSAGEQUEUE:_DisplayMessages() --- --- -- First we display all messages that a coalition needs to receive... Also those who are not in a client (CA module clients...). --- for CoalitionSideID, CoalitionSideData in pairs( self.CoalitionSides ) do --- for MessageID, MessageData in pairs( CoalitionSideData.Messages ) do --- if MessageData.MessageSent == false then --- --trigger.action.outTextForCoalition( CoalitionSideID, MessageData.MessageCategory .. '\n' .. MessageData.MessageText:gsub("\n$",""):gsub("\n$",""), MessageData.MessageDuration ) --- MessageData.MessageSent = true --- end --- local MessageTimeLeft = ( MessageData.MessageTime + MessageData.MessageDuration ) - timer.getTime() --- if MessageTimeLeft <= 0 then --- MessageData = nil --- end --- end --- end --- --- -- Then we send the messages for each individual client, but also to be included are those Coalition messages for the Clients who belong to a coalition. --- -- Because the Client messages will overwrite the Coalition messages (for that Client). --- for ClientGroupName, ClientGroupData in pairs( self.ClientGroups ) do --- for MessageID, MessageData in pairs( ClientGroupData.Messages ) do --- if MessageData.MessageGroup == false then --- trigger.action.outTextForGroup( Group.getByName(ClientGroupName):getID(), MessageData.MessageCategory .. '\n' .. MessageData.MessageText:gsub("\n$",""):gsub("\n$",""), MessageData.MessageDuration ) --- MessageData.MessageGroup = true --- end --- local MessageTimeLeft = ( MessageData.MessageTime + MessageData.MessageDuration ) - timer.getTime() --- if MessageTimeLeft <= 0 then --- MessageData = nil --- end --- end --- --- -- Now check if the Client also has messages that belong to the Coalition of the Client... --- for CoalitionSideID, CoalitionSideData in pairs( self.CoalitionSides ) do --- for MessageID, MessageData in pairs( CoalitionSideData.Messages ) do --- local CoalitionGroup = Group.getByName( ClientGroupName ) --- if CoalitionGroup and CoalitionGroup:getCoalition() == CoalitionSideID then --- if MessageData.MessageCoalition == false then --- trigger.action.outTextForGroup( Group.getByName(ClientGroupName):getID(), MessageData.MessageCategory .. '\n' .. MessageData.MessageText:gsub("\n$",""):gsub("\n$",""), MessageData.MessageDuration ) --- MessageData.MessageCoalition = true --- end --- end --- local MessageTimeLeft = ( MessageData.MessageTime + MessageData.MessageDuration ) - timer.getTime() --- if MessageTimeLeft <= 0 then --- MessageData = nil --- end --- end --- end --- end --- --- return true ---end --- ------ The _MessageQueue object is created when the MESSAGE class module is loaded. -----_MessageQueue = MESSAGEQUEUE:New( 0.5 ) --- ---- Stages within a @{TASK} within a @{MISSION}. All of the STAGE functionality is considered internally administered and not to be used by any Mission designer. --- @module STAGE --- @author Flightcontrol - - - - - - - ---- The STAGE class --- @type -STAGE = { - ClassName = "STAGE", - MSG = { ID = "None", TIME = 10 }, - FREQUENCY = { NONE = 0, ONCE = 1, REPEAT = -1 }, - - Name = "NoStage", - StageType = '', - WaitTime = 1, - Frequency = 1, - MessageCount = 0, - MessageInterval = 15, - MessageShown = {}, - MessageShow = false, - MessageFlash = false -} - - -function STAGE:New() - local self = BASE:Inherit( self, BASE:New() ) - self:F() - return self -end - -function STAGE:Execute( Mission, Client, Task ) - - local Valid = true - - return Valid -end - -function STAGE:Executing( Mission, Client, Task ) - -end - -function STAGE:Validate( Mission, Client, Task ) - local Valid = true - - return Valid -end - - -STAGEBRIEF = { - ClassName = "BRIEF", - MSG = { ID = "Brief", TIME = 1 }, - Name = "Brief", - StageBriefingTime = 0, - StageBriefingDuration = 1 -} - -function STAGEBRIEF:New() - local self = BASE:Inherit( self, STAGE:New() ) - self:F() - self.StageType = 'CLIENT' - return self -end - ---- Execute --- @param #STAGEBRIEF self --- @param Mission#MISSION Mission --- @param Client#CLIENT Client --- @param Task#TASK Task --- @return #boolean -function STAGEBRIEF:Execute( Mission, Client, Task ) - local Valid = BASE:Inherited(self):Execute( Mission, Client, Task ) - self:F() - Client:ShowMissionBriefing( Mission.MissionBriefing ) - self.StageBriefingTime = timer.getTime() - return Valid -end - -function STAGEBRIEF:Validate( Mission, Client, Task ) - local Valid = STAGE:Validate( Mission, Client, Task ) - self:T() - - if timer.getTime() - self.StageBriefingTime <= self.StageBriefingDuration then - return 0 - else - self.StageBriefingTime = timer.getTime() - return 1 - end - -end - - -STAGESTART = { - ClassName = "START", - MSG = { ID = "Start", TIME = 1 }, - Name = "Start", - StageStartTime = 0, - StageStartDuration = 1 -} - -function STAGESTART:New() - local self = BASE:Inherit( self, STAGE:New() ) - self:F() - self.StageType = 'CLIENT' - return self -end - -function STAGESTART:Execute( Mission, Client, Task ) - self:F() - local Valid = BASE:Inherited(self):Execute( Mission, Client, Task ) - if Task.TaskBriefing then - Client:Message( Task.TaskBriefing, 30, "Command" ) - else - Client:Message( 'Task ' .. Task.TaskNumber .. '.', 30, "Command" ) - end - self.StageStartTime = timer.getTime() - return Valid -end - -function STAGESTART:Validate( Mission, Client, Task ) - self:F() - local Valid = STAGE:Validate( Mission, Client, Task ) - - if timer.getTime() - self.StageStartTime <= self.StageStartDuration then - return 0 - else - self.StageStartTime = timer.getTime() - return 1 - end - - return 1 - -end - -STAGE_CARGO_LOAD = { - ClassName = "STAGE_CARGO_LOAD" -} - -function STAGE_CARGO_LOAD:New() - local self = BASE:Inherit( self, STAGE:New() ) - self:F() - self.StageType = 'CLIENT' - return self -end - -function STAGE_CARGO_LOAD:Execute( Mission, Client, Task ) - self:F() - local Valid = BASE:Inherited(self):Execute( Mission, Client, Task ) - - for LoadCargoID, LoadCargo in pairs( Task.Cargos.LoadCargos ) do - LoadCargo:Load( Client ) - end - - if Mission.MissionReportFlash and Client:IsTransport() then - Client:ShowCargo() - end - - return Valid -end - -function STAGE_CARGO_LOAD:Validate( Mission, Client, Task ) - self:F() - local Valid = STAGE:Validate( Mission, Client, Task ) - - return 1 -end - - -STAGE_CARGO_INIT = { - ClassName = "STAGE_CARGO_INIT" -} - -function STAGE_CARGO_INIT:New() - local self = BASE:Inherit( self, STAGE:New() ) - self:F() - self.StageType = 'CLIENT' - return self -end - -function STAGE_CARGO_INIT:Execute( Mission, Client, Task ) - self:F() - local Valid = BASE:Inherited(self):Execute( Mission, Client, Task ) - - for InitLandingZoneID, InitLandingZone in pairs( Task.LandingZones.LandingZones ) do - self:T( InitLandingZone ) - InitLandingZone:Spawn() - end - - - self:T( Task.Cargos.InitCargos ) - for InitCargoID, InitCargoData in pairs( Task.Cargos.InitCargos ) do - self:T( { InitCargoData } ) - InitCargoData:Spawn( Client ) - end - - return Valid -end - - -function STAGE_CARGO_INIT:Validate( Mission, Client, Task ) - self:F() - local Valid = STAGE:Validate( Mission, Client, Task ) - - return 1 -end - - - -STAGEROUTE = { - ClassName = "STAGEROUTE", - MSG = { ID = "Route", TIME = 5 }, - Frequency = STAGE.FREQUENCY.REPEAT, - Name = "Route" -} - -function STAGEROUTE:New() - local self = BASE:Inherit( self, STAGE:New() ) - self:F() - self.StageType = 'CLIENT' - self.MessageSwitch = true - return self -end - - ---- Execute the routing. --- @param #STAGEROUTE self --- @param Mission#MISSION Mission --- @param Client#CLIENT Client --- @param Task#TASK Task -function STAGEROUTE:Execute( Mission, Client, Task ) - self:F() - local Valid = BASE:Inherited(self):Execute( Mission, Client, Task ) - - local RouteMessage = "Fly to: " - self:T( Task.LandingZones ) - for LandingZoneID, LandingZoneName in pairs( Task.LandingZones.LandingZoneNames ) do - RouteMessage = RouteMessage .. "\n " .. LandingZoneName .. ' at ' .. routines.getBRStringZone( { zone = LandingZoneName, ref = Client:GetClientGroupDCSUnit():getPoint(), true, true } ) .. ' km.' - end - - if Client:IsMultiSeated() then - Client:Message( RouteMessage, self.MSG.TIME, "Co-Pilot", 20, "Route" ) - else - Client:Message( RouteMessage, self.MSG.TIME, "Command", 20, "Route" ) - end - - - if Mission.MissionReportFlash and Client:IsTransport() then - Client:ShowCargo() - end - - return Valid -end - -function STAGEROUTE:Validate( Mission, Client, Task ) - self:F() - local Valid = STAGE:Validate( Mission, Client, Task ) - - -- check if the Client is in the landing zone - self:T( Task.LandingZones.LandingZoneNames ) - Task.CurrentLandingZoneName = routines.IsUnitNearZonesRadius( Client:GetClientGroupDCSUnit(), Task.LandingZones.LandingZoneNames, 500 ) - - if Task.CurrentLandingZoneName then - - Task.CurrentLandingZone = Task.LandingZones.LandingZones[Task.CurrentLandingZoneName].CargoZone - Task.CurrentCargoZone = Task.LandingZones.LandingZones[Task.CurrentLandingZoneName] - - if Task.CurrentCargoZone then - if not Task.Signalled then - Task.Signalled = Task.CurrentCargoZone:Signal() - end - end - - self:T( 1 ) - return 1 - end - - self:T( 0 ) - return 0 -end - - - -STAGELANDING = { - ClassName = "STAGELANDING", - MSG = { ID = "Landing", TIME = 10 }, - Name = "Landing", - Signalled = false -} - -function STAGELANDING:New() - local self = BASE:Inherit( self, STAGE:New() ) - self:F() - self.StageType = 'CLIENT' - return self -end - ---- Execute the landing coordination. --- @param #STAGELANDING self --- @param Mission#MISSION Mission --- @param Client#CLIENT Client --- @param Task#TASK Task -function STAGELANDING:Execute( Mission, Client, Task ) - self:F() - - if Client:IsMultiSeated() then - Client:Message( "We have arrived at the landing zone.", self.MSG.TIME, "Co-Pilot" ) - else - Client:Message( "You have arrived at the landing zone.", self.MSG.TIME, "Command" ) - end - - Task.HostUnit = Task.CurrentCargoZone:GetHostUnit() - - self:T( { Task.HostUnit } ) - - if Task.HostUnit then - - Task.HostUnitName = Task.HostUnit:GetPrefix() - Task.HostUnitTypeName = Task.HostUnit:GetTypeName() - - local HostMessage = "" - Task.CargoNames = "" - - local IsFirst = true - - for CargoID, Cargo in pairs( CARGOS ) do - if Cargo.CargoType == Task.CargoType then - - if Cargo:IsLandingRequired() then - self:T( "Task for cargo " .. Cargo.CargoType .. " requires landing.") - Task.IsLandingRequired = true - end - - if Cargo:IsSlingLoad() then - self:T( "Task for cargo " .. Cargo.CargoType .. " is a slingload.") - Task.IsSlingLoad = true - end - - if IsFirst then - IsFirst = false - Task.CargoNames = Task.CargoNames .. Cargo.CargoName .. "( " .. Cargo.CargoWeight .. " )" - else - Task.CargoNames = Task.CargoNames .. "; " .. Cargo.CargoName .. "( " .. Cargo.CargoWeight .. " )" - end - end - end - - if Task.IsLandingRequired then - HostMessage = "Land the helicopter to " .. Task.TEXT[1] .. " " .. Task.CargoNames .. "." - else - HostMessage = "Use the Radio menu and F6 to find the cargo, then fly or land near the cargo and " .. Task.TEXT[1] .. " " .. Task.CargoNames .. "." - end - - local Host = "Command" - if Task.HostUnitName then - Host = Task.HostUnitName .. " (" .. Task.HostUnitTypeName .. ")" - else - if Client:IsMultiSeated() then - Host = "Co-Pilot" - end - end - - Client:Message( HostMessage, self.MSG.TIME, Host ) - - end -end - -function STAGELANDING:Validate( Mission, Client, Task ) - self:F() - - Task.CurrentLandingZoneName = routines.IsUnitNearZonesRadius( Client:GetClientGroupDCSUnit(), Task.LandingZones.LandingZoneNames, 500 ) - if Task.CurrentLandingZoneName then - - -- Client is in de landing zone. - self:T( Task.CurrentLandingZoneName ) - - Task.CurrentLandingZone = Task.LandingZones.LandingZones[Task.CurrentLandingZoneName].CargoZone - Task.CurrentCargoZone = Task.LandingZones.LandingZones[Task.CurrentLandingZoneName] - - if Task.CurrentCargoZone then - if not Task.Signalled then - Task.Signalled = Task.CurrentCargoZone:Signal() - end - end - else - if Task.CurrentLandingZone then - Task.CurrentLandingZone = nil - end - if Task.CurrentCargoZone then - Task.CurrentCargoZone = nil - end - Task.Signalled = false - Task:RemoveCargoMenus( Client ) - self:T( -1 ) - return -1 - end - - - local DCSUnitVelocityVec3 = Client:GetClientGroupDCSUnit():getVelocity() - local DCSUnitVelocity = ( DCSUnitVelocityVec3.x ^2 + DCSUnitVelocityVec3.y ^2 + DCSUnitVelocityVec3.z ^2 ) ^ 0.5 - - local DCSUnitPointVec3 = Client:GetClientGroupDCSUnit():getPoint() - local LandHeight = land.getHeight( { x = DCSUnitPointVec3.x, y = DCSUnitPointVec3.z } ) - local DCSUnitHeight = DCSUnitPointVec3.y - LandHeight - - self:T( { Task.IsLandingRequired, Client:GetClientGroupDCSUnit():inAir() } ) - if Task.IsLandingRequired and not Client:GetClientGroupDCSUnit():inAir() then - self:T( 1 ) - Task.IsInAirTestRequired = true - return 1 - end - - self:T( { DCSUnitVelocity, DCSUnitHeight, LandHeight, Task.CurrentCargoZone.SignalHeight } ) - if Task.IsLandingRequired and DCSUnitVelocity <= 0.05 and DCSUnitHeight <= Task.CurrentCargoZone.SignalHeight then - self:T( 1 ) - Task.IsInAirTestRequired = false - return 1 - end - - self:T( 0 ) - return 0 -end - -STAGELANDED = { - ClassName = "STAGELANDED", - MSG = { ID = "Land", TIME = 10 }, - Name = "Landed", - MenusAdded = false -} - -function STAGELANDED:New() - local self = BASE:Inherit( self, STAGE:New() ) - self:F() - self.StageType = 'CLIENT' - return self -end - -function STAGELANDED:Execute( Mission, Client, Task ) - self:F() - - if Task.IsLandingRequired then - - local Host = "Command" - if Task.HostUnitName then - Host = Task.HostUnitName .. " (" .. Task.HostUnitTypeName .. ")" - else - if Client:IsMultiSeated() then - Host = "Co-Pilot" - end - end - - Client:Message( 'You have landed within the landing zone. Use the radio menu (F10) to ' .. Task.TEXT[1] .. ' the ' .. Task.CargoType .. '.', - self.MSG.TIME, Host ) - - if not self.MenusAdded then - Task.Cargo = nil - Task:RemoveCargoMenus( Client ) - Task:AddCargoMenus( Client, CARGOS, 250 ) - end - end -end - - - -function STAGELANDED:Validate( Mission, Client, Task ) - self:F() - - if not routines.IsUnitNearZonesRadius( Client:GetClientGroupDCSUnit(), Task.CurrentLandingZoneName, 500 ) then - self:T( "Client is not anymore in the landing zone, go back to stage Route, and remove cargo menus." ) - Task.Signalled = false - Task:RemoveCargoMenus( Client ) - self:T( -2 ) - return -2 - end - - local DCSUnitVelocityVec3 = Client:GetClientGroupDCSUnit():getVelocity() - local DCSUnitVelocity = ( DCSUnitVelocityVec3.x ^2 + DCSUnitVelocityVec3.y ^2 + DCSUnitVelocityVec3.z ^2 ) ^ 0.5 - - local DCSUnitPointVec3 = Client:GetClientGroupDCSUnit():getPoint() - local LandHeight = land.getHeight( { x = DCSUnitPointVec3.x, y = DCSUnitPointVec3.z } ) - local DCSUnitHeight = DCSUnitPointVec3.y - LandHeight - - self:T( { Task.IsLandingRequired, Client:GetClientGroupDCSUnit():inAir() } ) - if Task.IsLandingRequired and Task.IsInAirTestRequired == true and Client:GetClientGroupDCSUnit():inAir() then - self:T( "Client went back in the air. Go back to stage Landing." ) - self:T( -1 ) - return -1 - end - - self:T( { DCSUnitVelocity, DCSUnitHeight, LandHeight, Task.CurrentCargoZone.SignalHeight } ) - if Task.IsLandingRequired and Task.IsInAirTestRequired == false and DCSUnitVelocity >= 2 and DCSUnitHeight >= Task.CurrentCargoZone.SignalHeight then - self:T( "It seems the Client went back in the air and over the boundary limits. Go back to stage Landing." ) - self:T( -1 ) - return -1 - end - - -- Wait until cargo is selected from the menu. - if Task.IsLandingRequired then - if not Task.Cargo then - self:T( 0 ) - return 0 - end - end - - self:T( 1 ) - return 1 -end - -STAGEUNLOAD = { - ClassName = "STAGEUNLOAD", - MSG = { ID = "Unload", TIME = 10 }, - Name = "Unload" -} - -function STAGEUNLOAD:New() - local self = BASE:Inherit( self, STAGE:New() ) - self:F() - self.StageType = 'CLIENT' - return self -end - ---- Coordinate UnLoading --- @param #STAGEUNLOAD self --- @param Mission#MISSION Mission --- @param Client#CLIENT Client --- @param Task#TASK Task -function STAGEUNLOAD:Execute( Mission, Client, Task ) - self:F() - - if Client:IsMultiSeated() then - Client:Message( 'The ' .. Task.CargoType .. ' are being ' .. Task.TEXT[2] .. ' within the landing zone. Wait until the helicopter is ' .. Task.TEXT[3] .. '.', - "Co-Pilot" ) - else - Client:Message( 'You are unloading the ' .. Task.CargoType .. ' ' .. Task.TEXT[2] .. ' within the landing zone. Wait until the helicopter is ' .. Task.TEXT[3] .. '.', - "Command" ) - end - Task:RemoveCargoMenus( Client ) -end - -function STAGEUNLOAD:Executing( Mission, Client, Task ) - self:F() - env.info( 'STAGEUNLOAD:Executing() Task.Cargo.CargoName = ' .. Task.Cargo.CargoName ) - - local TargetZoneName - - if Task.TargetZoneName then - TargetZoneName = Task.TargetZoneName - else - TargetZoneName = Task.CurrentLandingZoneName - end - - if Task.Cargo:UnLoad( Client, TargetZoneName ) then - Task.ExecuteStage = _TransportExecuteStage.SUCCESS - if Mission.MissionReportFlash then - Client:ShowCargo() - end - end -end - ---- Validate UnLoading --- @param #STAGEUNLOAD self --- @param Mission#MISSION Mission --- @param Client#CLIENT Client --- @param Task#TASK Task -function STAGEUNLOAD:Validate( Mission, Client, Task ) - self:F() - env.info( 'STAGEUNLOAD:Validate()' ) - - if routines.IsUnitNearZonesRadius( Client:GetClientGroupDCSUnit(), Task.CurrentLandingZoneName, 500 ) then - else - Task.ExecuteStage = _TransportExecuteStage.FAILED - Task:RemoveCargoMenus( Client ) - if Client:IsMultiSeated() then - Client:Message( 'The ' .. Task.CargoType .. " haven't been successfully " .. Task.TEXT[3] .. ' within the landing zone. Task and mission has failed.', - _TransportStageMsgTime.DONE, "Co-Pilot" ) - else - Client:Message( 'The ' .. Task.CargoType .. " haven't been successfully " .. Task.TEXT[3] .. ' within the landing zone. Task and mission has failed.', - _TransportStageMsgTime.DONE, "Command" ) - end - return 1 - end - - if not Client:GetClientGroupDCSUnit():inAir() then - else - Task.ExecuteStage = _TransportExecuteStage.FAILED - Task:RemoveCargoMenus( Client ) - if Client:IsMultiSeated() then - Client:Message( 'The ' .. Task.CargoType .. " haven't been successfully " .. Task.TEXT[3] .. ' within the landing zone. Task and mission has failed.', - _TransportStageMsgTime.DONE, "Co-Pilot" ) - else - Client:Message( 'The ' .. Task.CargoType .. " haven't been successfully " .. Task.TEXT[3] .. ' within the landing zone. Task and mission has failed.', - _TransportStageMsgTime.DONE, "Command" ) - end - return 1 - end - - if Task.ExecuteStage == _TransportExecuteStage.SUCCESS then - if Client:IsMultiSeated() then - Client:Message( 'The ' .. Task.CargoType .. ' have been sucessfully ' .. Task.TEXT[3] .. ' within the landing zone.', _TransportStageMsgTime.DONE, "Co-Pilot" ) - else - Client:Message( 'The ' .. Task.CargoType .. ' have been sucessfully ' .. Task.TEXT[3] .. ' within the landing zone.', _TransportStageMsgTime.DONE, "Command" ) - end - Task:RemoveCargoMenus( Client ) - Task.MissionTask:AddGoalCompletion( Task.MissionTask.GoalVerb, Task.CargoName, 1 ) -- We set the cargo as one more goal completed in the mission. - return 1 - end - - return 1 -end - -STAGELOAD = { - ClassName = "STAGELOAD", - MSG = { ID = "Load", TIME = 10 }, - Name = "Load" -} - -function STAGELOAD:New() - local self = BASE:Inherit( self, STAGE:New() ) - self:F() - self.StageType = 'CLIENT' - return self -end - -function STAGELOAD:Execute( Mission, Client, Task ) - self:F() - - if not Task.IsSlingLoad then - - local Host = "Command" - if Task.HostUnitName then - Host = Task.HostUnitName .. " (" .. Task.HostUnitTypeName .. ")" - else - if Client:IsMultiSeated() then - Host = "Co-Pilot" - end - end - - Client:Message( 'The ' .. Task.CargoType .. ' are being ' .. Task.TEXT[2] .. ' within the landing zone. Wait until the helicopter is ' .. Task.TEXT[3] .. '.', - _TransportStageMsgTime.EXECUTING, Host ) - - -- Route the cargo to the Carrier - - Task.Cargo:OnBoard( Client, Task.CurrentCargoZone, Task.OnBoardSide ) - Task.ExecuteStage = _TransportExecuteStage.EXECUTING - else - Task.ExecuteStage = _TransportExecuteStage.EXECUTING - end -end - -function STAGELOAD:Executing( Mission, Client, Task ) - self:F() - - -- If the Cargo is ready to be loaded, load it into the Client. - - local Host = "Command" - if Task.HostUnitName then - Host = Task.HostUnitName .. " (" .. Task.HostUnitTypeName .. ")" - else - if Client:IsMultiSeated() then - Host = "Co-Pilot" - end - end - - if not Task.IsSlingLoad then - self:T( Task.Cargo.CargoName) - - if Task.Cargo:OnBoarded( Client, Task.CurrentCargoZone ) then - - -- Load the Cargo onto the Client - Task.Cargo:Load( Client ) - - -- Message to the pilot that cargo has been loaded. - Client:Message( "The cargo " .. Task.Cargo.CargoName .. " has been loaded in our helicopter.", - 20, Host ) - Task.ExecuteStage = _TransportExecuteStage.SUCCESS - - Client:ShowCargo() - end - else - Client:Message( "Hook the " .. Task.CargoNames .. " onto the helicopter " .. Task.TEXT[3] .. " within the landing zone.", - _TransportStageMsgTime.EXECUTING, Host ) - for CargoID, Cargo in pairs( CARGOS ) do - self:T( "Cargo.CargoName = " .. Cargo.CargoName ) - - if Cargo:IsSlingLoad() then - local CargoStatic = StaticObject.getByName( Cargo.CargoStaticName ) - if CargoStatic then - self:T( "Cargo is found in the DCS simulator.") - local CargoStaticPosition = CargoStatic:getPosition().p - self:T( "Cargo Position x = " .. CargoStaticPosition.x .. ", y = " .. CargoStaticPosition.y .. ", z = " .. CargoStaticPosition.z ) - local CargoStaticHeight = routines.GetUnitHeight( CargoStatic ) - if CargoStaticHeight > 5 then - self:T( "Cargo is airborne.") - Cargo:StatusLoaded() - Task.Cargo = Cargo - Client:Message( 'The Cargo has been successfully hooked onto the helicopter and is now being sling loaded. Fly outside the landing zone.', - self.MSG.TIME, Host ) - Task.ExecuteStage = _TransportExecuteStage.SUCCESS - break - end - else - self:T( "Cargo not found in the DCS simulator." ) - end - end - end - end - -end - -function STAGELOAD:Validate( Mission, Client, Task ) - self:F() - - self:T( "Task.CurrentLandingZoneName = " .. Task.CurrentLandingZoneName ) - - local Host = "Command" - if Task.HostUnitName then - Host = Task.HostUnitName .. " (" .. Task.HostUnitTypeName .. ")" - else - if Client:IsMultiSeated() then - Host = "Co-Pilot" - end - end - - if not Task.IsSlingLoad then - if not routines.IsUnitNearZonesRadius( Client:GetClientGroupDCSUnit(), Task.CurrentLandingZoneName, 500 ) then - Task:RemoveCargoMenus( Client ) - Task.ExecuteStage = _TransportExecuteStage.FAILED - Task.CargoName = nil - Client:Message( "The " .. Task.CargoType .. " loading has been aborted. You flew outside the pick-up zone while loading. ", - self.MSG.TIME, Host ) - self:T( -1 ) - return -1 - end - - local DCSUnitVelocityVec3 = Client:GetClientGroupDCSUnit():getVelocity() - local DCSUnitVelocity = ( DCSUnitVelocityVec3.x ^2 + DCSUnitVelocityVec3.y ^2 + DCSUnitVelocityVec3.z ^2 ) ^ 0.5 - - local DCSUnitPointVec3 = Client:GetClientGroupDCSUnit():getPoint() - local LandHeight = land.getHeight( { x = DCSUnitPointVec3.x, y = DCSUnitPointVec3.z } ) - local DCSUnitHeight = DCSUnitPointVec3.y - LandHeight - - self:T( { Task.IsLandingRequired, Client:GetClientGroupDCSUnit():inAir() } ) - if Task.IsLandingRequired and Task.IsInAirTestRequired == true and Client:GetClientGroupDCSUnit():inAir() then - Task:RemoveCargoMenus( Client ) - Task.ExecuteStage = _TransportExecuteStage.FAILED - Task.CargoName = nil - Client:Message( "The " .. Task.CargoType .. " loading has been aborted. Re-start the " .. Task.TEXT[3] .. " process. Don't fly outside the pick-up zone.", - self.MSG.TIME, Host ) - self:T( -1 ) - return -1 - end - - self:T( { DCSUnitVelocity, DCSUnitHeight, LandHeight, Task.CurrentCargoZone.SignalHeight } ) - if Task.IsLandingRequired and Task.IsInAirTestRequired == false and DCSUnitVelocity >= 2 and DCSUnitHeight >= Task.CurrentCargoZone.SignalHeight then - Task:RemoveCargoMenus( Client ) - Task.ExecuteStage = _TransportExecuteStage.FAILED - Task.CargoName = nil - Client:Message( "The " .. Task.CargoType .. " loading has been aborted. Re-start the " .. Task.TEXT[3] .. " process. Don't fly outside the pick-up zone.", - self.MSG.TIME, Host ) - self:T( -1 ) - return -1 - end - - if Task.ExecuteStage == _TransportExecuteStage.SUCCESS then - Task:RemoveCargoMenus( Client ) - Client:Message( "Good Job. The " .. Task.CargoType .. " has been sucessfully " .. Task.TEXT[3] .. " within the landing zone.", - self.MSG.TIME, Host ) - Task.MissionTask:AddGoalCompletion( Task.MissionTask.GoalVerb, Task.CargoName, 1 ) - self:T( 1 ) - return 1 - end - - else - if Task.ExecuteStage == _TransportExecuteStage.SUCCESS then - CargoStatic = StaticObject.getByName( Task.Cargo.CargoStaticName ) - if CargoStatic and not routines.IsStaticInZones( CargoStatic, Task.CurrentLandingZoneName ) then - Client:Message( "Good Job. The " .. Task.CargoType .. " has been sucessfully " .. Task.TEXT[3] .. " and flown outside of the landing zone.", - self.MSG.TIME, Host ) - Task.MissionTask:AddGoalCompletion( Task.MissionTask.GoalVerb, Task.Cargo.CargoName, 1 ) - self:T( 1 ) - return 1 - end - end - - end - - - self:T( 0 ) - return 0 -end - - -STAGEDONE = { - ClassName = "STAGEDONE", - MSG = { ID = "Done", TIME = 10 }, - Name = "Done" -} - -function STAGEDONE:New() - local self = BASE:Inherit( self, STAGE:New() ) - self:F() - self.StageType = 'AI' - return self -end - -function STAGEDONE:Execute( Mission, Client, Task ) - self:F() - -end - -function STAGEDONE:Validate( Mission, Client, Task ) - self:F() - - Task:Done() - - return 0 -end - -STAGEARRIVE = { - ClassName = "STAGEARRIVE", - MSG = { ID = "Arrive", TIME = 10 }, - Name = "Arrive" -} - -function STAGEARRIVE:New() - local self = BASE:Inherit( self, STAGE:New() ) - self:F() - self.StageType = 'CLIENT' - return self -end - - ---- Execute Arrival --- @param #STAGEARRIVE self --- @param Mission#MISSION Mission --- @param Client#CLIENT Client --- @param Task#TASK Task -function STAGEARRIVE:Execute( Mission, Client, Task ) - self:F() - - if Client:IsMultiSeated() then - Client:Message( 'We have arrived at ' .. Task.CurrentLandingZoneName .. ".", self.MSG.TIME, "Co-Pilot" ) - else - Client:Message( 'We have arrived at ' .. Task.CurrentLandingZoneName .. ".", self.MSG.TIME, "Command" ) - end - -end - -function STAGEARRIVE:Validate( Mission, Client, Task ) - self:F() - - Task.CurrentLandingZoneID = routines.IsUnitInZones( Client:GetClientGroupDCSUnit(), Task.LandingZones ) - if ( Task.CurrentLandingZoneID ) then - else - return -1 - end - - return 1 -end - -STAGEGROUPSDESTROYED = { - ClassName = "STAGEGROUPSDESTROYED", - DestroyGroupSize = -1, - Frequency = STAGE.FREQUENCY.REPEAT, - MSG = { ID = "DestroyGroup", TIME = 10 }, - Name = "GroupsDestroyed" -} - -function STAGEGROUPSDESTROYED:New() - local self = BASE:Inherit( self, STAGE:New() ) - self:F() - self.StageType = 'AI' - return self -end - ---function STAGEGROUPSDESTROYED:Execute( Mission, Client, Task ) --- --- Client:Message( 'Task: Still ' .. DestroyGroupSize .. " of " .. Task.DestroyGroupCount .. " " .. Task.DestroyGroupType .. " to be destroyed!", self.MSG.TIME, Mission.Name .. "/Stage" ) --- ---end - -function STAGEGROUPSDESTROYED:Validate( Mission, Client, Task ) - self:F() - - if Task.MissionTask:IsGoalReached() then - return 1 - else - return 0 - end -end - -function STAGEGROUPSDESTROYED:Execute( Mission, Client, Task ) - self:F() - self:T( { Task.ClassName, Task.Destroyed } ) - --env.info( 'Event Table Task = ' .. tostring(Task) ) - -end - - - - - - - - - - - - - ---[[ - _TransportStage: Defines the different stages of which of transport missions can be in. This table is internal and is used to control the sequence of messages, actions and flow. - - - _TransportStage.START - - _TransportStage.ROUTE - - _TransportStage.LAND - - _TransportStage.EXECUTE - - _TransportStage.DONE - - _TransportStage.REMOVE ---]] -_TransportStage = { - HOLD = "HOLD", - START = "START", - ROUTE = "ROUTE", - LANDING = "LANDING", - LANDED = "LANDED", - EXECUTING = "EXECUTING", - LOAD = "LOAD", - UNLOAD = "UNLOAD", - DONE = "DONE", - NEXT = "NEXT" -} - -_TransportStageMsgTime = { - HOLD = 10, - START = 60, - ROUTE = 5, - LANDING = 10, - LANDED = 30, - EXECUTING = 30, - LOAD = 30, - UNLOAD = 30, - DONE = 30, - NEXT = 0 -} - -_TransportStageTime = { - HOLD = 10, - START = 5, - ROUTE = 5, - LANDING = 1, - LANDED = 1, - EXECUTING = 5, - LOAD = 5, - UNLOAD = 5, - DONE = 1, - NEXT = 0 -} - -_TransportStageAction = { - REPEAT = -1, - NONE = 0, - ONCE = 1 -} ---- This module contains the TASK_BASE class. --- --- 1) @{#TASK_BASE} class, extends @{Base#BASE} --- ============================================ --- 1.1) The @{#TASK_BASE} class implements the methods for task orchestration within MOOSE. --- ---------------------------------------------------------------------------------------- --- The class provides a couple of methods to: --- --- * @{#TASK_BASE.AssignToGroup}():Assign a task to a group (of players). --- * @{#TASK_BASE.AddProcess}():Add a @{Process} to a task. --- * @{#TASK_BASE.RemoveProcesses}():Remove a running @{Process} from a running task. --- * @{#TASK_BASE.AddStateMachine}():Add a @{StateMachine} to a task. --- * @{#TASK_BASE.RemoveStateMachines}():Remove @{StateMachine}s from a task. --- * @{#TASK_BASE.HasStateMachine}():Enquire if the task has a @{StateMachine} --- * @{#TASK_BASE.AssignToUnit}(): Assign a task to a unit. (Needs to be implemented in the derived classes from @{#TASK_BASE}. --- * @{#TASK_BASE.UnAssignFromUnit}(): Unassign the task from a unit. --- --- 1.2) Set and enquire task status (beyond the task state machine processing). --- ---------------------------------------------------------------------------- --- A task needs to implement as a minimum the following task states: --- --- * **Success**: Expresses the successful execution and finalization of the task. --- * **Failed**: Expresses the failure of a task. --- * **Planned**: Expresses that the task is created, but not yet in execution and is not assigned yet. --- * **Assigned**: Expresses that the task is assigned to a Group of players, and that the task is in execution mode. --- --- A task may also implement the following task states: --- --- * **Rejected**: Expresses that the task is rejected by a player, who was requested to accept the task. --- * **Cancelled**: Expresses that the task is cancelled by HQ or through a logical situation where a cancellation of the task is required. --- --- A task can implement more statusses than the ones outlined above. Please consult the documentation of the specific tasks to understand the different status modelled. --- --- The status of tasks can be set by the methods **State** followed by the task status. An example is `StateAssigned()`. --- The status of tasks can be enquired by the methods **IsState** followed by the task status name. An example is `if IsStateAssigned() then`. --- --- 1.3) Add scoring when reaching a certain task status: --- ----------------------------------------------------- --- Upon reaching a certain task status in a task, additional scoring can be given. If the Mission has a scoring system attached, the scores will be added to the mission scoring. --- Use the method @{#TASK_BASE.AddScore}() to add scores when a status is reached. --- --- 1.4) Task briefing: --- ------------------- --- A task briefing can be given that is shown to the player when he is assigned to the task. --- --- === --- --- ### Authors: FlightControl - Design and Programming --- --- @module Task - ---- The TASK_BASE class --- @type TASK_BASE --- @field Scheduler#SCHEDULER TaskScheduler --- @field Mission#MISSION Mission --- @field StateMachine#STATEMACHINE Fsm --- @field Set#SET_GROUP SetGroup The Set of Groups assigned to the Task --- @extends Base#BASE -TASK_BASE = { - ClassName = "TASK_BASE", - TaskScheduler = nil, - Processes = {}, - Players = nil, - Scores = {}, - Menu = {}, - SetGroup = nil, -} - - ---- Instantiates a new TASK_BASE. Should never be used. Interface Class. --- @param #TASK_BASE self --- @param Mission#MISSION The mission wherein the Task is registered. --- @param Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. --- @param #string TaskName The name of the Task --- @param #string TaskType The type of the Task --- @param #string TaskCategory The category of the Task (A2G, A2A, Transport, ... ) --- @return #TASK_BASE self -function TASK_BASE:New( Mission, SetGroup, TaskName, TaskType, TaskCategory ) - - local self = BASE:Inherit( self, BASE:New() ) - self:E( "New TASK " .. TaskName ) - - self.Processes = {} - self.Fsm = {} - - self.Mission = Mission - self.SetGroup = SetGroup - - self:SetCategory( TaskCategory ) - self:SetType( TaskType ) - self:SetName( TaskName ) - self:SetID( Mission:GetNextTaskID( self ) ) -- The Mission orchestrates the task sequences .. - - self.TaskBriefing = "You are assigned to the task: " .. self.TaskName .. "." - - return self -end - ---- Cleans all references of a TASK_BASE. --- @param #TASK_BASE self --- @return #nil -function TASK_BASE:CleanUp() - - _EVENTDISPATCHER:OnPlayerLeaveRemove( self ) - _EVENTDISPATCHER:OnDeadRemove( self ) - _EVENTDISPATCHER:OnCrashRemove( self ) - _EVENTDISPATCHER:OnPilotDeadRemove( self ) - - return nil -end - - ---- Assign the @{Task}to a @{Group}. --- @param #TASK_BASE self --- @param Group#GROUP TaskGroup -function TASK_BASE:AssignToGroup( TaskGroup ) - self:F2( TaskGroup:GetName() ) - - local TaskGroupName = TaskGroup:GetName() - - TaskGroup:SetState( TaskGroup, "Assigned", self ) - - self:RemoveMenuForGroup( TaskGroup ) - self:SetAssignedMenuForGroup( TaskGroup ) - - local TaskUnits = TaskGroup:GetUnits() - for UnitID, UnitData in pairs( TaskUnits ) do - local TaskUnit = UnitData -- Unit#UNIT - local PlayerName = TaskUnit:GetPlayerName() - if PlayerName ~= nil or PlayerName ~= "" then - self:AssignToUnit( TaskUnit ) - end - end -end - ---- Send the briefng message of the @{Task} to the assigned @{Group}s. --- @param #TASK_BASE self -function TASK_BASE:SendBriefingToAssignedGroups() - self:F2() - - for TaskGroupName, TaskGroup in pairs( self.SetGroup:GetSet() ) do - - if self:IsAssignedToGroup( TaskGroup ) then - TaskGroup:Message( self.TaskBriefing, 60 ) - end - end -end - - ---- Assign the @{Task} from the @{Group}s. --- @param #TASK_BASE self -function TASK_BASE:UnAssignFromGroups() - self:F2() - - for TaskGroupName, TaskGroup in pairs( self.SetGroup:GetSet() ) do - - TaskGroup:SetState( TaskGroup, "Assigned", nil ) - local TaskUnits = TaskGroup:GetUnits() - for UnitID, UnitData in pairs( TaskUnits ) do - local TaskUnit = UnitData -- Unit#UNIT - local PlayerName = TaskUnit:GetPlayerName() - if PlayerName ~= nil or PlayerName ~= "" then - self:UnAssignFromUnit( TaskUnit ) - end - end - end -end - ---- Returns if the @{Task} is assigned to the Group. --- @param #TASK_BASE self --- @param Group#GROUP TaskGroup --- @return #boolean -function TASK_BASE:IsAssignedToGroup( TaskGroup ) - - local TaskGroupName = TaskGroup:GetName() - - if self:IsStateAssigned() then - if TaskGroup:GetState( TaskGroup, "Assigned" ) == self then - return true - end - end - - return false -end - ---- Assign the @{Task}to an alive @{Unit}. --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @return #TASK_BASE self -function TASK_BASE:AssignToUnit( TaskUnit ) - self:F( TaskUnit:GetName() ) - - return nil -end - ---- UnAssign the @{Task} from an alive @{Unit}. --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @return #TASK_BASE self -function TASK_BASE:UnAssignFromUnit( TaskUnitName ) - self:F( TaskUnitName ) - - if self:HasStateMachine( TaskUnitName ) == true then - self:RemoveStateMachines( TaskUnitName ) - self:RemoveProcesses( TaskUnitName ) - end - - return self -end - ---- Set the menu options of the @{Task} to all the groups in the SetGroup. --- @param #TASK_BASE self --- @return #TASK_BASE self -function TASK_BASE:SetPlannedMenu() - - local MenuText = self:GetPlannedMenuText() - for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - if not self:IsAssignedToGroup( TaskGroup ) then - self:SetPlannedMenuForGroup( TaskGroup, MenuText ) - end - end -end - ---- Set the menu options of the @{Task} to all the groups in the SetGroup. --- @param #TASK_BASE self --- @return #TASK_BASE self -function TASK_BASE:SetAssignedMenu() - - for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - if self:IsAssignedToGroup( TaskGroup ) then - self:SetAssignedMenuForGroup( TaskGroup ) - end - end -end - ---- Remove the menu options of the @{Task} to all the groups in the SetGroup. --- @param #TASK_BASE self --- @return #TASK_BASE self -function TASK_BASE:RemoveMenu() - - for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - self:RemoveMenuForGroup( TaskGroup ) - end -end - ---- Set the planned menu option of the @{Task}. --- @param #TASK_BASE self --- @param Group#GROUP TaskGroup --- @param #string MenuText The menu text. --- @return #TASK_BASE self -function TASK_BASE:SetPlannedMenuForGroup( TaskGroup, MenuText ) - self:E( TaskGroup:GetName() ) - - local TaskMission = self.Mission:GetName() - local TaskCategory = self:GetCategory() - local TaskType = self:GetType() - - local Mission = self.Mission - - Mission.MenuMission = Mission.MenuMission or {} - local MenuMission = Mission.MenuMission - - Mission.MenuCategory = Mission.MenuCategory or {} - local MenuCategory = Mission.MenuCategory - - Mission.MenuType = Mission.MenuType or {} - local MenuType = Mission.MenuType - - self.Menu = self.Menu or {} - local Menu = self.Menu - - local TaskGroupName = TaskGroup:GetName() - MenuMission[TaskGroupName] = MenuMission[TaskGroupName] or MENU_GROUP:New( TaskGroup, TaskMission, nil ) - - MenuCategory[TaskGroupName] = MenuCategory[TaskGroupName] or {} - MenuCategory[TaskGroupName][TaskCategory] = MenuCategory[TaskGroupName][TaskCategory] or MENU_GROUP:New( TaskGroup, TaskCategory, MenuMission[TaskGroupName] ) - - MenuType[TaskGroupName] = MenuType[TaskGroupName] or {} - MenuType[TaskGroupName][TaskType] = MenuType[TaskGroupName][TaskType] or MENU_GROUP:New( TaskGroup, TaskType, MenuCategory[TaskGroupName][TaskCategory] ) - - if Menu[TaskGroupName] then - Menu[TaskGroupName]:Remove() - end - Menu[TaskGroupName] = MENU_GROUP_COMMAND:New( TaskGroup, MenuText, MenuType[TaskGroupName][TaskType], self.MenuAssignToGroup, { self = self, TaskGroup = TaskGroup } ) - - return self -end - ---- Set the assigned menu options of the @{Task}. --- @param #TASK_BASE self --- @param Group#GROUP TaskGroup --- @return #TASK_BASE self -function TASK_BASE:SetAssignedMenuForGroup( TaskGroup ) - self:E( TaskGroup:GetName() ) - - local TaskMission = self.Mission:GetName() - - local Mission = self.Mission - - Mission.MenuMission = Mission.MenuMission or {} - local MenuMission = Mission.MenuMission - - self.MenuStatus = self.MenuStatus or {} - local MenuStatus = self.MenuStatus - - - self.MenuAbort = self.MenuAbort or {} - local MenuAbort = self.MenuAbort - - local TaskGroupName = TaskGroup:GetName() - MenuMission[TaskGroupName] = MenuMission[TaskGroupName] or MENU_GROUP:New( TaskGroup, TaskMission, nil ) - MenuStatus[TaskGroupName] = MENU_GROUP_COMMAND:New( TaskGroup, "Task Status", MenuMission[TaskGroupName], self.MenuTaskStatus, { self = self, TaskGroup = TaskGroup } ) - MenuAbort[TaskGroupName] = MENU_GROUP_COMMAND:New( TaskGroup, "Abort Task", MenuMission[TaskGroupName], self.MenuTaskAbort, { self = self, TaskGroup = TaskGroup } ) - - return self -end - ---- Remove the menu option of the @{Task} for a @{Group}. --- @param #TASK_BASE self --- @param Group#GROUP TaskGroup --- @return #TASK_BASE self -function TASK_BASE:RemoveMenuForGroup( TaskGroup ) - - local TaskGroupName = TaskGroup:GetName() - - local Mission = self.Mission - local MenuMission = Mission.MenuMission - local MenuCategory = Mission.MenuCategory - local MenuType = Mission.MenuType - local MenuStatus = self.MenuStatus - local MenuAbort = self.MenuAbort - local Menu = self.Menu - - Menu = Menu or {} - if Menu[TaskGroupName] then - Menu[TaskGroupName]:Remove() - Menu[TaskGroupName] = nil - end - - MenuType = MenuType or {} - if MenuType[TaskGroupName] then - for _, Menu in pairs( MenuType[TaskGroupName] ) do - Menu:Remove() - end - MenuType[TaskGroupName] = nil - end - - MenuCategory = MenuCategory or {} - if MenuCategory[TaskGroupName] then - for _, Menu in pairs( MenuCategory[TaskGroupName] ) do - Menu:Remove() - end - MenuCategory[TaskGroupName] = nil - end - - MenuStatus = MenuStatus or {} - if MenuStatus[TaskGroupName] then - MenuStatus[TaskGroupName]:Remove() - MenuStatus[TaskGroupName] = nil - end - - MenuAbort = MenuAbort or {} - if MenuAbort[TaskGroupName] then - MenuAbort[TaskGroupName]:Remove() - MenuAbort[TaskGroupName] = nil - end - -end - -function TASK_BASE.MenuAssignToGroup( MenuParam ) - - local self = MenuParam.self - local TaskGroup = MenuParam.TaskGroup - - self:AssignToGroup( TaskGroup ) -end - -function TASK_BASE.MenuTaskStatus( MenuParam ) - - local self = MenuParam.self - local TaskGroup = MenuParam.TaskGroup - - --self:AssignToGroup( TaskGroup ) -end - -function TASK_BASE.MenuTaskAbort( MenuParam ) - - local self = MenuParam.self - local TaskGroup = MenuParam.TaskGroup - - --self:AssignToGroup( TaskGroup ) -end - - - ---- Returns the @{Task} name. --- @param #TASK_BASE self --- @return #string TaskName -function TASK_BASE:GetTaskName() - return self.TaskName -end - - ---- Add Process to @{Task} with key @{Unit}. --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @return #TASK_BASE self -function TASK_BASE:AddProcess( TaskUnit, Process ) - local TaskUnitName = TaskUnit:GetName() - self.Processes = self.Processes or {} - self.Processes[TaskUnitName] = self.Processes[TaskUnitName] or {} - self.Processes[TaskUnitName][#self.Processes[TaskUnitName]+1] = Process - return Process -end - - ---- Remove Processes from @{Task} with key @{Unit} --- @param #TASK_BASE self --- @param #string TaskUnitName --- @return #TASK_BASE self -function TASK_BASE:RemoveProcesses( TaskUnitName ) - - for ProcessID, ProcessData in pairs( self.Processes[TaskUnitName] ) do - local Process = ProcessData -- Process#PROCESS - Process:StopEvents() - Process = nil - self.Processes[TaskUnitName][ProcessID] = nil - self:E( self.Processes[TaskUnitName][ProcessID] ) - end - self.Processes[TaskUnitName] = nil -end - ---- Fail processes from @{Task} with key @{Unit} --- @param #TASK_BASE self --- @param #string TaskUnitName --- @return #TASK_BASE self -function TASK_BASE:FailProcesses( TaskUnitName ) - - for ProcessID, ProcessData in pairs( self.Processes[TaskUnitName] ) do - local Process = ProcessData -- Process#PROCESS - Process.Fsm:Fail() - end -end - ---- Add a FiniteStateMachine to @{Task} with key @{Unit} --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @return #TASK_BASE self -function TASK_BASE:AddStateMachine( TaskUnit, Fsm ) - local TaskUnitName = TaskUnit:GetName() - self.Fsm[TaskUnitName] = self.Fsm[TaskUnitName] or {} - self.Fsm[TaskUnitName][#self.Fsm[TaskUnitName]+1] = Fsm - return Fsm -end - ---- Remove FiniteStateMachines from @{Task} with key @{Unit} --- @param #TASK_BASE self --- @param #string TaskUnitName --- @return #TASK_BASE self -function TASK_BASE:RemoveStateMachines( TaskUnitName ) - - for _, Fsm in pairs( self.Fsm[TaskUnitName] ) do - Fsm = nil - self.Fsm[TaskUnitName][_] = nil - self:E( self.Fsm[TaskUnitName][_] ) - end - self.Fsm[TaskUnitName] = nil -end - ---- Checks if there is a FiniteStateMachine assigned to @{Unit} for @{Task} --- @param #TASK_BASE self --- @param #string TaskUnitName --- @return #TASK_BASE self -function TASK_BASE:HasStateMachine( TaskUnitName ) - - self:F( { TaskUnitName, self.Fsm[TaskUnitName] ~= nil } ) - return ( self.Fsm[TaskUnitName] ~= nil ) -end - - - - - ---- Register a potential new assignment for a new spawned @{Unit}. --- Tasks only get assigned if there are players in it. --- @param #TASK_BASE self --- @param Event#EVENTDATA Event --- @return #TASK_BASE self -function TASK_BASE:_EventAssignUnit( Event ) - if Event.IniUnit then - self:F( Event ) - local TaskUnit = Event.IniUnit - if TaskUnit:IsAlive() then - local TaskPlayerName = TaskUnit:GetPlayerName() - if TaskPlayerName ~= nil then - if not self:HasStateMachine( TaskUnit ) then - -- Check if the task was assigned to the group, if it was assigned to the group, assign to the unit just spawned and initiate the processes. - local TaskGroup = TaskUnit:GetGroup() - if self:IsAssignedToGroup( TaskGroup ) then - self:AssignToUnit( TaskUnit ) - end - end - end - end - end - return nil -end - ---- Catches the "player leave unit" event for a @{Unit} .... --- When a player is an air unit, and leaves the unit: --- --- * and he is not at an airbase runway on the ground, he will fail its task. --- * and he is on an airbase and on the ground, the process for him will just continue to work, he can switch airplanes, and take-off again. --- This is important to model the change from plane types for a player during mission assignment. --- @param #TASK_BASE self --- @param Event#EVENTDATA Event --- @return #TASK_BASE self -function TASK_BASE:_EventPlayerLeaveUnit( Event ) - self:F( Event ) - if Event.IniUnit then - local TaskUnit = Event.IniUnit - local TaskUnitName = Event.IniUnitName - - -- Check if for this unit in the task there is a process ongoing. - if self:HasStateMachine( TaskUnitName ) then - if TaskUnit:IsAir() then - if TaskUnit:IsAboveRunway() then - -- do nothing - else - self:E( "IsNotAboveRunway" ) - -- Player left airplane during an assigned task and was not at an airbase. - self:FailProcesses( TaskUnitName ) - self:UnAssignFromUnit( TaskUnitName ) - end - end - end - - end - return nil -end - ---- UnAssigns a @{Unit} that is left by a player, crashed, dead, .... --- There are only assignments if there are players in it. --- @param #TASK_BASE self --- @param Event#EVENTDATA Event --- @return #TASK_BASE self -function TASK_BASE:_EventDead( Event ) - self:F( Event ) - if Event.IniUnit then - local TaskUnit = Event.IniUnit - local TaskUnitName = Event.IniUnitName - - -- Check if for this unit in the task there is a process ongoing. - if self:HasStateMachine( TaskUnitName ) then - self:FailProcesses( TaskUnitName ) - self:UnAssignFromUnit( TaskUnitName ) - end - - local TaskGroup = Event.IniUnit:GetGroup() - TaskGroup:SetState( TaskGroup, "Assigned", nil ) - end - return nil -end - ---- Gets the Scoring of the task --- @param #TASK_BASE self --- @return Scoring#SCORING Scoring -function TASK_BASE:GetScoring() - return self.Mission:GetScoring() -end - - ---- Gets the Task Index, which is a combination of the Task category, the Task type, the Task name. --- @param #TASK_BASE self --- @return #string The Task ID -function TASK_BASE:GetTaskIndex() - - local TaskCategory = self:GetCategory() - local TaskType = self:GetType() - local TaskName = self:GetName() - - return TaskCategory .. "." ..TaskType .. "." .. TaskName -end - ---- Sets the Name of the Task --- @param #TASK_BASE self --- @param #string TaskName -function TASK_BASE:SetName( TaskName ) - self.TaskName = TaskName -end - ---- Gets the Name of the Task --- @param #TASK_BASE self --- @return #string The Task Name -function TASK_BASE:GetName() - return self.TaskName -end - ---- Sets the Type of the Task --- @param #TASK_BASE self --- @param #string TaskType -function TASK_BASE:SetType( TaskType ) - self.TaskType = TaskType -end - ---- Gets the Type of the Task --- @param #TASK_BASE self --- @return #string TaskType -function TASK_BASE:GetType() - return self.TaskType -end - ---- Sets the Category of the Task --- @param #TASK_BASE self --- @param #string TaskCategory -function TASK_BASE:SetCategory( TaskCategory ) - self.TaskCategory = TaskCategory -end - ---- Gets the Category of the Task --- @param #TASK_BASE self --- @return #string TaskCategory -function TASK_BASE:GetCategory() - return self.TaskCategory -end - ---- Sets the ID of the Task --- @param #TASK_BASE self --- @param #string TaskID -function TASK_BASE:SetID( TaskID ) - self.TaskID = TaskID -end - ---- Gets the ID of the Task --- @param #TASK_BASE self --- @return #string TaskID -function TASK_BASE:GetID() - return self.TaskID -end - - ---- Sets a @{Task} to status **Success**. --- @param #TASK_BASE self -function TASK_BASE:StateSuccess() - self:SetState( self, "State", "Success" ) - return self -end - ---- Is the @{Task} status **Success**. --- @param #TASK_BASE self -function TASK_BASE:IsStateSuccess() - return self:GetStateString() == "Success" -end - ---- Sets a @{Task} to status **Failed**. --- @param #TASK_BASE self -function TASK_BASE:StateFailed() - self:SetState( self, "State", "Failed" ) - return self -end - ---- Is the @{Task} status **Failed**. --- @param #TASK_BASE self -function TASK_BASE:IsStateFailed() - return self:GetStateString() == "Failed" -end - ---- Sets a @{Task} to status **Planned**. --- @param #TASK_BASE self -function TASK_BASE:StatePlanned() - self:SetState( self, "State", "Planned" ) - return self -end - ---- Is the @{Task} status **Planned**. --- @param #TASK_BASE self -function TASK_BASE:IsStatePlanned() - return self:GetStateString() == "Planned" -end - ---- Sets a @{Task} to status **Assigned**. --- @param #TASK_BASE self -function TASK_BASE:StateAssigned() - self:SetState( self, "State", "Assigned" ) - return self -end - ---- Is the @{Task} status **Assigned**. --- @param #TASK_BASE self -function TASK_BASE:IsStateAssigned() - return self:GetStateString() == "Assigned" -end - ---- Sets a @{Task} to status **Hold**. --- @param #TASK_BASE self -function TASK_BASE:StateHold() - self:SetState( self, "State", "Hold" ) - return self -end - ---- Is the @{Task} status **Hold**. --- @param #TASK_BASE self -function TASK_BASE:IsStateHold() - return self:GetStateString() == "Hold" -end - ---- Sets a @{Task} to status **Replanned**. --- @param #TASK_BASE self -function TASK_BASE:StateReplanned() - self:SetState( self, "State", "Replanned" ) - return self -end - ---- Is the @{Task} status **Replanned**. --- @param #TASK_BASE self -function TASK_BASE:IsStateReplanned() - return self:GetStateString() == "Replanned" -end - ---- Gets the @{Task} status. --- @param #TASK_BASE self -function TASK_BASE:GetStateString() - return self:GetState( self, "State" ) -end - ---- Sets a @{Task} briefing. --- @param #TASK_BASE self --- @param #string TaskBriefing --- @return #TASK_BASE self -function TASK_BASE:SetBriefing( TaskBriefing ) - self.TaskBriefing = TaskBriefing - return self -end - - - ---- Adds a score for the TASK to be achieved. --- @param #TASK_BASE self --- @param #string TaskStatus is the status of the TASK when the score needs to be given. --- @param #string ScoreText is a text describing the score that is given according the status. --- @param #number Score is a number providing the score of the status. --- @return #TASK_BASE self -function TASK_BASE:AddScore( TaskStatus, ScoreText, Score ) - self:F2( { TaskStatus, ScoreText, Score } ) - - self.Scores[TaskStatus] = self.Scores[TaskStatus] or {} - self.Scores[TaskStatus].ScoreText = ScoreText - self.Scores[TaskStatus].Score = Score - return self -end - ---- StateMachine callback function for a TASK --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @param StateMachine#STATEMACHINE_TASK Fsm --- @param #string Event --- @param #string From --- @param #string To --- @param Event#EVENTDATA Event -function TASK_BASE:OnAssigned( TaskUnit, Fsm, Event, From, To ) - - self:E("Assigned") - - local TaskGroup = TaskUnit:GetGroup() - - TaskGroup:Message( self.TaskBriefing, 20 ) - - self:RemoveMenuForGroup( TaskGroup ) - self:SetAssignedMenuForGroup( TaskGroup ) - -end - - ---- StateMachine callback function for a TASK --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @param StateMachine#STATEMACHINE_TASK Fsm --- @param #string Event --- @param #string From --- @param #string To --- @param Event#EVENTDATA Event -function TASK_BASE:OnSuccess( TaskUnit, Fsm, Event, From, To ) - - self:E("Success") - - self:UnAssignFromGroups() - - local TaskGroup = TaskUnit:GetGroup() - self.Mission:SetPlannedMenu() - - self:StateSuccess() - - -- The task has become successful, the event catchers can be cleaned. - self:CleanUp() - -end - ---- StateMachine callback function for a TASK --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @param StateMachine#STATEMACHINE_TASK Fsm --- @param #string Event --- @param #string From --- @param #string To --- @param Event#EVENTDATA Event -function TASK_BASE:OnFailed( TaskUnit, Fsm, Event, From, To ) - - self:E( { "Failed for unit ", TaskUnit:GetName(), TaskUnit:GetPlayerName() } ) - - -- A task cannot be "failed", so a task will always be there waiting for players to join. - -- When the player leaves its unit, we will need to check whether he was on the ground or not at an airbase. - -- When the player crashes, we will need to check whether in the group there are other players still active. It not, we reset the task from Assigned to Planned, otherwise, we just leave as Assigned. - - self:UnAssignFromGroups() - self:StatePlanned() - -end - ---- StateMachine callback function for a TASK --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @param StateMachine#STATEMACHINE_TASK Fsm --- @param #string Event --- @param #string From --- @param #string To --- @param Event#EVENTDATA Event -function TASK_BASE:OnStateChange( TaskUnit, Fsm, Event, From, To ) - - if self:IsTrace() then - MESSAGE:New( "Task " .. self.TaskName .. " : " .. Event .. " changed to state " .. To, 15 ):ToAll() - end - - self:E( { Event, From, To } ) - self:SetState( self, "State", To ) - - if self.Scores[To] then - local Scoring = self:GetScoring() - if Scoring then - Scoring:_AddMissionScore( self.Mission, self.Scores[To].ScoreText, self.Scores[To].Score ) - end - end - -end - - ---- @param #TASK_BASE self -function TASK_BASE:_Schedule() - self:F2() - - self.TaskScheduler = SCHEDULER:New( self, _Scheduler, {}, 15, 15 ) - return self -end - - ---- @param #TASK_BASE self -function TASK_BASE._Scheduler() - self:F2() - - return true -end - - - - ---- A GOHOMETASK orchestrates the travel back to the home base, which is a specific zone defined within the ME. --- @module GOHOMETASK - ---- The GOHOMETASK class --- @type -GOHOMETASK = { - ClassName = "GOHOMETASK", -} - ---- Creates a new GOHOMETASK. --- @param table{string,...}|string LandingZones Table of Landing Zone names where Home(s) are located. --- @return GOHOMETASK -function GOHOMETASK:New( LandingZones ) - local self = BASE:Inherit( self, TASK:New() ) - self:F( { LandingZones } ) - local Valid = true - - Valid = routines.ValidateZone( LandingZones, "LandingZones", Valid ) - - if Valid then - self.Name = 'Fly Home' - self.TaskBriefing = "Task: Fly back to your home base. Your co-pilot will provide you with the directions (required flight angle in degrees) and the distance (in km) to your home base." - if type( LandingZones ) == "table" then - self.LandingZones = LandingZones - else - self.LandingZones = { LandingZones } - end - self.Stages = { STAGEBRIEF:New(), STAGESTART:New(), STAGEROUTE:New(), STAGEARRIVE:New(), STAGEDONE:New() } - self.SetStage( self, 1 ) - end - - return self -end ---- A DESTROYBASETASK will monitor the destruction of Groups and Units. This is a BASE class, other classes are derived from this class. --- @module DESTROYBASETASK --- @see DESTROYGROUPSTASK --- @see DESTROYUNITTYPESTASK --- @see DESTROY_RADARS_TASK - - - ---- The DESTROYBASETASK class --- @type DESTROYBASETASK -DESTROYBASETASK = { - ClassName = "DESTROYBASETASK", - Destroyed = 0, - GoalVerb = "Destroy", - DestroyPercentage = 100, -} - ---- Creates a new DESTROYBASETASK. --- @param #DESTROYBASETASK self --- @param #string DestroyGroupType Text describing the group to be destroyed. f.e. "Radar Installations", "Ships", "Vehicles", "Command Centers". --- @param #string DestroyUnitType Text describing the unit types to be destroyed. f.e. "SA-6", "Row Boats", "Tanks", "Tents". --- @param #list<#string> DestroyGroupPrefixes Table of Prefixes of the Groups to be destroyed before task is completed. --- @param #number DestroyPercentage defines the %-tage that needs to be destroyed to achieve mission success. eg. If in the Group there are 10 units, then a value of 75 would require 8 units to be destroyed from the Group to complete the @{TASK}. --- @return DESTROYBASETASK -function DESTROYBASETASK:New( DestroyGroupType, DestroyUnitType, DestroyGroupPrefixes, DestroyPercentage ) - local self = BASE:Inherit( self, TASK:New() ) - self:F() - - self.Name = 'Destroy' - self.Destroyed = 0 - self.DestroyGroupPrefixes = DestroyGroupPrefixes - self.DestroyGroupType = DestroyGroupType - self.DestroyUnitType = DestroyUnitType - if DestroyPercentage then - self.DestroyPercentage = DestroyPercentage - end - self.TaskBriefing = "Task: Destroy " .. DestroyGroupType .. "." - self.Stages = { STAGEBRIEF:New(), STAGESTART:New(), STAGEGROUPSDESTROYED:New(), STAGEDONE:New() } - self.SetStage( self, 1 ) - - return self -end - ---- Handle the S_EVENT_DEAD events to validate the destruction of units for the task monitoring. --- @param #DESTROYBASETASK self --- @param Event#EVENTDATA Event structure of MOOSE. -function DESTROYBASETASK:EventDead( Event ) - self:F( { Event } ) - - if Event.IniDCSUnit then - local DestroyUnit = Event.IniDCSUnit - local DestroyUnitName = Event.IniDCSUnitName - local DestroyGroup = Event.IniDCSGroup - local DestroyGroupName = Event.IniDCSGroupName - - --TODO: I need to fix here if 2 groups in the mission have a similar name with GroupPrefix equal, then i should differentiate for which group the goal was reached! - --I may need to test if for the goalverb that group goal was reached or something. Need to think about it a bit more ... - local UnitsDestroyed = 0 - for DestroyGroupPrefixID, DestroyGroupPrefix in pairs( self.DestroyGroupPrefixes ) do - self:T( DestroyGroupPrefix ) - if string.find( DestroyGroupName, DestroyGroupPrefix, 1, true ) then - self:T( BASE:Inherited(self).ClassName ) - UnitsDestroyed = self:ReportGoalProgress( DestroyGroup, DestroyUnit ) - self:T( UnitsDestroyed ) - end - end - - self:T( { UnitsDestroyed } ) - self:IncreaseGoalCount( UnitsDestroyed, self.GoalVerb ) - end - -end - ---- Validate task completeness of DESTROYBASETASK. --- @param DestroyGroup Group structure describing the group to be evaluated. --- @param DestroyUnit Unit structure describing the Unit to be evaluated. -function DESTROYBASETASK:ReportGoalProgress( DestroyGroup, DestroyUnit ) - self:F() - - return 0 -end ---- DESTROYGROUPSTASK --- @module DESTROYGROUPSTASK - - - ---- The DESTROYGROUPSTASK class --- @type -DESTROYGROUPSTASK = { - ClassName = "DESTROYGROUPSTASK", - GoalVerb = "Destroy Groups", -} - ---- Creates a new DESTROYGROUPSTASK. --- @param #DESTROYGROUPSTASK self --- @param #string DestroyGroupType String describing the group to be destroyed. --- @param #string DestroyUnitType String describing the unit to be destroyed. --- @param #list<#string> DestroyGroupNames Table of string containing the name of the groups to be destroyed before task is completed. --- @param #number DestroyPercentage defines the %-tage that needs to be destroyed to achieve mission success. eg. If in the Group there are 10 units, then a value of 75 would require 8 units to be destroyed from the Group to complete the @{TASK}. ----@return DESTROYGROUPSTASK -function DESTROYGROUPSTASK:New( DestroyGroupType, DestroyUnitType, DestroyGroupNames, DestroyPercentage ) - local self = BASE:Inherit( self, DESTROYBASETASK:New( DestroyGroupType, DestroyUnitType, DestroyGroupNames, DestroyPercentage ) ) - self:F() - - self.Name = 'Destroy Groups' - self.GoalVerb = "Destroy " .. DestroyGroupType - - _EVENTDISPATCHER:OnDead( self.EventDead , self ) - _EVENTDISPATCHER:OnCrash( self.EventDead , self ) - - return self -end - ---- Report Goal Progress. --- @param #DESTROYGROUPSTASK self --- @param DCSGroup#Group DestroyGroup Group structure describing the group to be evaluated. --- @param DCSUnit#Unit DestroyUnit Unit structure describing the Unit to be evaluated. --- @return #number The DestroyCount reflecting the amount of units destroyed within the group. -function DESTROYGROUPSTASK:ReportGoalProgress( DestroyGroup, DestroyUnit ) - self:F( { DestroyGroup, DestroyUnit, self.DestroyPercentage } ) - - local DestroyGroupSize = DestroyGroup:getSize() - 1 -- When a DEAD event occurs, the getSize is still one larger than the destroyed unit. - local DestroyGroupInitialSize = DestroyGroup:getInitialSize() - self:T( { DestroyGroupSize, DestroyGroupInitialSize - ( DestroyGroupInitialSize * self.DestroyPercentage / 100 ) } ) - - local DestroyCount = 0 - if DestroyGroup then - if DestroyGroupSize <= DestroyGroupInitialSize - ( DestroyGroupInitialSize * self.DestroyPercentage / 100 ) then - DestroyCount = 1 - end - else - DestroyCount = 1 - end - - self:T( DestroyCount ) - - return DestroyCount -end ---- Task class to destroy radar installations. --- @module DESTROYRADARSTASK - - - ---- The DESTROYRADARS class --- @type -DESTROYRADARSTASK = { - ClassName = "DESTROYRADARSTASK", - GoalVerb = "Destroy Radars" -} - ---- Creates a new DESTROYRADARSTASK. --- @param table{string,...} DestroyGroupNames Table of string containing the group names of which the radars are be destroyed. --- @return DESTROYRADARSTASK -function DESTROYRADARSTASK:New( DestroyGroupNames ) - local self = BASE:Inherit( self, DESTROYGROUPSTASK:New( 'radar installations', 'radars', DestroyGroupNames ) ) - self:F() - - self.Name = 'Destroy Radars' - - _EVENTDISPATCHER:OnDead( self.EventDead , self ) - - return self -end - ---- Report Goal Progress. --- @param Group DestroyGroup Group structure describing the group to be evaluated. --- @param Unit DestroyUnit Unit structure describing the Unit to be evaluated. -function DESTROYRADARSTASK:ReportGoalProgress( DestroyGroup, DestroyUnit ) - self:F( { DestroyGroup, DestroyUnit } ) - - local DestroyCount = 0 - if DestroyUnit and DestroyUnit:hasSensors( Unit.SensorType.RADAR, Unit.RadarType.AS ) then - if DestroyUnit and DestroyUnit:getLife() <= 1.0 then - self:T( 'Destroyed a radar' ) - DestroyCount = 1 - end - end - return DestroyCount -end ---- Set TASK to destroy certain unit types. --- @module DESTROYUNITTYPESTASK - - - ---- The DESTROYUNITTYPESTASK class --- @type -DESTROYUNITTYPESTASK = { - ClassName = "DESTROYUNITTYPESTASK", - GoalVerb = "Destroy", -} - ---- Creates a new DESTROYUNITTYPESTASK. --- @param string DestroyGroupType String describing the group to be destroyed. f.e. "Radar Installations", "Fleet", "Batallion", "Command Centers". --- @param string DestroyUnitType String describing the unit to be destroyed. f.e. "radars", "ships", "tanks", "centers". --- @param table{string,...} DestroyGroupNames Table of string containing the group names of which the radars are be destroyed. --- @param string DestroyUnitTypes Table of string containing the type names of the units to achieve mission success. --- @return DESTROYUNITTYPESTASK -function DESTROYUNITTYPESTASK:New( DestroyGroupType, DestroyUnitType, DestroyGroupNames, DestroyUnitTypes ) - local self = BASE:Inherit( self, DESTROYBASETASK:New( DestroyGroupType, DestroyUnitType, DestroyGroupNames ) ) - self:F( { DestroyGroupType, DestroyUnitType, DestroyGroupNames, DestroyUnitTypes } ) - - if type(DestroyUnitTypes) == 'table' then - self.DestroyUnitTypes = DestroyUnitTypes - else - self.DestroyUnitTypes = { DestroyUnitTypes } - end - - self.Name = 'Destroy Unit Types' - self.GoalVerb = "Destroy " .. DestroyGroupType - - _EVENTDISPATCHER:OnDead( self.EventDead , self ) - - return self -end - ---- Report Goal Progress. --- @param Group DestroyGroup Group structure describing the group to be evaluated. --- @param Unit DestroyUnit Unit structure describing the Unit to be evaluated. -function DESTROYUNITTYPESTASK:ReportGoalProgress( DestroyGroup, DestroyUnit ) - self:F( { DestroyGroup, DestroyUnit } ) - - local DestroyCount = 0 - for UnitTypeID, UnitType in pairs( self.DestroyUnitTypes ) do - if DestroyUnit and DestroyUnit:getTypeName() == UnitType then - if DestroyUnit and DestroyUnit:getLife() <= 1.0 then - DestroyCount = DestroyCount + 1 - end - end - end - return DestroyCount -end ---- A PICKUPTASK orchestrates the loading of CARGO at a specific landing zone. --- @module PICKUPTASK --- @parent TASK - ---- The PICKUPTASK class --- @type -PICKUPTASK = { - ClassName = "PICKUPTASK", - TEXT = { "Pick-Up", "picked-up", "loaded" }, - GoalVerb = "Pick-Up" -} - ---- Creates a new PICKUPTASK. --- @param table{string,...}|string LandingZones Table of Zone names where Cargo is to be loaded. --- @param CARGO_TYPE CargoType Type of the Cargo. The type must be of the following Enumeration:.. --- @param number OnBoardSide Reflects from which side the cargo Group will be on-boarded on the Carrier. -function PICKUPTASK:New( CargoType, OnBoardSide ) - local self = BASE:Inherit( self, TASK:New() ) - self:F() - - -- self holds the inherited instance of the PICKUPTASK Class to the BASE class. - - local Valid = true - - if Valid then - self.Name = 'Pickup Cargo' - self.TaskBriefing = "Task: Fly to the indicated landing zones and pickup " .. CargoType .. ". Your co-pilot will provide you with the directions (required flight angle in degrees) and the distance (in km) to the pickup zone." - self.CargoType = CargoType - self.GoalVerb = CargoType .. " " .. self.GoalVerb - self.OnBoardSide = OnBoardSide - self.IsLandingRequired = true -- required to decide whether the client needs to land or not - self.IsSlingLoad = false -- Indicates whether the cargo is a sling load cargo - self.Stages = { STAGE_CARGO_INIT:New(), STAGE_CARGO_LOAD:New(), STAGEBRIEF:New(), STAGESTART:New(), STAGEROUTE:New(), STAGELANDING:New(), STAGELANDED:New(), STAGELOAD:New(), STAGEDONE:New() } - self.SetStage( self, 1 ) - end - - return self -end - -function PICKUPTASK:FromZone( LandingZone ) - self:F() - - self.LandingZones.LandingZoneNames[LandingZone.CargoZoneName] = LandingZone.CargoZoneName - self.LandingZones.LandingZones[LandingZone.CargoZoneName] = LandingZone - - return self -end - -function PICKUPTASK:InitCargo( InitCargos ) - self:F( { InitCargos } ) - - if type( InitCargos ) == "table" then - self.Cargos.InitCargos = InitCargos - else - self.Cargos.InitCargos = { InitCargos } - end - - return self -end - -function PICKUPTASK:LoadCargo( LoadCargos ) - self:F( { LoadCargos } ) - - if type( LoadCargos ) == "table" then - self.Cargos.LoadCargos = LoadCargos - else - self.Cargos.LoadCargos = { LoadCargos } - end - - return self -end - -function PICKUPTASK:AddCargoMenus( Client, Cargos, TransportRadius ) - self:F() - - for CargoID, Cargo in pairs( Cargos ) do - - self:T( { Cargo.ClassName, Cargo.CargoName, Cargo.CargoType, Cargo:IsStatusNone(), Cargo:IsStatusLoaded(), Cargo:IsStatusLoading(), Cargo:IsStatusUnLoaded() } ) - - -- If the Cargo has no status, allow the menu option. - if Cargo:IsStatusNone() or ( Cargo:IsStatusLoading() and Client == Cargo:IsLoadingToClient() ) then - - local MenuAdd = false - if Cargo:IsNear( Client, self.CurrentCargoZone ) then - MenuAdd = true - end - - if MenuAdd then - if Client._Menus[Cargo.CargoType] == nil then - Client._Menus[Cargo.CargoType] = {} - end - - if not Client._Menus[Cargo.CargoType].PickupMenu then - Client._Menus[Cargo.CargoType].PickupMenu = missionCommands.addSubMenuForGroup( - Client:GetClientGroupID(), - self.TEXT[1] .. " " .. Cargo.CargoType, - nil - ) - self:T( 'Added PickupMenu: ' .. self.TEXT[1] .. " " .. Cargo.CargoType ) - end - - if Client._Menus[Cargo.CargoType].PickupSubMenus == nil then - Client._Menus[Cargo.CargoType].PickupSubMenus = {} - end - - Client._Menus[Cargo.CargoType].PickupSubMenus[ #Client._Menus[Cargo.CargoType].PickupSubMenus + 1 ] = missionCommands.addCommandForGroup( - Client:GetClientGroupID(), - Cargo.CargoName .. " ( " .. Cargo.CargoWeight .. "kg )", - Client._Menus[Cargo.CargoType].PickupMenu, - self.MenuAction, - { ReferenceTask = self, CargoTask = Cargo } - ) - self:T( 'Added PickupSubMenu' .. Cargo.CargoType .. ":" .. Cargo.CargoName .. " ( " .. Cargo.CargoWeight .. "kg )" ) - end - end - end - -end - -function PICKUPTASK:RemoveCargoMenus( Client ) - self:F() - - for MenuID, MenuData in pairs( Client._Menus ) do - for SubMenuID, SubMenuData in pairs( MenuData.PickupSubMenus ) do - missionCommands.removeItemForGroup( Client:GetClientGroupID(), SubMenuData ) - self:T( "Removed PickupSubMenu " ) - SubMenuData = nil - end - if MenuData.PickupMenu then - missionCommands.removeItemForGroup( Client:GetClientGroupID(), MenuData.PickupMenu ) - self:T( "Removed PickupMenu " ) - MenuData.PickupMenu = nil - end - end - - for CargoID, Cargo in pairs( CARGOS ) do - self:T( { Cargo.ClassName, Cargo.CargoName, Cargo.CargoType, Cargo:IsStatusNone(), Cargo:IsStatusLoaded(), Cargo:IsStatusLoading(), Cargo:IsStatusUnLoaded() } ) - if Cargo:IsStatusLoading() and Client == Cargo:IsLoadingToClient() then - Cargo:StatusNone() - end - end - -end - - - -function PICKUPTASK:HasFailed( ClientDead ) - self:F() - - local TaskHasFailed = self.TaskFailed - return TaskHasFailed -end - ---- A DEPLOYTASK orchestrates the deployment of CARGO within a specific landing zone. --- @module DEPLOYTASK - - - ---- A DeployTask --- @type DEPLOYTASK -DEPLOYTASK = { - ClassName = "DEPLOYTASK", - TEXT = { "Deploy", "deployed", "unloaded" }, - GoalVerb = "Deployment" -} - - ---- Creates a new DEPLOYTASK object, which models the sequence of STAGEs to unload a cargo. --- @function [parent=#DEPLOYTASK] New --- @param #string CargoType Type of the Cargo. --- @return #DEPLOYTASK The created DeployTask -function DEPLOYTASK:New( CargoType ) - local self = BASE:Inherit( self, TASK:New() ) - self:F() - - local Valid = true - - if Valid then - self.Name = 'Deploy Cargo' - self.TaskBriefing = "Fly to one of the indicated landing zones and deploy " .. CargoType .. ". Your co-pilot will provide you with the directions (required flight angle in degrees) and the distance (in km) to the deployment zone." - self.CargoType = CargoType - self.GoalVerb = CargoType .. " " .. self.GoalVerb - self.Stages = { STAGE_CARGO_INIT:New(), STAGE_CARGO_LOAD:New(), STAGEBRIEF:New(), STAGESTART:New(), STAGEROUTE:New(), STAGELANDING:New(), STAGELANDED:New(), STAGEUNLOAD:New(), STAGEDONE:New() } - self.SetStage( self, 1 ) - end - - return self -end - -function DEPLOYTASK:ToZone( LandingZone ) - self:F() - - self.LandingZones.LandingZoneNames[LandingZone.CargoZoneName] = LandingZone.CargoZoneName - self.LandingZones.LandingZones[LandingZone.CargoZoneName] = LandingZone - - return self -end - - -function DEPLOYTASK:InitCargo( InitCargos ) - self:F( { InitCargos } ) - - if type( InitCargos ) == "table" then - self.Cargos.InitCargos = InitCargos - else - self.Cargos.InitCargos = { InitCargos } - end - - return self -end - - -function DEPLOYTASK:LoadCargo( LoadCargos ) - self:F( { LoadCargos } ) - - if type( LoadCargos ) == "table" then - self.Cargos.LoadCargos = LoadCargos - else - self.Cargos.LoadCargos = { LoadCargos } - end - - return self -end - - ---- When the cargo is unloaded, it will move to the target zone name. --- @param string TargetZoneName Name of the Zone to where the Cargo should move after unloading. -function DEPLOYTASK:SetCargoTargetZoneName( TargetZoneName ) - self:F() - - local Valid = true - - Valid = routines.ValidateString( TargetZoneName, "TargetZoneName", Valid ) - - if Valid then - self.TargetZoneName = TargetZoneName - end - - return Valid - -end - -function DEPLOYTASK:AddCargoMenus( Client, Cargos, TransportRadius ) - self:F() - - local ClientGroupID = Client:GetClientGroupID() - - self:T( ClientGroupID ) - - for CargoID, Cargo in pairs( Cargos ) do - - self:T( { Cargo.ClassName, Cargo.CargoName, Cargo.CargoType, Cargo.CargoWeight } ) - - if Cargo:IsStatusLoaded() and Client == Cargo:IsLoadedInClient() then - - if Client._Menus[Cargo.CargoType] == nil then - Client._Menus[Cargo.CargoType] = {} - end - - if not Client._Menus[Cargo.CargoType].DeployMenu then - Client._Menus[Cargo.CargoType].DeployMenu = missionCommands.addSubMenuForGroup( - ClientGroupID, - self.TEXT[1] .. " " .. Cargo.CargoType, - nil - ) - self:T( 'Added DeployMenu ' .. self.TEXT[1] ) - end - - if Client._Menus[Cargo.CargoType].DeploySubMenus == nil then - Client._Menus[Cargo.CargoType].DeploySubMenus = {} - end - - if Client._Menus[Cargo.CargoType].DeployMenu == nil then - self:T( 'deploymenu is nil' ) - end - - Client._Menus[Cargo.CargoType].DeploySubMenus[ #Client._Menus[Cargo.CargoType].DeploySubMenus + 1 ] = missionCommands.addCommandForGroup( - ClientGroupID, - Cargo.CargoName .. " ( " .. Cargo.CargoWeight .. "kg )", - Client._Menus[Cargo.CargoType].DeployMenu, - self.MenuAction, - { ReferenceTask = self, CargoTask = Cargo } - ) - self:T( 'Added DeploySubMenu ' .. Cargo.CargoType .. ":" .. Cargo.CargoName .. " ( " .. Cargo.CargoWeight .. "kg )" ) - end - end - -end - -function DEPLOYTASK:RemoveCargoMenus( Client ) - self:F() - - local ClientGroupID = Client:GetClientGroupID() - self:T( ClientGroupID ) - - for MenuID, MenuData in pairs( Client._Menus ) do - if MenuData.DeploySubMenus ~= nil then - for SubMenuID, SubMenuData in pairs( MenuData.DeploySubMenus ) do - missionCommands.removeItemForGroup( ClientGroupID, SubMenuData ) - self:T( "Removed DeploySubMenu " ) - SubMenuData = nil - end - end - if MenuData.DeployMenu then - missionCommands.removeItemForGroup( ClientGroupID, MenuData.DeployMenu ) - self:T( "Removed DeployMenu " ) - MenuData.DeployMenu = nil - end - end - -end ---- A NOTASK is a dummy activity... But it will show a Mission Briefing... --- @module NOTASK - ---- The NOTASK class --- @type -NOTASK = { - ClassName = "NOTASK", -} - ---- Creates a new NOTASK. -function NOTASK:New() - local self = BASE:Inherit( self, TASK:New() ) - self:F() - - local Valid = true - - if Valid then - self.Name = 'Nothing' - self.TaskBriefing = "Task: Execute your mission." - self.Stages = { STAGEBRIEF:New(), STAGESTART:New(), STAGEDONE:New() } - self.SetStage( self, 1 ) - end - - return self -end ---- A ROUTETASK orchestrates the travel to a specific zone defined within the ME. --- @module ROUTETASK - ---- The ROUTETASK class --- @type -ROUTETASK = { - ClassName = "ROUTETASK", - GoalVerb = "Route", -} - ---- Creates a new ROUTETASK. --- @param table{sring,...}|string LandingZones Table of Zone Names where the target is located. --- @param string TaskBriefing (optional) Defines a text describing the briefing of the task. --- @return ROUTETASK -function ROUTETASK:New( LandingZones, TaskBriefing ) - local self = BASE:Inherit( self, TASK:New() ) - self:F( { LandingZones, TaskBriefing } ) - - local Valid = true - - Valid = routines.ValidateZone( LandingZones, "LandingZones", Valid ) - - if Valid then - self.Name = 'Route To Zone' - if TaskBriefing then - self.TaskBriefing = TaskBriefing .. " Your co-pilot will provide you with the directions (required flight angle in degrees) and the distance (in km) to the target objective." - else - self.TaskBriefing = "Task: Fly to specified zone(s). Your co-pilot will provide you with the directions (required flight angle in degrees) and the distance (in km) to the target objective." - end - if type( LandingZones ) == "table" then - self.LandingZones = LandingZones - else - self.LandingZones = { LandingZones } - end - self.Stages = { STAGEBRIEF:New(), STAGESTART:New(), STAGEROUTE:New(), STAGEARRIVE:New(), STAGEDONE:New() } - self.SetStage( self, 1 ) - end - - return self -end - ---- A MISSION is the main owner of a Mission orchestration within MOOSE . The Mission framework orchestrates @{CLIENT}s, @{TASK}s, @{STAGE}s etc. --- A @{CLIENT} needs to be registered within the @{MISSION} through the function @{AddClient}. A @{TASK} needs to be registered within the @{MISSION} through the function @{AddTask}. --- @module Mission - ---- The MISSION class --- @type MISSION --- @extends Base#BASE --- @field #MISSION.Clients _Clients --- @field Menu#MENU_COALITION MissionMenu --- @field #string MissionBriefing -MISSION = { - ClassName = "MISSION", - Name = "", - MissionStatus = "PENDING", - _Clients = {}, - Tasks = {}, - TaskMenus = {}, - TaskCategoryMenus = {}, - TaskTypeMenus = {}, - _ActiveTasks = {}, - GoalFunction = nil, - MissionReportTrigger = 0, - MissionProgressTrigger = 0, - MissionReportShow = false, - MissionReportFlash = false, - MissionTimeInterval = 0, - MissionCoalition = "", - SUCCESS = 1, - FAILED = 2, - REPEAT = 3, - _GoalTasks = {} -} - ---- @type MISSION.Clients --- @list - -function MISSION:Meta() - - local self = BASE:Inherit( self, BASE:New() ) - - return self -end - ---- This is the main MISSION declaration method. Each Mission is like the master or a Mission orchestration between, Clients, Tasks, Stages etc. --- @param #MISSION self --- @param #string MissionName is the name of the mission. This name will be used to reference the status of each mission by the players. --- @param #string MissionPriority is a string indicating the "priority" of the Mission. f.e. "Primary", "Secondary" or "First", "Second". It is free format and up to the Mission designer to choose. There are no rules behind this field. --- @param #string MissionBriefing is a string indicating the mission briefing to be shown when a player joins a @{CLIENT}. --- @param DCSCoalitionObject#coalition MissionCoalition is a string indicating the coalition or party to which this mission belongs to. It is free format and can be chosen freely by the mission designer. Note that this field is not to be confused with the coalition concept of the ME. Examples of a Mission Coalition could be "NATO", "CCCP", "Intruders", "Terrorists"... --- @return #MISSION self -function MISSION:New( MissionName, MissionPriority, MissionBriefing, MissionCoalition ) - - self = MISSION:Meta() - self:T( { MissionName, MissionPriority, MissionBriefing, MissionCoalition } ) - - self.Name = MissionName - self.MissionPriority = MissionPriority - self.MissionBriefing = MissionBriefing - self.MissionCoalition = MissionCoalition - - return self -end - ---- Gets the mission name. --- @param #MISSION self --- @return #MISSION self -function MISSION:GetName() - return self.Name -end - ---- Add a scoring to the mission. --- @param #MISSION self --- @return #MISSION self -function MISSION:AddScoring( Scoring ) - self.Scoring = Scoring - return self -end - ---- Get the scoring object of a mission. --- @param #MISSION self --- @return #SCORING Scoring -function MISSION:GetScoring() - return self.Scoring -end - - ---- Sets the Planned Task menu. --- @param #MISSION self -function MISSION:SetPlannedMenu() - - for _, Task in pairs( self.Tasks ) do - local Task = Task -- Task#TASK_BASE - Task:RemoveMenu() - Task:SetPlannedMenu() - end - -end - ---- Sets the Assigned Task menu. --- @param #MISSION self --- @param Task#TASK_BASE Task --- @param #string MenuText The menu text. --- @return #MISSION self -function MISSION:SetAssignedMenu( Task ) - - for _, Task in pairs( self.Tasks ) do - local Task = Task -- Task#TASK_BASE - Task:RemoveMenu() - Task:SetAssignedMenu() - end - -end - ---- Removes a Task menu. --- @param #MISSION self --- @param Task#TASK_BASE Task --- @return #MISSION self -function MISSION:RemoveTaskMenu( Task ) - - Task:RemoveMenu() -end - - ---- Gets the mission menu for the coalition. --- @param #MISSION self --- @param Group#GROUP TaskGroup --- @return Menu#MENU_COALITION self -function MISSION:GetMissionMenu( TaskGroup ) - local TaskGroupName = TaskGroup:GetName() - return self.MenuMission[TaskGroupName] -end - - ---- Clears the mission menu for the coalition. --- @param #MISSION self --- @return #MISSION self -function MISSION:ClearMissionMenu() - self.MissionMenu:Remove() - self.MissionMenu = nil -end - ---- Get the TASK identified by the TaskNumber from the Mission. This function is useful in GoalFunctions. --- @param #string TaskIndex is the Index of the @{Task} within the @{Mission}. --- @param #number TaskID is the ID of the @{Task} within the @{Mission}. --- @return Task#TASK_BASE The Task --- @return #nil Returns nil if no task was found. -function MISSION:GetTask( TaskName ) - self:F( { TaskName } ) - - return self.Tasks[TaskName] -end - - ---- Register a @{Task} to be completed within the @{Mission}. --- Note that there can be multiple @{Task}s registered to be completed. --- Each Task can be set a certain Goals. The Mission will not be completed until all Goals are reached. --- @param #MISSION self --- @param Task#TASK_BASE Task is the @{Task} object. --- @return Task#TASK_BASE The task added. -function MISSION:AddTask( Task ) - - local TaskName = Task:GetTaskName() - self:F( TaskName ) - self.Tasks[TaskName] = self.Tasks[TaskName] or { n = 0 } - - self.Tasks[TaskName] = Task - - return Task -end - ---- Removes a @{Task} to be completed within the @{Mission}. --- Note that there can be multiple @{Task}s registered to be completed. --- Each Task can be set a certain Goals. The Mission will not be completed until all Goals are reached. --- @param #MISSION self --- @param Task#TASK_BASE Task is the @{Task} object. --- @return #nil The cleaned Task reference. -function MISSION:RemoveTask( Task ) - - local TaskName = Task:GetTaskName() - self:F( TaskName ) - self.Tasks[TaskName] = self.Tasks[TaskName] or { n = 0 } - - Task:CleanUp() -- Cleans all events and sets task to nil to get Garbage Collected - - -- Ensure everything gets garbarge collected. - self.Tasks[TaskName] = nil - Task = nil - - return nil -end - ---- Return the next @{Task} ID to be completed within the @{Mission}. --- @param #MISSION self --- @param Task#TASK_BASE Task is the @{Task} object. --- @return Task#TASK_BASE The task added. -function MISSION:GetNextTaskID( Task ) - - local TaskName = Task:GetTaskName() - self:F( TaskName ) - self.Tasks[TaskName] = self.Tasks[TaskName] or { n = 0 } - - self.Tasks[TaskName].n = self.Tasks[TaskName].n + 1 - - return self.Tasks[TaskName].n -end - - - ---- old stuff - ---- Returns if a Mission has completed. --- @return bool -function MISSION:IsCompleted() - self:F() - return self.MissionStatus == "ACCOMPLISHED" -end - ---- Set a Mission to completed. -function MISSION:Completed() - self:F() - self.MissionStatus = "ACCOMPLISHED" - self:StatusToClients() -end - ---- Returns if a Mission is ongoing. --- treturn bool -function MISSION:IsOngoing() - self:F() - return self.MissionStatus == "ONGOING" -end - ---- Set a Mission to ongoing. -function MISSION:Ongoing() - self:F() - self.MissionStatus = "ONGOING" - --self:StatusToClients() -end - ---- Returns if a Mission is pending. --- treturn bool -function MISSION:IsPending() - self:F() - return self.MissionStatus == "PENDING" -end - ---- Set a Mission to pending. -function MISSION:Pending() - self:F() - self.MissionStatus = "PENDING" - self:StatusToClients() -end - ---- Returns if a Mission has failed. --- treturn bool -function MISSION:IsFailed() - self:F() - return self.MissionStatus == "FAILED" -end - ---- Set a Mission to failed. -function MISSION:Failed() - self:F() - self.MissionStatus = "FAILED" - self:StatusToClients() -end - ---- Send the status of the MISSION to all Clients. -function MISSION:StatusToClients() - self:F() - if self.MissionReportFlash then - for ClientID, Client in pairs( self._Clients ) do - Client:Message( self.MissionCoalition .. ' "' .. self.Name .. '": ' .. self.MissionStatus .. '! ( ' .. self.MissionPriority .. ' mission ) ', 10, "Mission Command: Mission Status") - end - end -end - ---- Handles the reporting. After certain time intervals, a MISSION report MESSAGE will be shown to All Players. -function MISSION:ReportTrigger() - self:F() - - if self.MissionReportShow == true then - self.MissionReportShow = false - return true - else - if self.MissionReportFlash == true then - if timer.getTime() >= self.MissionReportTrigger then - self.MissionReportTrigger = timer.getTime() + self.MissionTimeInterval - return true - else - return false - end - else - return false - end - end -end - ---- Report the status of all MISSIONs to all active Clients. -function MISSION:ReportToAll() - self:F() - - local AlivePlayers = '' - for ClientID, Client in pairs( self._Clients ) do - if Client:GetDCSGroup() then - if Client:GetClientGroupDCSUnit() then - if Client:GetClientGroupDCSUnit():getLife() > 0.0 then - if AlivePlayers == '' then - AlivePlayers = ' Players: ' .. Client:GetClientGroupDCSUnit():getPlayerName() - else - AlivePlayers = AlivePlayers .. ' / ' .. Client:GetClientGroupDCSUnit():getPlayerName() - end - end - end - end - end - local Tasks = self:GetTasks() - local TaskText = "" - for TaskID, TaskData in pairs( Tasks ) do - TaskText = TaskText .. " - Task " .. TaskID .. ": " .. TaskData.Name .. ": " .. TaskData:GetGoalProgress() .. "\n" - end - MESSAGE:New( self.MissionCoalition .. ' "' .. self.Name .. '": ' .. self.MissionStatus .. ' ( ' .. self.MissionPriority .. ' mission )' .. AlivePlayers .. "\n" .. TaskText:gsub("\n$",""), 10, "Mission Command: Mission Report" ):ToAll() -end - - ---- Add a goal function to a MISSION. Goal functions are called when a @{TASK} within a mission has been completed. --- @param function GoalFunction is the function defined by the mission designer to evaluate whether a certain goal has been reached after a @{TASK} finishes within the @{MISSION}. A GoalFunction must accept 2 parameters: Mission, Client, which contains the current MISSION object and the current CLIENT object respectively. --- @usage --- PatriotActivation = { --- { "US SAM Patriot Zerti", false }, --- { "US SAM Patriot Zegduleti", false }, --- { "US SAM Patriot Gvleti", false } --- } --- --- function DeployPatriotTroopsGoal( Mission, Client ) --- --- --- -- Check if the cargo is all deployed for mission success. --- for CargoID, CargoData in pairs( Mission._Cargos ) do --- if Group.getByName( CargoData.CargoGroupName ) then --- CargoGroup = Group.getByName( CargoData.CargoGroupName ) --- if CargoGroup then --- -- Check if the cargo is ready to activate --- CurrentLandingZoneID = routines.IsUnitInZones( CargoGroup:getUnits()[1], Mission:GetTask( 2 ).LandingZones ) -- The second task is the Deploytask to measure mission success upon --- if CurrentLandingZoneID then --- if PatriotActivation[CurrentLandingZoneID][2] == false then --- -- Now check if this is a new Mission Task to be completed... --- trigger.action.setGroupAIOn( Group.getByName( PatriotActivation[CurrentLandingZoneID][1] ) ) --- PatriotActivation[CurrentLandingZoneID][2] = true --- MessageToBlue( "Mission Command: Message to all airborne units! The " .. PatriotActivation[CurrentLandingZoneID][1] .. " is armed. Our air defenses are now stronger.", 60, "BLUE/PatriotDefense" ) --- MessageToRed( "Mission Command: Our satellite systems are detecting additional NATO air defenses. To all airborne units: Take care!!!", 60, "RED/PatriotDefense" ) --- Mission:GetTask( 2 ):AddGoalCompletion( "Patriots activated", PatriotActivation[CurrentLandingZoneID][1], 1 ) -- Register Patriot activation as part of mission goal. --- end --- end --- end --- end --- end --- end --- --- local Mission = MISSIONSCHEDULER.AddMission( 'NATO Transport Troops', 'Operational', 'Transport 3 groups of air defense engineers from our barracks "Gold" and "Titan" to each patriot battery control center to activate our air defenses.', 'NATO' ) --- Mission:AddGoalFunction( DeployPatriotTroopsGoal ) -function MISSION:AddGoalFunction( GoalFunction ) - self:F() - self.GoalFunction = GoalFunction -end - ---- Register a new @{CLIENT} to participate within the mission. --- @param CLIENT Client is the @{CLIENT} object. The object must have been instantiated with @{CLIENT:New}. --- @return CLIENT --- @usage --- Add a number of Client objects to the Mission. --- Mission:AddClient( CLIENT:FindByName( 'US UH-1H*HOT-Deploy Troops 1', 'Transport 3 groups of air defense engineers from our barracks "Gold" and "Titan" to each patriot battery control center to activate our air defenses.' ):Transport() ) --- Mission:AddClient( CLIENT:FindByName( 'US UH-1H*RAMP-Deploy Troops 3', 'Transport 3 groups of air defense engineers from our barracks "Gold" and "Titan" to each patriot battery control center to activate our air defenses.' ):Transport() ) --- Mission:AddClient( CLIENT:FindByName( 'US UH-1H*HOT-Deploy Troops 2', 'Transport 3 groups of air defense engineers from our barracks "Gold" and "Titan" to each patriot battery control center to activate our air defenses.' ):Transport() ) --- Mission:AddClient( CLIENT:FindByName( 'US UH-1H*RAMP-Deploy Troops 4', 'Transport 3 groups of air defense engineers from our barracks "Gold" and "Titan" to each patriot battery control center to activate our air defenses.' ):Transport() ) -function MISSION:AddClient( Client ) - self:F( { Client } ) - - local Valid = true - - if Valid then - self._Clients[Client.ClientName] = Client - end - - return Client -end - ---- Find a @{CLIENT} object within the @{MISSION} by its ClientName. --- @param CLIENT ClientName is a string defining the Client Group as defined within the ME. --- @return CLIENT --- @usage --- -- Seach for Client "Bomber" within the Mission. --- local BomberClient = Mission:FindClient( "Bomber" ) -function MISSION:FindClient( ClientName ) - self:F( { self._Clients[ClientName] } ) - return self._Clients[ClientName] -end - - ---- Get all the TASKs from the Mission. This function is useful in GoalFunctions. --- @return {TASK,...} Structure of TASKS with the @{TASK} number as the key. --- @usage --- -- Get Tasks from the Mission. --- Tasks = Mission:GetTasks() --- env.info( "Task 2 Completion = " .. Tasks[2]:GetGoalPercentage() .. "%" ) -function MISSION:GetTasks() - self:F() - - return self._Tasks -end - - ---[[ - _TransportExecuteStage: Defines the different stages of Transport unload/load execution. This table is internal and is used to control the validity of Transport load/unload timing. - - - _TransportExecuteStage.EXECUTING - - _TransportExecuteStage.SUCCESS - - _TransportExecuteStage.FAILED - ---]] -_TransportExecuteStage = { - NONE = 0, - EXECUTING = 1, - SUCCESS = 2, - FAILED = 3 -} - - ---- The MISSIONSCHEDULER is an OBJECT and is the main scheduler of ALL active MISSIONs registered within this scheduler. It's workings are considered internal and is automatically created when the Mission.lua file is included. --- @type MISSIONSCHEDULER --- @field #MISSIONSCHEDULER.MISSIONS Missions -MISSIONSCHEDULER = { - Missions = {}, - MissionCount = 0, - TimeIntervalCount = 0, - TimeIntervalShow = 150, - TimeSeconds = 14400, - TimeShow = 5 -} - ---- @type MISSIONSCHEDULER.MISSIONS --- @list <#MISSION> Mission - ---- This is the main MISSIONSCHEDULER Scheduler function. It is considered internal and is automatically created when the Mission.lua file is included. -function MISSIONSCHEDULER.Scheduler() - - - -- loop through the missions in the TransportTasks - for MissionName, MissionData in pairs( MISSIONSCHEDULER.Missions ) do - - local Mission = MissionData -- #MISSION - - if not Mission:IsCompleted() then - - -- This flag will monitor if for this mission, there are clients alive. If this flag is still false at the end of the loop, the mission status will be set to Pending (if not Failed or Completed). - local ClientsAlive = false - - for ClientID, ClientData in pairs( Mission._Clients ) do - - local Client = ClientData -- Client#CLIENT - - if Client:IsAlive() then - - -- There is at least one Client that is alive... So the Mission status is set to Ongoing. - ClientsAlive = true - - -- If this Client was not registered as Alive before: - -- 1. We register the Client as Alive. - -- 2. We initialize the Client Tasks and make a link to the original Mission Task. - -- 3. We initialize the Cargos. - -- 4. We flag the Mission as Ongoing. - if not Client.ClientAlive then - Client.ClientAlive = true - Client.ClientBriefingShown = false - for TaskNumber, Task in pairs( Mission._Tasks ) do - -- Note that this a deepCopy. Each client must have their own Tasks with own Stages!!! - Client._Tasks[TaskNumber] = routines.utils.deepCopy( Mission._Tasks[TaskNumber] ) - -- Each MissionTask must point to the original Mission. - Client._Tasks[TaskNumber].MissionTask = Mission._Tasks[TaskNumber] - Client._Tasks[TaskNumber].Cargos = Mission._Tasks[TaskNumber].Cargos - Client._Tasks[TaskNumber].LandingZones = Mission._Tasks[TaskNumber].LandingZones - end - - Mission:Ongoing() - end - - - -- For each Client, check for each Task the state and evolve the mission. - -- This flag will indicate if the Task of the Client is Complete. - local TaskComplete = false - - for TaskNumber, Task in pairs( Client._Tasks ) do - - if not Task.Stage then - Task:SetStage( 1 ) - end - - - local TransportTime = timer.getTime() - - if not Task:IsDone() then - - if Task:Goal() then - Task:ShowGoalProgress( Mission, Client ) - end - - --env.info( 'Scheduler: Mission = ' .. Mission.Name .. ' / Client = ' .. Client.ClientName .. ' / Task = ' .. Task.Name .. ' / Stage = ' .. Task.ActiveStage .. ' - ' .. Task.Stage.Name .. ' - ' .. Task.Stage.StageType ) - - -- Action - if Task:StageExecute() then - Task.Stage:Execute( Mission, Client, Task ) - end - - -- Wait until execution is finished - if Task.ExecuteStage == _TransportExecuteStage.EXECUTING then - Task.Stage:Executing( Mission, Client, Task ) - end - - -- Validate completion or reverse to earlier stage - if Task.Time + Task.Stage.WaitTime <= TransportTime then - Task:SetStage( Task.Stage:Validate( Mission, Client, Task ) ) - end - - if Task:IsDone() then - --env.info( 'Scheduler: Mission '.. Mission.Name .. ' Task ' .. Task.Name .. ' Stage ' .. Task.Stage.Name .. ' done. TaskComplete = ' .. string.format ( "%s", TaskComplete and "true" or "false" ) ) - TaskComplete = true -- when a task is not yet completed, a mission cannot be completed - - else - -- break only if this task is not yet done, so that future task are not yet activated. - TaskComplete = false -- when a task is not yet completed, a mission cannot be completed - --env.info( 'Scheduler: Mission "'.. Mission.Name .. '" Task "' .. Task.Name .. '" Stage "' .. Task.Stage.Name .. '" break. TaskComplete = ' .. string.format ( "%s", TaskComplete and "true" or "false" ) ) - break - end - - if TaskComplete then - - if Mission.GoalFunction ~= nil then - Mission.GoalFunction( Mission, Client ) - end - if MISSIONSCHEDULER.Scoring then - MISSIONSCHEDULER.Scoring:_AddMissionTaskScore( Client:GetClientGroupDCSUnit(), Mission.Name, 25 ) - end - --- if not Mission:IsCompleted() then --- end - end - end - end - - local MissionComplete = true - for TaskNumber, Task in pairs( Mission._Tasks ) do - if Task:Goal() then --- Task:ShowGoalProgress( Mission, Client ) - if Task:IsGoalReached() then - else - MissionComplete = false - end - else - MissionComplete = false -- If there is no goal, the mission should never be ended. The goal status will be set somewhere else. - end - end - - if MissionComplete then - Mission:Completed() - if MISSIONSCHEDULER.Scoring then - MISSIONSCHEDULER.Scoring:_AddMissionScore( Mission.Name, 100 ) - end - else - if TaskComplete then - -- Reset for new tasking of active client - Client.ClientAlive = false -- Reset the client tasks. - end - end - - - else - if Client.ClientAlive then - env.info( 'Scheduler: Client "' .. Client.ClientName .. '" is inactive.' ) - Client.ClientAlive = false - - -- This is tricky. If we sanitize Client._Tasks before sanitizing Client._Tasks[TaskNumber].MissionTask, then the original MissionTask will be sanitized, and will be lost within the garbage collector. - -- So first sanitize Client._Tasks[TaskNumber].MissionTask, after that, sanitize only the whole _Tasks structure... - --Client._Tasks[TaskNumber].MissionTask = nil - --Client._Tasks = nil - end - end - end - - -- If all Clients of this Mission are not activated, then the Mission status needs to be put back into Pending status. - -- But only if the Mission was Ongoing. In case the Mission is Completed or Failed, the Mission status may not be changed. In these cases, this will be the last run of this Mission in the Scheduler. - if ClientsAlive == false then - if Mission:IsOngoing() then - -- Mission status back to pending... - Mission:Pending() - end - end - end - - Mission:StatusToClients() - - if Mission:ReportTrigger() then - Mission:ReportToAll() - end - end - - return true -end - ---- Start the MISSIONSCHEDULER. -function MISSIONSCHEDULER.Start() - if MISSIONSCHEDULER ~= nil then - --MISSIONSCHEDULER.SchedulerId = routines.scheduleFunction( MISSIONSCHEDULER.Scheduler, { }, 0, 2 ) - MISSIONSCHEDULER.SchedulerId = SCHEDULER:New( nil, MISSIONSCHEDULER.Scheduler, { }, 0, 2 ) - end -end - ---- Stop the MISSIONSCHEDULER. -function MISSIONSCHEDULER.Stop() - if MISSIONSCHEDULER.SchedulerId then - routines.removeFunction(MISSIONSCHEDULER.SchedulerId) - MISSIONSCHEDULER.SchedulerId = nil - end -end - ---- This is the main MISSION declaration method. Each Mission is like the master or a Mission orchestration between, Clients, Tasks, Stages etc. --- @param Mission is the MISSION object instantiated by @{MISSION:New}. --- @return MISSION --- @usage --- -- Declare a mission. --- Mission = MISSION:New( 'Russia Transport Troops SA-6', --- 'Operational', --- 'Transport troops from the control center to one of the SA-6 SAM sites to activate their operation.', --- 'Russia' ) --- MISSIONSCHEDULER:AddMission( Mission ) -function MISSIONSCHEDULER.AddMission( Mission ) - MISSIONSCHEDULER.Missions[Mission.Name] = Mission - MISSIONSCHEDULER.MissionCount = MISSIONSCHEDULER.MissionCount + 1 - -- Add an overall AI Client for the AI tasks... This AI Client will facilitate the Events in the background for each Task. - --MissionAdd:AddClient( CLIENT:Register( 'AI' ) ) - - return Mission -end - ---- Remove a MISSION from the MISSIONSCHEDULER. --- @param MissionName is the name of the MISSION given at declaration using @{AddMission}. --- @usage --- -- Declare a mission. --- Mission = MISSION:New( 'Russia Transport Troops SA-6', --- 'Operational', --- 'Transport troops from the control center to one of the SA-6 SAM sites to activate their operation.', --- 'Russia' ) --- MISSIONSCHEDULER:AddMission( Mission ) --- --- -- Now remove the Mission. --- MISSIONSCHEDULER:RemoveMission( 'Russia Transport Troops SA-6' ) -function MISSIONSCHEDULER.RemoveMission( MissionName ) - MISSIONSCHEDULER.Missions[MissionName] = nil - MISSIONSCHEDULER.MissionCount = MISSIONSCHEDULER.MissionCount - 1 -end - ---- Find a MISSION within the MISSIONSCHEDULER. --- @param MissionName is the name of the MISSION given at declaration using @{AddMission}. --- @return MISSION --- @usage --- -- Declare a mission. --- Mission = MISSION:New( 'Russia Transport Troops SA-6', --- 'Operational', --- 'Transport troops from the control center to one of the SA-6 SAM sites to activate their operation.', --- 'Russia' ) --- MISSIONSCHEDULER:AddMission( Mission ) --- --- -- Now find the Mission. --- MissionFind = MISSIONSCHEDULER:FindMission( 'Russia Transport Troops SA-6' ) -function MISSIONSCHEDULER.FindMission( MissionName ) - return MISSIONSCHEDULER.Missions[MissionName] -end - --- Internal function used by the MISSIONSCHEDULER menu. -function MISSIONSCHEDULER.ReportMissionsShow( ) - for MissionName, Mission in pairs( MISSIONSCHEDULER.Missions ) do - Mission.MissionReportShow = true - Mission.MissionReportFlash = false - end -end - --- Internal function used by the MISSIONSCHEDULER menu. -function MISSIONSCHEDULER.ReportMissionsFlash( TimeInterval ) - local Count = 0 - for MissionName, Mission in pairs( MISSIONSCHEDULER.Missions ) do - Mission.MissionReportShow = false - Mission.MissionReportFlash = true - Mission.MissionReportTrigger = timer.getTime() + Count * TimeInterval - Mission.MissionTimeInterval = MISSIONSCHEDULER.MissionCount * TimeInterval - env.info( "TimeInterval = " .. Mission.MissionTimeInterval ) - Count = Count + 1 - end -end - --- Internal function used by the MISSIONSCHEDULER menu. -function MISSIONSCHEDULER.ReportMissionsHide( Prm ) - for MissionName, Mission in pairs( MISSIONSCHEDULER.Missions ) do - Mission.MissionReportShow = false - Mission.MissionReportFlash = false - end -end - ---- Enables a MENU option in the communications menu under F10 to control the status of the active missions. --- This function should be called only once when starting the MISSIONSCHEDULER. -function MISSIONSCHEDULER.ReportMenu() - local ReportMenu = SUBMENU:New( 'Status' ) - local ReportMenuShow = COMMANDMENU:New( 'Show Report Missions', ReportMenu, MISSIONSCHEDULER.ReportMissionsShow, 0 ) - local ReportMenuFlash = COMMANDMENU:New('Flash Report Missions', ReportMenu, MISSIONSCHEDULER.ReportMissionsFlash, 120 ) - local ReportMenuHide = COMMANDMENU:New( 'Hide Report Missions', ReportMenu, MISSIONSCHEDULER.ReportMissionsHide, 0 ) -end - ---- Show the remaining mission time. -function MISSIONSCHEDULER:TimeShow() - self.TimeIntervalCount = self.TimeIntervalCount + 1 - if self.TimeIntervalCount >= self.TimeTriggerShow then - local TimeMsg = string.format("%00d", ( self.TimeSeconds / 60 ) - ( timer.getTime() / 60 )) .. ' minutes left until mission reload.' - MESSAGE:New( TimeMsg, self.TimeShow, "Mission time" ):ToAll() - self.TimeIntervalCount = 0 - end -end - -function MISSIONSCHEDULER:Time( TimeSeconds, TimeIntervalShow, TimeShow ) - - self.TimeIntervalCount = 0 - self.TimeSeconds = TimeSeconds - self.TimeIntervalShow = TimeIntervalShow - self.TimeShow = TimeShow -end - ---- Adds a mission scoring to the game. -function MISSIONSCHEDULER:Scoring( Scoring ) - - self.Scoring = Scoring -end - --- The CLEANUP class keeps an area clean of crashing or colliding airplanes. It also prevents airplanes from firing within this area. -- @module CleanUp -- @author Flightcontrol @@ -20572,7 +17417,7 @@ end --- The CLEANUP class. -- @type CLEANUP --- @extends Base#BASE +-- @extends Core.Base#BASE CLEANUP = { ClassName = "CLEANUP", ZoneNames = {}, @@ -20613,7 +17458,7 @@ end --- Destroys a group from the simulator, but checks first if it is still existing! -- @param #CLEANUP self --- @param DCSGroup#Group GroupObject The object to be destroyed. +-- @param Dcs.DCSWrapper.Group#Group GroupObject The object to be destroyed. -- @param #string CleanUpGroupName The groupname... function CLEANUP:_DestroyGroup( GroupObject, CleanUpGroupName ) self:F( { GroupObject, CleanUpGroupName } ) @@ -20624,9 +17469,9 @@ function CLEANUP:_DestroyGroup( GroupObject, CleanUpGroupName ) end end ---- Destroys a @{DCSUnit#Unit} from the simulator, but checks first if it is still existing! +--- Destroys a @{Dcs.DCSWrapper.Unit#Unit} from the simulator, but checks first if it is still existing! -- @param #CLEANUP self --- @param DCSUnit#Unit CleanUpUnit The object to be destroyed. +-- @param Dcs.DCSWrapper.Unit#Unit CleanUpUnit The object to be destroyed. -- @param #string CleanUpUnitName The Unit name ... function CLEANUP:_DestroyUnit( CleanUpUnit, CleanUpUnitName ) self:F( { CleanUpUnit, CleanUpUnitName } ) @@ -20651,10 +17496,10 @@ function CLEANUP:_DestroyUnit( CleanUpUnit, CleanUpUnitName ) end end --- TODO check DCSTypes#Weapon +-- TODO check Dcs.DCSTypes#Weapon --- Destroys a missile from the simulator, but checks first if it is still existing! -- @param #CLEANUP self --- @param DCSTypes#Weapon MissileObject +-- @param Dcs.DCSTypes#Weapon MissileObject function CLEANUP:_DestroyMissile( MissileObject ) self:F( { MissileObject } ) @@ -20696,7 +17541,7 @@ end --- Detects if a crash event occurs. -- Crashed units go into a CleanUpList for removal. -- @param #CLEANUP self --- @param DCSTypes#Event event +-- @param Dcs.DCSTypes#Event event function CLEANUP:_EventCrash( Event ) self:F( { Event } ) @@ -20719,7 +17564,7 @@ end --- Detects if a unit shoots a missile. -- If this occurs within one of the zones, then the weapon used must be destroyed. -- @param #CLEANUP self --- @param DCSTypes#Event event +-- @param Dcs.DCSTypes#Event event function CLEANUP:_EventShot( Event ) self:F( { Event } ) @@ -20736,7 +17581,7 @@ end --- Detects if the Unit has an S_EVENT_HIT within the given ZoneNames. If this is the case, destroy the unit. -- @param #CLEANUP self --- @param DCSTypes#Event event +-- @param Dcs.DCSTypes#Event event function CLEANUP:_EventHitCleanUp( Event ) self:F( { Event } ) @@ -20761,7 +17606,7 @@ function CLEANUP:_EventHitCleanUp( Event ) end end ---- Add the @{DCSUnit#Unit} to the CleanUpList for CleanUp. +--- Add the @{Dcs.DCSWrapper.Unit#Unit} to the CleanUpList for CleanUp. function CLEANUP:_AddForCleanUp( CleanUpUnit, CleanUpUnitName ) self:F( { CleanUpUnit, CleanUpUnitName } ) @@ -20779,7 +17624,7 @@ end --- Detects if the Unit has an S_EVENT_ENGINE_SHUTDOWN or an S_EVENT_HIT within the given ZoneNames. If this is the case, add the Group to the CLEANUP List. -- @param #CLEANUP self --- @param DCSTypes#Event event +-- @param Dcs.DCSTypes#Event event function CLEANUP:_EventAddForCleanUp( Event ) if Event.IniDCSUnit then @@ -20882,7 +17727,7 @@ end --- This module contains the SPAWN class. -- --- 1) @{Spawn#SPAWN} class, extends @{Base#BASE} +-- 1) @{Functional.Spawn#SPAWN} class, extends @{Core.Base#BASE} -- ============================================= -- The @{#SPAWN} class allows to spawn dynamically new groups, based on pre-defined initialization settings, modifying the behaviour when groups are spawned. -- For each group to be spawned, within the mission editor, a group has to be created with the "late activation flag" set. We call this group the *"Spawn Template"* of the SPAWN object. @@ -20908,9 +17753,10 @@ end -- -- 1.1) SPAWN construction methods -- ------------------------------- --- Create a new SPAWN object with the @{#SPAWN.New} or the @{#SPAWN.NewWithAlias} methods: +-- Create a new SPAWN object with the @{#SPAWN.New}() or the @{#SPAWN.NewWithAlias}() methods: -- --- * @{#SPAWN.New}: Creates a new SPAWN object taking the name of the group that represents the GROUP Template (definition). +-- * @{#SPAWN.New}(): Creates a new SPAWN object taking the name of the group that represents the GROUP Template (definition). +-- * @{#SPAWN.NewWithAlias}(): Creates a new SPAWN object taking the name of the group that represents the GROUP Template (definition), and gives each spawned @{Group} an different name. -- -- It is important to understand how the SPAWN class works internally. The SPAWN object created will contain internally a list of groups that will be spawned and that are already spawned. -- The initialization methods will modify this list of groups so that when a group gets spawned, ALL information is already prepared when spawning. This is done for performance reasons. @@ -20918,27 +17764,29 @@ end -- -- 1.2) SPAWN initialization methods -- --------------------------------- --- A spawn object will behave differently based on the usage of initialization methods: +-- A spawn object will behave differently based on the usage of **initialization** methods, which all start with the **Init** prefix: -- --- * @{#SPAWN.Limit}: Limits the amount of groups that can be alive at the same time and that can be dynamically spawned. --- * @{#SPAWN.RandomizeRoute}: Randomize the routes of spawned groups, and for air groups also optionally the height. --- * @{#SPAWN.RandomizeTemplate}: Randomize the group templates so that when a new group is spawned, a random group template is selected from one of the templates defined. --- * @{#SPAWN.Uncontrolled}: Spawn plane groups uncontrolled. --- * @{#SPAWN.Array}: Make groups visible before they are actually activated, and order these groups like a batallion in an array. --- * @{#SPAWN.InitRepeat}: Re-spawn groups when they land at the home base. Similar methods are @{#SPAWN.InitRepeatOnLanding} and @{#SPAWN.InitRepeatOnEngineShutDown}. +-- * @{#SPAWN.InitLimit}(): Limits the amount of groups that can be alive at the same time and that can be dynamically spawned. +-- * @{#SPAWN.InitRandomizeRoute}(): Randomize the routes of spawned groups, and for air groups also optionally the height. +-- * @{#SPAWN.InitRandomizeTemplate}(): Randomize the group templates so that when a new group is spawned, a random group template is selected from one of the templates defined. +-- * @{#SPAWN.InitUncontrolled}(): Spawn plane groups uncontrolled. +-- * @{#SPAWN.InitArray}(): Make groups visible before they are actually activated, and order these groups like a batallion in an array. +-- * @{#SPAWN.InitRepeat}(): Re-spawn groups when they land at the home base. Similar methods are @{#SPAWN.InitRepeatOnLanding} and @{#SPAWN.InitRepeatOnEngineShutDown}. +-- * @{#SPAWN.InitRandomizeUnits}(): Randomizes the @{Unit}s in the @{Group} that is spawned within a **radius band**, given an Outer and Inner radius. +-- * @{#SPAWN.InitRandomizeZones}(): Randomizes the spawning between a predefined list of @{Zone}s that are declared using this function. Each zone can be given a probability factor. -- -- 1.3) SPAWN spawning methods -- --------------------------- -- Groups can be spawned at different times and methods: -- --- * @{#SPAWN.Spawn}: Spawn one new group based on the last spawned index. --- * @{#SPAWN.ReSpawn}: Re-spawn a group based on a given index. --- * @{#SPAWN.SpawnScheduled}: Spawn groups at scheduled but randomized intervals. You can use @{#SPAWN.SpawnScheduleStart} and @{#SPAWN.SpawnScheduleStop} to start and stop the schedule respectively. --- * @{#SPAWN.SpawnFromVec3}: Spawn a new group from a Vec3 coordinate. (The group will can be spawned at a point in the air). --- * @{#SPAWN.SpawnFromVec2}: Spawn a new group from a Vec2 coordinate. (The group will be spawned at land height ). --- * @{#SPAWN.SpawnFromStatic}: Spawn a new group from a structure, taking the position of a @{STATIC}. --- * @{#SPAWN.SpawnFromUnit}: Spawn a new group taking the position of a @{UNIT}. --- * @{#SPAWN.SpawnInZone}: Spawn a new group in a @{ZONE}. +-- * @{#SPAWN.Spawn}(): Spawn one new group based on the last spawned index. +-- * @{#SPAWN.ReSpawn}(): Re-spawn a group based on a given index. +-- * @{#SPAWN.SpawnScheduled}(): Spawn groups at scheduled but randomized intervals. You can use @{#SPAWN.SpawnScheduleStart}() and @{#SPAWN.SpawnScheduleStop}() to start and stop the schedule respectively. +-- * @{#SPAWN.SpawnFromVec3}(): Spawn a new group from a Vec3 coordinate. (The group will can be spawned at a point in the air). +-- * @{#SPAWN.SpawnFromVec2}(): Spawn a new group from a Vec2 coordinate. (The group will be spawned at land height ). +-- * @{#SPAWN.SpawnFromStatic}(): Spawn a new group from a structure, taking the position of a @{Static}. +-- * @{#SPAWN.SpawnFromUnit}(): Spawn a new group taking the position of a @{Unit}. +-- * @{#SPAWN.SpawnInZone}(): Spawn a new group in a @{Zone}. -- -- Note that @{#SPAWN.Spawn} and @{#SPAWN.ReSpawn} return a @{GROUP#GROUP.New} object, that contains a reference to the DCSGroup object. -- You can use the @{GROUP} object to do further actions with the DCSGroup. @@ -20949,32 +17797,132 @@ end -- Every time a SPAWN object spawns a new GROUP object, a reference to the GROUP object is added to an internal table of GROUPS. -- SPAWN provides methods to iterate through that internal GROUP object reference table: -- --- * @{#SPAWN.GetFirstAliveGroup}: Will find the first alive GROUP it has spawned, and return the alive GROUP object and the first Index where the first alive GROUP object has been found. --- * @{#SPAWN.GetNextAliveGroup}: Will find the next alive GROUP object from a given Index, and return a reference to the alive GROUP object and the next Index where the alive GROUP has been found. --- * @{#SPAWN.GetLastAliveGroup}: Will find the last alive GROUP object, and will return a reference to the last live GROUP object and the last Index where the last alive GROUP object has been found. +-- * @{#SPAWN.GetFirstAliveGroup}(): Will find the first alive GROUP it has spawned, and return the alive GROUP object and the first Index where the first alive GROUP object has been found. +-- * @{#SPAWN.GetNextAliveGroup}(): Will find the next alive GROUP object from a given Index, and return a reference to the alive GROUP object and the next Index where the alive GROUP has been found. +-- * @{#SPAWN.GetLastAliveGroup}(): Will find the last alive GROUP object, and will return a reference to the last live GROUP object and the last Index where the last alive GROUP object has been found. -- --- You can use the methods @{#SPAWN.GetFirstAliveGroup} and sequently @{#SPAWN.GetNextAliveGroup} to iterate through the alive GROUPS within the SPAWN object, and to actions... See the respective methods for an example. --- The method @{#SPAWN.GetGroupFromIndex} will return the GROUP object reference from the given Index, dead or alive... +-- You can use the methods @{#SPAWN.GetFirstAliveGroup}() and sequently @{#SPAWN.GetNextAliveGroup}() to iterate through the alive GROUPS within the SPAWN object, and to actions... See the respective methods for an example. +-- The method @{#SPAWN.GetGroupFromIndex}() will return the GROUP object reference from the given Index, dead or alive... -- -- 1.5) SPAWN object cleaning -- -------------------------- -- Sometimes, it will occur during a mission run-time, that ground or especially air objects get damaged, and will while being damged stop their activities, while remaining alive. -- In such cases, the SPAWN object will just sit there and wait until that group gets destroyed, but most of the time it won't, -- and it may occur that no new groups are or can be spawned as limits are reached. --- To prevent this, a @{#SPAWN.CleanUp} initialization method has been defined that will silently monitor the status of each spawned group. +-- To prevent this, a @{#SPAWN.InitCleanUp}() initialization method has been defined that will silently monitor the status of each spawned group. -- Once a group has a velocity = 0, and has been waiting for a defined interval, that group will be cleaned or removed from run-time. -- There is a catch however :-) If a damaged group has returned to an airbase within the coalition, that group will not be considered as "lost"... -- In such a case, when the inactive group is cleaned, a new group will Re-spawned automatically. -- This models AI that has succesfully returned to their airbase, to restart their combat activities. --- Check the @{#SPAWN.CleanUp} for further info. +-- Check the @{#SPAWN.InitCleanUp}() for further info. +-- +-- 1.6) Catch the @{Group} spawn event in a callback function! +-- ----------------------------------------------------------- +-- When using the SpawnScheduled method, new @{Group}s are created following the schedule timing parameters. +-- When a new @{Group} is spawned, you maybe want to execute actions with that group spawned at the spawn event. +-- To SPAWN class supports this functionality through the @{#SPAWN.OnSpawnGroup}( **function( SpawnedGroup ) end ** ) method, which takes a function as a parameter that you can define locally. +-- Whenever a new @{Group} is spawned, the given function is called, and the @{Group} that was just spawned, is given as a parameter. +-- As a result, your spawn event handling function requires one parameter to be declared, which will contain the spawned @{Group} object. +-- A coding example is provided at the description of the @{#SPAWN.OnSpawnGroup}( **function( SpawnedGroup ) end ** ) method. +-- +-- ==== +-- +-- **API CHANGE HISTORY** +-- ====================== +-- +-- The underlying change log documents the API changes. Please read this carefully. The following notation is used: +-- +-- * **Added** parts are expressed in bold type face. +-- * _Removed_ parts are expressed in italic type face. +-- +-- Hereby the change log: +-- +-- 2016-08-15: SPAWN:**InitCleanUp**( SpawnCleanUpInterval ) replaces SPAWN:_CleanUp_( SpawnCleanUpInterval ) +-- +-- * Want to ensure that the methods starting with **Init** are the first called methods before any _Spawn_ method is called! +-- * This notation makes it now more clear which methods are initialization methods and which methods are Spawn enablement methods. +-- +-- 2016-08-15: SPAWN:**InitRandomizeZones( SpawnZones )** added. +-- +-- * This method provides the functionality to randomize the spawning of the Groups at a given list of zones of different types. +-- +-- 2016-08-14: SPAWN:**OnSpawnGroup**( SpawnCallBackFunction, ... ) replaces SPAWN:_SpawnFunction_( SpawnCallBackFunction, ... ). +-- +-- 2016-08-14: SPAWN.SpawnInZone( Zone, __RandomizeGroup__, SpawnIndex ) replaces SpawnInZone( Zone, _RandomizeUnits, OuterRadius, InnerRadius,_ SpawnIndex ). +-- +-- * The RandomizeUnits, OuterRadius and InnerRadius have been replaced with a new method @{#SPAWN.InitRandomizeUnits}( RandomizeUnits, OuterRadius, InnerRadius ). +-- * A new parameter RandomizeGroup to reflect the randomization of the starting position of the Spawned @{Group}. +-- +-- 2016-08-14: SPAWN.SpawnFromVec3( Vec3, SpawnIndex ) replaces SpawnFromVec3( Vec3, _RandomizeUnits, OuterRadius, InnerRadius,_ SpawnIndex ): +-- +-- * The RandomizeUnits, OuterRadius and InnerRadius have been replaced with a new method @{#SPAWN.InitRandomizeUnits}( RandomizeUnits, OuterRadius, InnerRadius ). +-- * A new parameter RandomizeGroup to reflect the randomization of the starting position of the Spawned @{Group}. +-- +-- 2016-08-14: SPAWN.SpawnFromVec2( Vec2, SpawnIndex ) replaces SpawnFromVec2( Vec2, _RandomizeUnits, OuterRadius, InnerRadius,_ SpawnIndex ): +-- +-- * The RandomizeUnits, OuterRadius and InnerRadius have been replaced with a new method @{#SPAWN.InitRandomizeUnits}( RandomizeUnits, OuterRadius, InnerRadius ). +-- * A new parameter RandomizeGroup to reflect the randomization of the starting position of the Spawned @{Group}. +-- +-- 2016-08-14: SPAWN.SpawnFromUnit( SpawnUnit, SpawnIndex ) replaces SpawnFromUnit( SpawnUnit, _RandomizeUnits, OuterRadius, InnerRadius,_ SpawnIndex ): +-- +-- * The RandomizeUnits, OuterRadius and InnerRadius have been replaced with a new method @{#SPAWN.InitRandomizeUnits}( RandomizeUnits, OuterRadius, InnerRadius ). +-- * A new parameter RandomizeGroup to reflect the randomization of the starting position of the Spawned @{Group}. +-- +-- 2016-08-14: SPAWN.SpawnFromUnit( SpawnUnit, SpawnIndex ) replaces SpawnFromStatic( SpawnStatic, _RandomizeUnits, OuterRadius, InnerRadius,_ SpawnIndex ): +-- +-- * The RandomizeUnits, OuterRadius and InnerRadius have been replaced with a new method @{#SPAWN.InitRandomizeUnits}( RandomizeUnits, OuterRadius, InnerRadius ). +-- * A new parameter RandomizeGroup to reflect the randomization of the starting position of the Spawned @{Group}. +-- +-- 2016-08-14: SPAWN.**InitRandomizeUnits( RandomizeUnits, OuterRadius, InnerRadius )** added: +-- +-- * This method enables the randomization of units at the first route point in a radius band at a spawn event. +-- +-- 2016-08-14: SPAWN.**Init**Limit( SpawnMaxUnitsAlive, SpawnMaxGroups ) replaces SPAWN._Limit_( SpawnMaxUnitsAlive, SpawnMaxGroups ): +-- +-- * Want to ensure that the methods starting with **Init** are the first called methods before any _Spawn_ method is called! +-- * This notation makes it now more clear which methods are initialization methods and which methods are Spawn enablement methods. +-- +-- 2016-08-14: SPAWN.**Init**Array( SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY ) replaces SPAWN._Array_( SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY ). +-- +-- * Want to ensure that the methods starting with **Init** are the first called methods before any _Spawn_ method is called! +-- * This notation makes it now more clear which methods are initialization methods and which methods are Spawn enablement methods. +-- +-- 2016-08-14: SPAWN.**Init**RandomizeRoute( SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight ) replaces SPAWN._RandomizeRoute_( SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight ). +-- +-- * Want to ensure that the methods starting with **Init** are the first called methods before any _Spawn_ method is called! +-- * This notation makes it now more clear which methods are initialization methods and which methods are Spawn enablement methods. +-- +-- 2016-08-14: SPAWN.**Init**RandomizeTemplate( SpawnTemplatePrefixTable ) replaces SPAWN._RandomizeTemplate_( SpawnTemplatePrefixTable ). +-- +-- * Want to ensure that the methods starting with **Init** are the first called methods before any _Spawn_ method is called! +-- * This notation makes it now more clear which methods are initialization methods and which methods are Spawn enablement methods. +-- +-- 2016-08-14: SPAWN.**Init**UnControlled() replaces SPAWN._UnControlled_(). +-- +-- * Want to ensure that the methods starting with **Init** are the first called methods before any _Spawn_ method is called! +-- * This notation makes it now more clear which methods are initialization methods and which methods are Spawn enablement methods. +-- +-- === +-- +-- AUTHORS and CONTRIBUTIONS +-- ========================= +-- +-- ### Contributions: +-- +-- * **Aaron**: Posed the idea for Group position randomization at SpawnInZone and make the Unit randomization separate from the Group randomization. +-- +-- ### Authors: +-- +-- * **FlightControl**: Design & Programming -- -- -- @module Spawn --- @author FlightControl + + --- SPAWN Class -- @type SPAWN --- @extends Base#BASE +-- @extends Core.Base#BASE -- @field ClassName -- @field #string SpawnTemplatePrefix -- @field #string SpawnAliasPrefix @@ -20982,15 +17930,18 @@ end -- @field #number MaxAliveUnits -- @field #number SpawnIndex -- @field #number MaxAliveGroups +-- @field #SPAWN.SpawnZoneTable SpawnZoneTable SPAWN = { ClassName = "SPAWN", SpawnTemplatePrefix = nil, SpawnAliasPrefix = nil, } +--- @type SPAWN.SpawnZoneTable +-- @list SpawnZone ---- Creates the main object to spawn a GROUP defined in the DCS ME. +--- Creates the main object to spawn a @{Group} defined in the DCS ME. -- @param #SPAWN self -- @param #string SpawnTemplatePrefix is the name of the Group in the ME that defines the Template. Each new group will have the name starting with SpawnTemplatePrefix. -- @return #SPAWN @@ -21065,7 +18016,7 @@ end --- Limits the Maximum amount of Units that can be alive at the same time, and the maximum amount of groups that can be spawned. -- Note that this method is exceptionally important to balance the performance of the mission. Depending on the machine etc, a mission can only process a maximum amount of units. --- If the time interval must be short, but there should not be more Units or Groups alive than a maximum amount of units, then this function should be used... +-- If the time interval must be short, but there should not be more Units or Groups alive than a maximum amount of units, then this method should be used... -- When a @{#SPAWN.New} is executed and the limit of the amount of units alive is reached, then no new spawn will happen of the group, until some of these units of the spawn object will be destroyed. -- @param #SPAWN self -- @param #number SpawnMaxUnitsAlive The maximum amount of units that can be alive at runtime. @@ -21077,8 +18028,8 @@ end -- -- NATO helicopters engaging in the battle field. -- -- This helicopter group consists of one Unit. So, this group will SPAWN maximum 2 groups simultaneously within the DCSRTE. -- -- There will be maximum 24 groups spawned during the whole mission lifetime. --- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):Limit( 2, 24 ) -function SPAWN:Limit( SpawnMaxUnitsAlive, SpawnMaxGroups ) +-- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitLimit( 2, 24 ) +function SPAWN:InitLimit( SpawnMaxUnitsAlive, SpawnMaxGroups ) self:F( { self.SpawnTemplatePrefix, SpawnMaxUnitsAlive, SpawnMaxGroups } ) self.SpawnMaxUnitsAlive = SpawnMaxUnitsAlive -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time. @@ -21106,8 +18057,8 @@ end -- -- The KA-50 has waypoints Start point ( =0 or SP ), 1, 2, 3, 4, End point (= 5 or DP). -- -- Waypoints 2 and 3 will only be randomized. The others will remain on their original position with each new spawn of the helicopter. -- -- The randomization of waypoint 2 and 3 will take place within a radius of 2000 meters. --- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):RandomizeRoute( 2, 2, 2000 ) -function SPAWN:RandomizeRoute( SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight ) +-- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitRandomizeRoute( 2, 2, 2000 ) +function SPAWN:InitRandomizeRoute( SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight ) self:F( { self.SpawnTemplatePrefix, SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight } ) self.SpawnRandomizeRoute = true @@ -21123,9 +18074,34 @@ function SPAWN:RandomizeRoute( SpawnStartPoint, SpawnEndPoint, SpawnRadius, Spaw return self end +--- Randomizes the UNITs that are spawned within a radius band given an Outer and Inner radius. +-- @param #SPAWN self +-- @param #boolean RandomizeUnits If true, SPAWN will perform the randomization of the @{UNIT}s position within the group between a given outer and inner radius. +-- @param Dcs.DCSTypes#Distance OuterRadius (optional) The outer radius in meters where the new group will be spawned. +-- @param Dcs.DCSTypes#Distance InnerRadius (optional) The inner radius in meters where the new group will NOT be spawned. +-- @return #SPAWN +-- @usage +-- -- NATO helicopters engaging in the battle field. +-- -- The KA-50 has waypoints Start point ( =0 or SP ), 1, 2, 3, 4, End point (= 5 or DP). +-- -- Waypoints 2 and 3 will only be randomized. The others will remain on their original position with each new spawn of the helicopter. +-- -- The randomization of waypoint 2 and 3 will take place within a radius of 2000 meters. +-- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitRandomizeRoute( 2, 2, 2000 ) +function SPAWN:InitRandomizeUnits( RandomizeUnits, OuterRadius, InnerRadius ) + self:F( { self.SpawnTemplatePrefix, RandomizeUnits, OuterRadius, InnerRadius } ) ---- This function is rather complicated to understand. But I'll try to explain. --- This function becomes useful when you need to spawn groups with random templates of groups defined within the mission editor, + self.SpawnRandomizeUnits = RandomizeUnits or false + self.SpawnOuterRadius = OuterRadius or 0 + self.SpawnInnerRadius = InnerRadius or 0 + + for GroupID = 1, self.SpawnMaxGroups do + self:_RandomizeRoute( GroupID ) + end + + return self +end + +--- This method is rather complicated to understand. But I'll try to explain. +-- This method becomes useful when you need to spawn groups with random templates of groups defined within the mission editor, -- but they will all follow the same Template route and have the same prefix name. -- In other words, this method randomizes between a defined set of groups the template to be used for each new spawn of a group. -- @param #SPAWN self @@ -21140,10 +18116,10 @@ end -- Spawn_US_Platoon = { 'US Tank Platoon 1', 'US Tank Platoon 2', 'US Tank Platoon 3', 'US Tank Platoon 4', 'US Tank Platoon 5', -- 'US Tank Platoon 6', 'US Tank Platoon 7', 'US Tank Platoon 8', 'US Tank Platoon 9', 'US Tank Platoon 10', -- 'US Tank Platoon 11', 'US Tank Platoon 12', 'US Tank Platoon 13' } --- Spawn_US_Platoon_Left = SPAWN:New( 'US Tank Platoon Left' ):Limit( 12, 150 ):Schedule( 200, 0.4 ):RandomizeTemplate( Spawn_US_Platoon ):RandomizeRoute( 3, 3, 2000 ) --- Spawn_US_Platoon_Middle = SPAWN:New( 'US Tank Platoon Middle' ):Limit( 12, 150 ):Schedule( 200, 0.4 ):RandomizeTemplate( Spawn_US_Platoon ):RandomizeRoute( 3, 3, 2000 ) --- Spawn_US_Platoon_Right = SPAWN:New( 'US Tank Platoon Right' ):Limit( 12, 150 ):Schedule( 200, 0.4 ):RandomizeTemplate( Spawn_US_Platoon ):RandomizeRoute( 3, 3, 2000 ) -function SPAWN:RandomizeTemplate( SpawnTemplatePrefixTable ) +-- Spawn_US_Platoon_Left = SPAWN:New( 'US Tank Platoon Left' ):InitLimit( 12, 150 ):Schedule( 200, 0.4 ):InitRandomizeTemplate( Spawn_US_Platoon ):InitRandomizeRoute( 3, 3, 2000 ) +-- Spawn_US_Platoon_Middle = SPAWN:New( 'US Tank Platoon Middle' ):InitLimit( 12, 150 ):Schedule( 200, 0.4 ):InitRandomizeTemplate( Spawn_US_Platoon ):InitRandomizeRoute( 3, 3, 2000 ) +-- Spawn_US_Platoon_Right = SPAWN:New( 'US Tank Platoon Right' ):InitLimit( 12, 150 ):Schedule( 200, 0.4 ):InitRandomizeTemplate( Spawn_US_Platoon ):InitRandomizeRoute( 3, 3, 2000 ) +function SPAWN:InitRandomizeTemplate( SpawnTemplatePrefixTable ) self:F( { self.SpawnTemplatePrefix, SpawnTemplatePrefixTable } ) self.SpawnTemplatePrefixTable = SpawnTemplatePrefixTable @@ -21156,12 +18132,33 @@ function SPAWN:RandomizeTemplate( SpawnTemplatePrefixTable ) return self end +--TODO: Add example. +--- This method provides the functionality to randomize the spawning of the Groups at a given list of zones of different types. +-- @param #SPAWN self +-- @param #table SpawnZoneTable A table with @{Zone} objects. If this table is given, then each spawn will be executed within the given list of @{Zone}s objects. +-- @return #SPAWN +-- @usage +-- -- NATO Tank Platoons invading Gori. +-- -- Choose between 3 different zones for each new SPAWN the Group to be executed, regardless of the zone type. +function SPAWN:InitRandomizeZones( SpawnZoneTable ) + self:F( { self.SpawnTemplatePrefix, SpawnZoneTable } ) + + self.SpawnZoneTable = SpawnZoneTable + self.SpawnRandomizeZones = true + + for SpawnGroupID = 1, self.SpawnMaxGroups do + self:_RandomizeZones( SpawnGroupID ) + end + + return self +end + --- For planes and helicopters, when these groups go home and land on their home airbases and farps, they normally would taxi to the parking spot, shut-down their engines and wait forever until the Group is removed by the runtime environment. --- This function is used to re-spawn automatically (so no extra call is needed anymore) the same group after it has landed. +-- This method is used to re-spawn automatically (so no extra call is needed anymore) the same group after it has landed. -- This will enable a spawned group to be re-spawned after it lands, until it is destroyed... -- Note: When the group is respawned, it will re-spawn from the original airbase where it took off. -- So ensure that the routes for groups that respawn, always return to the original airbase, or players may get confused ... @@ -21170,7 +18167,7 @@ end -- @usage -- -- RU Su-34 - AI Ship Attack -- -- Re-SPAWN the Group(s) after each landing and Engine Shut-Down automatically. --- SpawnRU_SU34 = SPAWN:New( 'TF1 RU Su-34 Krymsk@AI - Attack Ships' ):Schedule( 2, 3, 1800, 0.4 ):SpawnUncontrolled():RandomizeRoute( 1, 1, 3000 ):RepeatOnEngineShutDown() +-- SpawnRU_SU34 = SPAWN:New( 'TF1 RU Su-34 Krymsk@AI - Attack Ships' ):Schedule( 2, 3, 1800, 0.4 ):SpawnUncontrolled():InitRandomizeRoute( 1, 1, 3000 ):RepeatOnEngineShutDown() function SPAWN:InitRepeat() self:F( { self.SpawnTemplatePrefix, self.SpawnIndex } ) @@ -21215,11 +18212,15 @@ end -- @param #string SpawnCleanUpInterval The interval to check for inactive groups within seconds. -- @return #SPAWN self -- @usage Spawn_Helicopter:CleanUp( 20 ) -- CleanUp the spawning of the helicopters every 20 seconds when they become inactive. -function SPAWN:CleanUp( SpawnCleanUpInterval ) +function SPAWN:InitCleanUp( SpawnCleanUpInterval ) self:F( { self.SpawnTemplatePrefix, SpawnCleanUpInterval } ) self.SpawnCleanUpInterval = SpawnCleanUpInterval self.SpawnCleanUpTimeStamps = {} + + local SpawnGroup, SpawnCursor = self:GetFirstAliveGroup() + self:T( { "CleanUp Scheduler:", SpawnGroup } ) + --self.CleanUpFunction = routines.scheduleFunction( self._SpawnCleanUpScheduler, { self }, timer.getTime() + 1, SpawnCleanUpInterval ) self.CleanUpScheduler = SCHEDULER:New( self, self._SpawnCleanUpScheduler, {}, 1, SpawnCleanUpInterval, 0.2 ) return self @@ -21237,8 +18238,8 @@ end -- @return #SPAWN self -- @usage -- -- Define an array of Groups. --- Spawn_BE_Ground = SPAWN:New( 'BE Ground' ):Limit( 2, 24 ):Visible( 90, "Diamond", 10, 100, 50 ) -function SPAWN:Array( SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY ) +-- Spawn_BE_Ground = SPAWN:New( 'BE Ground' ):InitLimit( 2, 24 ):InitArray( 90, "Diamond", 10, 100, 50 ) +function SPAWN:InitArray( SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY ) self:F( { self.SpawnTemplatePrefix, SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY } ) self.SpawnVisible = true -- When the first Spawn executes, all the Groups need to be made visible before start. @@ -21298,7 +18299,7 @@ end --- Will spawn a group based on the internal index. -- Note: Uses @{DATABASE} module defined in MOOSE. -- @param #SPAWN self --- @return Group#GROUP The group that was spawned. You can use this group for further actions. +-- @return Wrapper.Group#GROUP The group that was spawned. You can use this group for further actions. function SPAWN:Spawn() self:F( { self.SpawnTemplatePrefix, self.SpawnIndex, self.AliveUnits } ) @@ -21309,7 +18310,7 @@ end -- Note: Uses @{DATABASE} module defined in MOOSE. -- @param #SPAWN self -- @param #string SpawnIndex The index of the group to be spawned. --- @return Group#GROUP The group that was spawned. You can use this group for further actions. +-- @return Wrapper.Group#GROUP The group that was spawned. You can use this group for further actions. function SPAWN:ReSpawn( SpawnIndex ) self:F( { self.SpawnTemplatePrefix, SpawnIndex } ) @@ -21332,7 +18333,8 @@ end --- Will spawn a group with a specified index number. -- Uses @{DATABASE} global object defined in MOOSE. -- @param #SPAWN self --- @return Group#GROUP The group that was spawned. You can use this group for further actions. +-- @param #string SpawnIndex The index of the group to be spawned. +-- @return Wrapper.Group#GROUP The group that was spawned. You can use this group for further actions. function SPAWN:SpawnWithIndex( SpawnIndex ) self:F2( { SpawnTemplatePrefix = self.SpawnTemplatePrefix, SpawnIndex = SpawnIndex, AliveUnits = self.AliveUnits, SpawnMaxGroups = self.SpawnMaxGroups } ) @@ -21341,20 +18343,40 @@ function SPAWN:SpawnWithIndex( SpawnIndex ) if self.SpawnGroups[self.SpawnIndex].Visible then self.SpawnGroups[self.SpawnIndex].Group:Activate() else - _EVENTDISPATCHER:OnBirthForTemplate( self.SpawnGroups[self.SpawnIndex].SpawnTemplate, self._OnBirth, self ) - _EVENTDISPATCHER:OnCrashForTemplate( self.SpawnGroups[self.SpawnIndex].SpawnTemplate, self._OnDeadOrCrash, self ) - _EVENTDISPATCHER:OnDeadForTemplate( self.SpawnGroups[self.SpawnIndex].SpawnTemplate, self._OnDeadOrCrash, self ) + + local SpawnTemplate = self.SpawnGroups[self.SpawnIndex].SpawnTemplate + self:T( SpawnTemplate.name ) + + if SpawnTemplate then + + local PointVec3 = POINT_VEC3:New( SpawnTemplate.route.points[1].x, SpawnTemplate.route.points[1].alt, SpawnTemplate.route.points[1].y ) + self:T( { "Current point of ", self.SpawnTemplatePrefix, PointVec3 } ) + + -- If RandomizeUnits, then Randomize the formation at the start point. + if self.SpawnRandomizeUnits then + for UnitID = 1, #SpawnTemplate.units do + local RandomVec2 = PointVec3:GetRandomVec2InRadius( self.SpawnOuterRadius, self.SpawnInnerRadius ) + SpawnTemplate.units[UnitID].x = RandomVec2.x + SpawnTemplate.units[UnitID].y = RandomVec2.y + self:T( 'SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y ) + end + end + end + + _EVENTDISPATCHER:OnBirthForTemplate( SpawnTemplate, self._OnBirth, self ) + _EVENTDISPATCHER:OnCrashForTemplate( SpawnTemplate, self._OnDeadOrCrash, self ) + _EVENTDISPATCHER:OnDeadForTemplate( SpawnTemplate, self._OnDeadOrCrash, self ) if self.Repeat then - _EVENTDISPATCHER:OnTakeOffForTemplate( self.SpawnGroups[self.SpawnIndex].SpawnTemplate, self._OnTakeOff, self ) - _EVENTDISPATCHER:OnLandForTemplate( self.SpawnGroups[self.SpawnIndex].SpawnTemplate, self._OnLand, self ) + _EVENTDISPATCHER:OnTakeOffForTemplate( SpawnTemplate, self._OnTakeOff, self ) + _EVENTDISPATCHER:OnLandForTemplate( SpawnTemplate, self._OnLand, self ) end if self.RepeatOnEngineShutDown then - _EVENTDISPATCHER:OnEngineShutDownForTemplate( self.SpawnGroups[self.SpawnIndex].SpawnTemplate, self._OnEngineShutDown, self ) + _EVENTDISPATCHER:OnEngineShutDownForTemplate( SpawnTemplate, self._OnEngineShutDown, self ) end - self:T3( self.SpawnGroups[self.SpawnIndex].SpawnTemplate ) + self:T3( SpawnTemplate.name ) - self.SpawnGroups[self.SpawnIndex].Group = _DATABASE:Spawn( self.SpawnGroups[self.SpawnIndex].SpawnTemplate ) + self.SpawnGroups[self.SpawnIndex].Group = _DATABASE:Spawn( SpawnTemplate ) -- If there is a SpawnFunction hook defined, call it. if self.SpawnFunctionHook then @@ -21402,7 +18424,7 @@ function SPAWN:SpawnScheduled( SpawnTime, SpawnTimeVariation ) end --- Will re-start the spawning scheduler. --- Note: This function is only required to be called when the schedule was stopped. +-- Note: This method is only required to be called when the schedule was stopped. function SPAWN:SpawnScheduleStart() self:F( { self.SpawnTemplatePrefix } ) @@ -21418,16 +18440,27 @@ end --- Allows to place a CallFunction hook when a new group spawns. --- The provided function will be called when a new group is spawned, including its given parameters. --- The first parameter of the SpawnFunction is the @{Group#GROUP} that was spawned. +-- The provided method will be called when a new group is spawned, including its given parameters. +-- The first parameter of the SpawnFunction is the @{Wrapper.Group#GROUP} that was spawned. -- @param #SPAWN self --- @param #function SpawnFunctionHook The function to be called when a group spawns. +-- @param #function SpawnCallBackFunction The function to be called when a group spawns. -- @param SpawnFunctionArguments A random amount of arguments to be provided to the function when the group spawns. -- @return #SPAWN -function SPAWN:SpawnFunction( SpawnFunctionHook, ... ) - self:F( SpawnFunction ) +-- @usage +-- -- Declare SpawnObject and call a function when a new Group is spawned. +-- local SpawnObject = SPAWN +-- :New( "SpawnObject" ) +-- :InitLimit( 2, 10 ) +-- :OnSpawnGroup( +-- function( SpawnGroup ) +-- SpawnGroup:E( "I am spawned" ) +-- end +-- ) +-- :SpawnScheduled( 300, 0.3 ) +function SPAWN:OnSpawnGroup( SpawnCallBackFunction, ... ) + self:F( "OnSpawnGroup" ) - self.SpawnFunctionHook = SpawnFunctionHook + self.SpawnFunctionHook = SpawnCallBackFunction self.SpawnFunctionArguments = {} if arg then self.SpawnFunctionArguments = arg @@ -21438,18 +18471,16 @@ end --- Will spawn a group from a Vec3 in 3D space. --- This function is mostly advisable to be used if you want to simulate spawning units in the air, like helicopters or airplanes. +-- This method is mostly advisable to be used if you want to simulate spawning units in the air, like helicopters or airplanes. -- Note that each point in the route assigned to the spawning group is reset to the point of the spawn. -- You can use the returned group to further define the route to be followed. -- @param #SPAWN self --- @param DCSTypes#Vec3 Vec3 The Vec3 coordinates where to spawn the group. --- @param #number OuterRadius (Optional) The outer radius in meters where the new group will be spawned. --- @param #number InnerRadius (Optional) The inner radius in meters where the new group will NOT be spawned. --- @param #number SpawnIndex (Optional) The index which group to spawn within the given zone. --- @return Group#GROUP that was spawned. +-- @param Dcs.DCSTypes#Vec3 Vec3 The Vec3 coordinates where to spawn the group. +-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. +-- @return Wrapper.Group#GROUP that was spawned. -- @return #nil Nothing was spawned. -function SPAWN:SpawnFromVec3( Vec3, OuterRadius, InnerRadius, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, Vec3, OuterRadius, InnerRadius, SpawnIndex } ) +function SPAWN:SpawnFromVec3( Vec3, SpawnIndex ) + self:F( { self.SpawnTemplatePrefix, Vec3, SpawnIndex } ) local PointVec3 = POINT_VEC3:NewFromVec3( Vec3 ) self:T2(PointVec3) @@ -21466,33 +18497,30 @@ function SPAWN:SpawnFromVec3( Vec3, OuterRadius, InnerRadius, SpawnIndex ) if SpawnTemplate then self:T( { "Current point of ", self.SpawnTemplatePrefix, Vec3 } ) + + -- Translate the position of the Group Template to the Vec3. + for UnitID = 1, #SpawnTemplate.units do + self:T( 'Before Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y ) + local UnitTemplate = SpawnTemplate.units[UnitID] + local SX = UnitTemplate.x + local SY = UnitTemplate.y + local BX = SpawnTemplate.route.points[1].x + local BY = SpawnTemplate.route.points[1].y + local TX = Vec3.x + ( SX - BX ) + local TY = Vec3.z + ( SY - BY ) + SpawnTemplate.units[UnitID].x = TX + SpawnTemplate.units[UnitID].y = TY + SpawnTemplate.units[UnitID].alt = Vec3.y + self:T( 'After Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y ) + end SpawnTemplate.route.points[1].x = Vec3.x SpawnTemplate.route.points[1].y = Vec3.z SpawnTemplate.route.points[1].alt = Vec3.y - InnerRadius = InnerRadius or 0 - OuterRadius = OuterRadius or 0 - - -- Apply SpawnFormation - for UnitID = 1, #SpawnTemplate.units do - local RandomVec2 = PointVec3:GetRandomVec2InRadius( OuterRadius, InnerRadius ) - SpawnTemplate.units[UnitID].x = RandomVec2.x - SpawnTemplate.units[UnitID].y = RandomVec2.y - SpawnTemplate.units[UnitID].alt = Vec3.y - self:T( 'SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y ) - end - - -- TODO: Need to rework this. A spawn action should always be at the random point to start from. This move is not correct to be here. --- local RandomVec2 = PointVec3:GetRandomVec2InRadius( OuterRadius, InnerRadius ) --- local Point = {} --- Point.type = "Turning Point" --- Point.x = RandomVec2.x --- Point.y = RandomVec2.y --- Point.action = "Cone" --- Point.speed = 5 --- table.insert( SpawnTemplate.route.points, 2, Point ) - + SpawnTemplate.x = Vec3.x + SpawnTemplate.y = Vec3.z + return self:SpawnWithIndex( self.SpawnIndex ) end end @@ -21501,93 +18529,86 @@ function SPAWN:SpawnFromVec3( Vec3, OuterRadius, InnerRadius, SpawnIndex ) end --- Will spawn a group from a Vec2 in 3D space. --- This function is mostly advisable to be used if you want to simulate spawning groups on the ground from air units, like vehicles. +-- This method is mostly advisable to be used if you want to simulate spawning groups on the ground from air units, like vehicles. -- Note that each point in the route assigned to the spawning group is reset to the point of the spawn. -- You can use the returned group to further define the route to be followed. -- @param #SPAWN self --- @param DCSTypes#Vec2 Vec2 The Vec2 coordinates where to spawn the group. --- @param #number OuterRadius (Optional) The outer radius in meters where the new group will be spawned. --- @param #number InnerRadius (Optional) The inner radius in meters where the new group will NOT be spawned. --- @param #number SpawnIndex (Optional) The index which group to spawn within the given zone. --- @return Group#GROUP that was spawned. +-- @param Dcs.DCSTypes#Vec2 Vec2 The Vec2 coordinates where to spawn the group. +-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. +-- @return Wrapper.Group#GROUP that was spawned. -- @return #nil Nothing was spawned. -function SPAWN:SpawnFromVec2( Vec2, OuterRadius, InnerRadius, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, Vec2, OuterRadius, InnerRadius, SpawnIndex } ) +function SPAWN:SpawnFromVec2( Vec2, SpawnIndex ) + self:F( { self.SpawnTemplatePrefix, Vec2, SpawnIndex } ) local PointVec2 = POINT_VEC2:NewFromVec2( Vec2 ) - return self:SpawnFromVec3( PointVec2:GetVec3(), OuterRadius, InnerRadius, SpawnIndex ) + return self:SpawnFromVec3( PointVec2:GetVec3(), SpawnIndex ) end ---- Will spawn a group from a hosting unit. This function is mostly advisable to be used if you want to simulate spawning from air units, like helicopters, which are dropping infantry into a defined Landing Zone. +--- Will spawn a group from a hosting unit. This method is mostly advisable to be used if you want to simulate spawning from air units, like helicopters, which are dropping infantry into a defined Landing Zone. -- Note that each point in the route assigned to the spawning group is reset to the point of the spawn. -- You can use the returned group to further define the route to be followed. -- @param #SPAWN self --- @param Unit#UNIT HostUnit The air or ground unit dropping or unloading the group. --- @param #number OuterRadius (Optional) The outer radius in meters where the new group will be spawned. --- @param #number InnerRadius (Optional) The inner radius in meters where the new group will NOT be spawned. --- @param #number SpawnIndex (Optional) The index which group to spawn within the given zone. --- @return Group#GROUP that was spawned. +-- @param Wrapper.Unit#UNIT HostUnit The air or ground unit dropping or unloading the group. +-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. +-- @return Wrapper.Group#GROUP that was spawned. -- @return #nil Nothing was spawned. -function SPAWN:SpawnFromUnit( HostUnit, OuterRadius, InnerRadius, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, HostUnit, OuterRadius, InnerRadius, SpawnIndex } ) +function SPAWN:SpawnFromUnit( HostUnit, SpawnIndex ) + self:F( { self.SpawnTemplatePrefix, HostUnit, SpawnIndex } ) if HostUnit and HostUnit:IsAlive() then -- and HostUnit:getUnit(1):inAir() == false then - return self:SpawnFromVec3( HostUnit:GetVec3(), OuterRadius, InnerRadius, SpawnIndex ) + return self:SpawnFromVec3( HostUnit:GetVec3(), SpawnIndex ) end return nil end ---- Will spawn a group from a hosting static. This function is mostly advisable to be used if you want to simulate spawning from buldings and structures (static buildings). +--- Will spawn a group from a hosting static. This method is mostly advisable to be used if you want to simulate spawning from buldings and structures (static buildings). -- You can use the returned group to further define the route to be followed. -- @param #SPAWN self --- @param Static#STATIC HostStatic The static dropping or unloading the group. --- @param #number OuterRadius (Optional) The outer radius in meters where the new group will be spawned. --- @param #number InnerRadius (Optional) The inner radius in meters where the new group will NOT be spawned. --- @param #number SpawnIndex (Optional) The index which group to spawn within the given zone. --- @return Group#GROUP that was spawned. +-- @param Wrapper.Static#STATIC HostStatic The static dropping or unloading the group. +-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. +-- @return Wrapper.Group#GROUP that was spawned. -- @return #nil Nothing was spawned. -function SPAWN:SpawnFromStatic( HostStatic, OuterRadius, InnerRadius, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, HostStatic, OuterRadius, InnerRadius, SpawnIndex } ) +function SPAWN:SpawnFromStatic( HostStatic, SpawnIndex ) + self:F( { self.SpawnTemplatePrefix, HostStatic, SpawnIndex } ) if HostStatic and HostStatic:IsAlive() then - return self:SpawnFromVec3( HostStatic:GetVec3(), OuterRadius, InnerRadius, SpawnIndex ) + return self:SpawnFromVec3( HostStatic:GetVec3(), SpawnIndex ) end return nil end ---- Will spawn a Group within a given @{Zone#ZONE}. --- Once the group is spawned within the zone, it will continue on its route. --- The first waypoint (where the group is spawned) is replaced with the zone coordinates. +--- Will spawn a Group within a given @{Zone}. +-- The @{Zone} can be of any type derived from @{Core.Zone#ZONE_BASE}. +-- Once the @{Group} is spawned within the zone, the @{Group} will continue on its route. +-- The **first waypoint** (where the group is spawned) is replaced with the zone location coordinates. -- @param #SPAWN self --- @param Zone#ZONE Zone The zone where the group is to be spawned. --- @param #number ZoneRandomize (Optional) Set to true if you want to randomize the starting point in the zone. --- @param #number SpawnIndex (Optional) The index which group to spawn within the given zone. --- @return Group#GROUP that was spawned. +-- @param Core.Zone#ZONE Zone The zone where the group is to be spawned. +-- @param #boolean RandomizeGroup (optional) Randomization of the @{Group} position in the zone. +-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. +-- @return Wrapper.Group#GROUP that was spawned. -- @return #nil when nothing was spawned. -function SPAWN:SpawnInZone( Zone, ZoneRandomize, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, Zone, ZoneRandomize, SpawnIndex } ) +function SPAWN:SpawnInZone( Zone, RandomizeGroup, SpawnIndex ) + self:F( { self.SpawnTemplatePrefix, Zone, RandomizeGroup, SpawnIndex } ) if Zone then - if ZoneRandomize then - return self:SpawnFromVec2( Zone:GetVec2(), Zone:GetRadius(), 0, SpawnIndex ) + if RandomizeGroup then + return self:SpawnFromVec2( Zone:GetRandomVec2(), SpawnIndex ) else - return self:SpawnFromVec2( Zone:GetVec2(), 0, 0, SpawnIndex ) + return self:SpawnFromVec2( Zone:GetVec2(), SpawnIndex ) end end return nil end - - - ---- Will spawn a plane group in uncontrolled mode... +--- (AIR) Will spawn a plane group in uncontrolled mode... -- This will be similar to the uncontrolled flag setting in the ME. +-- @param #SPAWN self -- @return #SPAWN self -function SPAWN:UnControlled() +function SPAWN:InitUnControlled() self:F( { self.SpawnTemplatePrefix } ) self.SpawnUnControlled = true @@ -21624,12 +18645,12 @@ function SPAWN:SpawnGroupName( SpawnIndex ) end ---- Will find the first alive GROUP it has spawned, and return the alive GROUP object and the first Index where the first alive GROUP object has been found. +--- Will find the first alive @{Group} it has spawned, and return the alive @{Group} object and the first Index where the first alive @{Group} object has been found. -- @param #SPAWN self --- @return Group#GROUP, #number The GROUP object found, the new Index where the group was found. +-- @return Wrapper.Group#GROUP, #number The @{Group} object found, the new Index where the group was found. -- @return #nil, #nil When no group is found, #nil is returned. -- @usage --- -- Find the first alive GROUP object of the SpawnPlanes SPAWN object GROUP collection that it has spawned during the mission. +-- -- Find the first alive @{Group} object of the SpawnPlanes SPAWN object @{Group} collection that it has spawned during the mission. -- local GroupPlane, Index = SpawnPlanes:GetFirstAliveGroup() -- while GroupPlane ~= nil do -- -- Do actions with the GroupPlane object. @@ -21649,13 +18670,13 @@ function SPAWN:GetFirstAliveGroup() end ---- Will find the next alive GROUP object from a given Index, and return a reference to the alive GROUP object and the next Index where the alive GROUP has been found. +--- Will find the next alive @{Group} object from a given Index, and return a reference to the alive @{Group} object and the next Index where the alive @{Group} has been found. -- @param #SPAWN self --- @param #number SpawnIndexStart A Index holding the start position to search from. This function can also be used to find the first alive GROUP object from the given Index. --- @return Group#GROUP, #number The next alive GROUP object found, the next Index where the next alive GROUP object was found. --- @return #nil, #nil When no alive GROUP object is found from the start Index position, #nil is returned. +-- @param #number SpawnIndexStart A Index holding the start position to search from. This method can also be used to find the first alive @{Group} object from the given Index. +-- @return Wrapper.Group#GROUP, #number The next alive @{Group} object found, the next Index where the next alive @{Group} object was found. +-- @return #nil, #nil When no alive @{Group} object is found from the start Index position, #nil is returned. -- @usage --- -- Find the first alive GROUP object of the SpawnPlanes SPAWN object GROUP collection that it has spawned during the mission. +-- -- Find the first alive @{Group} object of the SpawnPlanes SPAWN object @{Group} collection that it has spawned during the mission. -- local GroupPlane, Index = SpawnPlanes:GetFirstAliveGroup() -- while GroupPlane ~= nil do -- -- Do actions with the GroupPlane object. @@ -21675,12 +18696,12 @@ function SPAWN:GetNextAliveGroup( SpawnIndexStart ) return nil, nil end ---- Will find the last alive GROUP object, and will return a reference to the last live GROUP object and the last Index where the last alive GROUP object has been found. +--- Will find the last alive @{Group} object, and will return a reference to the last live @{Group} object and the last Index where the last alive @{Group} object has been found. -- @param #SPAWN self --- @return Group#GROUP, #number The last alive GROUP object found, the last Index where the last alive GROUP object was found. --- @return #nil, #nil When no alive GROUP object is found, #nil is returned. +-- @return Wrapper.Group#GROUP, #number The last alive @{Group} object found, the last Index where the last alive @{Group} object was found. +-- @return #nil, #nil When no alive @{Group} object is found, #nil is returned. -- @usage --- -- Find the last alive GROUP object of the SpawnPlanes SPAWN object GROUP collection that it has spawned during the mission. +-- -- Find the last alive @{Group} object of the SpawnPlanes SPAWN object @{Group} collection that it has spawned during the mission. -- local GroupPlane, Index = SpawnPlanes:GetLastAliveGroup() -- if GroupPlane then -- GroupPlane can be nil!!! -- -- Do actions with the GroupPlane object. @@ -21708,7 +18729,7 @@ end -- If no index is given, it will return the first group in the list. -- @param #SPAWN self -- @param #number SpawnIndex The index of the group to return. --- @return Group#GROUP self +-- @return Wrapper.Group#GROUP self function SPAWN:GetGroupFromIndex( SpawnIndex ) self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnIndex } ) @@ -21728,7 +18749,7 @@ end -- The method will search for a #-mark, and will return the index behind the #-mark of the DCSUnit. -- It will return nil of no prefix was found. -- @param #SPAWN self --- @param DCSUnit#Unit DCSUnit The @{DCSUnit} to be searched. +-- @param Dcs.DCSWrapper.Unit#Unit DCSUnit The @{DCSUnit} to be searched. -- @return #string The prefix -- @return #nil Nothing found function SPAWN:_GetGroupIndexFromDCSUnit( DCSUnit ) @@ -21750,7 +18771,7 @@ end -- The method will search for a #-mark, and will return the text before the #-mark. -- It will return nil of no prefix was found. -- @param #SPAWN self --- @param DCSUnit#UNIT DCSUnit The @{DCSUnit} to be searched. +-- @param Dcs.DCSWrapper.Unit#UNIT DCSUnit The @{DCSUnit} to be searched. -- @return #string The prefix -- @return #nil Nothing found function SPAWN:_GetPrefixFromDCSUnit( DCSUnit ) @@ -21770,8 +18791,8 @@ end --- Return the group within the SpawnGroups collection with input a DCSUnit. -- @param #SPAWN self --- @param DCSUnit#Unit DCSUnit The @{DCSUnit} to be searched. --- @return Group#GROUP The Group +-- @param Dcs.DCSWrapper.Unit#Unit DCSUnit The @{DCSUnit} to be searched. +-- @return Wrapper.Group#GROUP The Group -- @return #nil Nothing found function SPAWN:_GetGroupFromDCSUnit( DCSUnit ) self:F3( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, DCSUnit } ) @@ -21920,8 +18941,6 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) for UnitID = 1, #SpawnTemplate.units do SpawnTemplate.units[UnitID].name = string.format( SpawnTemplate.name .. '-%02d', UnitID ) SpawnTemplate.units[UnitID].unitId = nil - SpawnTemplate.units[UnitID].x = SpawnTemplate.route.points[1].x - SpawnTemplate.units[UnitID].y = SpawnTemplate.route.points[1].y end self:T3( { "Template:", SpawnTemplate } ) @@ -21958,6 +18977,8 @@ function SPAWN:_RandomizeRoute( SpawnIndex ) end end + self:_RandomizeZones( SpawnIndex ) + return self end @@ -21988,6 +19009,57 @@ function SPAWN:_RandomizeTemplate( SpawnIndex ) return self end +--- Private method that randomizes the @{Zone}s where the Group will be spawned. +-- @param #SPAWN self +-- @param #number SpawnIndex +-- @return #SPAWN self +function SPAWN:_RandomizeZones( SpawnIndex ) + self:F( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeZones } ) + + if self.SpawnRandomizeZones then + local SpawnZone = nil -- Core.Zone#ZONE_BASE + while not SpawnZone do + self:T( { SpawnZoneTableCount = #self.SpawnZoneTable, self.SpawnZoneTable } ) + local ZoneID = math.random( #self.SpawnZoneTable ) + self:T( ZoneID ) + SpawnZone = self.SpawnZoneTable[ ZoneID ]:GetZoneMaybe() + end + + self:T( "Preparing Spawn in Zone", SpawnZone:GetName() ) + + local SpawnVec2 = SpawnZone:GetRandomVec2() + + self:T( { SpawnVec2 = SpawnVec2 } ) + + local SpawnTemplate = self.SpawnGroups[SpawnIndex].SpawnTemplate + + self:T( { Route = SpawnTemplate.route } ) + + for UnitID = 1, #SpawnTemplate.units do + local UnitTemplate = SpawnTemplate.units[UnitID] + self:T( 'Before Translation SpawnTemplate.units['..UnitID..'].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. UnitTemplate.y ) + local SX = UnitTemplate.x + local SY = UnitTemplate.y + local BX = SpawnTemplate.route.points[1].x + local BY = SpawnTemplate.route.points[1].y + local TX = SpawnVec2.x + ( SX - BX ) + local TY = SpawnVec2.y + ( SY - BY ) + UnitTemplate.x = TX + UnitTemplate.y = TY + -- TODO: Manage altitude based on landheight... + --SpawnTemplate.units[UnitID].alt = SpawnVec2: + self:T( 'After Translation SpawnTemplate.units['..UnitID..'].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. UnitTemplate.y ) + end + SpawnTemplate.x = SpawnVec2.x + SpawnTemplate.y = SpawnVec2.y + SpawnTemplate.route.points[1].x = SpawnVec2.x + SpawnTemplate.route.points[1].y = SpawnVec2.y + end + + return self + +end + function SPAWN:_TranslateRotate( SpawnIndex, SpawnRootX, SpawnRootY, SpawnX, SpawnY, SpawnAngle ) self:F( { self.SpawnTemplatePrefix, SpawnIndex, SpawnRootX, SpawnRootY, SpawnX, SpawnY, SpawnAngle } ) @@ -22031,7 +19103,7 @@ function SPAWN:_TranslateRotate( SpawnIndex, SpawnRootX, SpawnRootY, SpawnX, Spa return self end ---- Get the next index of the groups to be spawned. This function is complicated, as it is used at several spaces. +--- Get the next index of the groups to be spawned. This method is complicated, as it is used at several spaces. function SPAWN:_GetSpawnIndex( SpawnIndex ) self:F2( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnMaxGroups, self.SpawnMaxUnitsAlive, self.AliveUnits, #self.SpawnTemplate.units } ) @@ -22059,7 +19131,7 @@ end -- TODO Need to delete this... _DATABASE does this now ... --- @param #SPAWN self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event function SPAWN:_OnBirth( Event ) if timer.getTime0() < timer.getAbsTime() then @@ -22079,7 +19151,7 @@ end -- @todo Need to delete this... _DATABASE does this now ... --- @param #SPAWN self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event function SPAWN:_OnDeadOrCrash( Event ) self:F( self.SpawnTemplatePrefix, Event ) @@ -22166,32 +19238,64 @@ function SPAWN:_Scheduler() return true end +--- Schedules the CleanUp of Groups +-- @param #SPAWN self +-- @return #boolean True = Continue Scheduler function SPAWN:_SpawnCleanUpScheduler() self:F( { "CleanUp Scheduler:", self.SpawnTemplatePrefix } ) - local SpawnCursor - local SpawnGroup, SpawnCursor = self:GetFirstAliveGroup( SpawnCursor ) - - self:T( { "CleanUp Scheduler:", SpawnGroup } ) + local SpawnGroup, SpawnCursor = self:GetFirstAliveGroup() + self:T( { "CleanUp Scheduler:", SpawnGroup, SpawnCursor } ) while SpawnGroup do - - if SpawnGroup:AllOnGround() and SpawnGroup:GetMaxVelocity() < 1 then - if not self.SpawnCleanUpTimeStamps[SpawnGroup:GetName()] then - self.SpawnCleanUpTimeStamps[SpawnGroup:GetName()] = timer.getTime() - else - if self.SpawnCleanUpTimeStamps[SpawnGroup:GetName()] + self.SpawnCleanUpInterval < timer.getTime() then - self:T( { "CleanUp Scheduler:", "Cleaning:", SpawnGroup } ) - SpawnGroup:Destroy() - end - end - else - self.SpawnCleanUpTimeStamps[SpawnGroup:GetName()] = nil - end + + local SpawnUnits = SpawnGroup:GetUnits() + + for UnitID, UnitData in pairs( SpawnUnits ) do + + local SpawnUnit = UnitData -- Wrapper.Unit#UNIT + local SpawnUnitName = SpawnUnit:GetName() + + + self.SpawnCleanUpTimeStamps[SpawnUnitName] = self.SpawnCleanUpTimeStamps[SpawnUnitName] or {} + local Stamp = self.SpawnCleanUpTimeStamps[SpawnUnitName] + self:T( { SpawnUnitName, Stamp } ) + + if Stamp.Vec2 then + if SpawnUnit:InAir() == false and SpawnUnit:GetVelocityKMH() < 1 then + local NewVec2 = SpawnUnit:GetVec2() + if Stamp.Vec2.x == NewVec2.x and Stamp.Vec2.y == NewVec2.y then + -- If the plane is not moving, and is on the ground, assign it with a timestamp... + if Stamp.Time + self.SpawnCleanUpInterval < timer.getTime() then + self:T( { "CleanUp Scheduler:", "ReSpawning:", SpawnGroup:GetName() } ) + self:ReSpawn( SpawnCursor ) + Stamp.Vec2 = nil + Stamp.Time = nil + end + else + Stamp.Time = timer.getTime() + Stamp.Vec2 = SpawnUnit:GetVec2() + end + else + Stamp.Vec2 = nil + Stamp.Time = nil + end + else + if SpawnUnit:InAir() == false then + Stamp.Vec2 = SpawnUnit:GetVec2() + if SpawnUnit:GetVelocityKMH() < 1 then + Stamp.Time = timer.getTime() + end + else + Stamp.Time = nil + Stamp.Vec2 = nil + end + end + end SpawnGroup, SpawnCursor = self:GetNextAliveGroup( SpawnCursor ) - self:T( { "CleanUp Scheduler:", SpawnGroup } ) + self:T( { "CleanUp Scheduler:", SpawnGroup, SpawnCursor } ) end @@ -22333,7 +19437,7 @@ end --- The SEAD class -- @type SEAD --- @extends Base#BASE +-- @extends Core.Base#BASE SEAD = { ClassName = "SEAD", TargetSkill = { @@ -22537,7 +19641,7 @@ end -- ============================ -- Create a new SPAWN object with the @{#ESCORT.New} method: -- --- * @{#ESCORT.New}: Creates a new ESCORT object from a @{Group#GROUP} for a @{Client#CLIENT}, with an optional briefing text. +-- * @{#ESCORT.New}: Creates a new ESCORT object from a @{Wrapper.Group#GROUP} for a @{Wrapper.Client#CLIENT}, with an optional briefing text. -- -- ESCORT initialization methods. -- ============================== @@ -22574,17 +19678,17 @@ end --- ESCORT class -- @type ESCORT --- @extends Base#BASE --- @field Client#CLIENT EscortClient --- @field Group#GROUP EscortGroup +-- @extends Core.Base#BASE +-- @field Wrapper.Client#CLIENT EscortClient +-- @field Wrapper.Group#GROUP EscortGroup -- @field #string EscortName -- @field #ESCORT.MODE EscortMode The mode the escort is in. --- @field Scheduler#SCHEDULER FollowScheduler The instance of the SCHEDULER class. +-- @field Core.Scheduler#SCHEDULER FollowScheduler The instance of the SCHEDULER class. -- @field #number FollowDistance The current follow distance. -- @field #boolean ReportTargets If true, nearby targets are reported. --- @Field DCSTypes#AI.Option.Air.val.ROE OptionROE Which ROE is set to the EscortGroup. --- @field DCSTypes#AI.Option.Air.val.REACTION_ON_THREAT OptionReactionOnThreat Which REACTION_ON_THREAT is set to the EscortGroup. --- @field Menu#MENU_CLIENT EscortMenuResumeMission +-- @Field Dcs.DCSTypes#AI.Option.Air.val.ROE OptionROE Which ROE is set to the EscortGroup. +-- @field Dcs.DCSTypes#AI.Option.Air.val.REACTION_ON_THREAT OptionReactionOnThreat Which REACTION_ON_THREAT is set to the EscortGroup. +-- @field Core.Menu#MENU_CLIENT EscortMenuResumeMission ESCORT = { ClassName = "ESCORT", EscortName = nil, -- The Escort Name @@ -22618,8 +19722,8 @@ ESCORT = { --- ESCORT class constructor for an AI group -- @param #ESCORT self --- @param Client#CLIENT EscortClient The client escorted by the EscortGroup. --- @param Group#GROUP EscortGroup The group AI escorting the EscortClient. +-- @param Wrapper.Client#CLIENT EscortClient The client escorted by the EscortGroup. +-- @param Wrapper.Group#GROUP EscortGroup The group AI escorting the EscortClient. -- @param #string EscortName Name of the escort. -- @param #string EscortBriefing A text showing the ESCORT briefing to the player. Note that if no EscortBriefing is provided, the default briefing will be shown. -- @return #ESCORT self @@ -22636,8 +19740,8 @@ function ESCORT:New( EscortClient, EscortGroup, EscortName, EscortBriefing ) local self = BASE:Inherit( self, BASE:New() ) self:F( { EscortClient, EscortGroup, EscortName } ) - self.EscortClient = EscortClient -- Client#CLIENT - self.EscortGroup = EscortGroup -- Group#GROUP + self.EscortClient = EscortClient -- Wrapper.Client#CLIENT + self.EscortGroup = EscortGroup -- Wrapper.Group#GROUP self.EscortName = EscortName self.EscortBriefing = EscortBriefing @@ -22725,7 +19829,7 @@ end --- Defines a menu slot to let the escort Join and Follow you at a certain distance. -- This menu will appear under **Navigation**. -- @param #ESCORT self --- @param DCSTypes#Distance Distance The distance in meters that the escort needs to follow the client. +-- @param Dcs.DCSTypes#Distance Distance The distance in meters that the escort needs to follow the client. -- @return #ESCORT function ESCORT:MenuFollowAt( Distance ) self:F(Distance) @@ -22750,8 +19854,8 @@ end --- Defines a menu slot to let the escort hold at their current position and stay low with a specified height during a specified time in seconds. -- This menu will appear under **Hold position**. -- @param #ESCORT self --- @param DCSTypes#Distance Height Optional parameter that sets the height in meters to let the escort orbit at the current location. The default value is 30 meters. --- @param DCSTypes#Time Seconds Optional parameter that lets the escort orbit at the current position for a specified time. (not implemented yet). The default value is 0 seconds, meaning, that the escort will orbit forever until a sequent command is given. +-- @param Dcs.DCSTypes#Distance Height Optional parameter that sets the height in meters to let the escort orbit at the current location. The default value is 30 meters. +-- @param Dcs.DCSTypes#Time Seconds Optional parameter that lets the escort orbit at the current position for a specified time. (not implemented yet). The default value is 0 seconds, meaning, that the escort will orbit forever until a sequent command is given. -- @param #string MenuTextFormat Optional parameter that shows the menu option text. The text string is formatted, and should contain two %d tokens in the string. The first for the Height, the second for the Time (if given). If no text is given, the default text will be displayed. -- @return #ESCORT -- TODO: Implement Seconds parameter. Challenge is to first develop the "continue from last activity" function. @@ -22812,8 +19916,8 @@ end --- Defines a menu slot to let the escort hold at the client position and stay low with a specified height during a specified time in seconds. -- This menu will appear under **Navigation**. -- @param #ESCORT self --- @param DCSTypes#Distance Height Optional parameter that sets the height in meters to let the escort orbit at the current location. The default value is 30 meters. --- @param DCSTypes#Time Seconds Optional parameter that lets the escort orbit at the current position for a specified time. (not implemented yet). The default value is 0 seconds, meaning, that the escort will orbit forever until a sequent command is given. +-- @param Dcs.DCSTypes#Distance Height Optional parameter that sets the height in meters to let the escort orbit at the current location. The default value is 30 meters. +-- @param Dcs.DCSTypes#Time Seconds Optional parameter that lets the escort orbit at the current position for a specified time. (not implemented yet). The default value is 0 seconds, meaning, that the escort will orbit forever until a sequent command is given. -- @param #string MenuTextFormat Optional parameter that shows the menu option text. The text string is formatted, and should contain one or two %d tokens in the string. The first for the Height, the second for the Time (if given). If no text is given, the default text will be displayed. -- @return #ESCORT -- TODO: Implement Seconds parameter. Challenge is to first develop the "continue from last activity" function. @@ -22873,8 +19977,8 @@ end --- Defines a menu slot to let the escort scan for targets at a certain height for a certain time in seconds. -- This menu will appear under **Scan targets**. -- @param #ESCORT self --- @param DCSTypes#Distance Height Optional parameter that sets the height in meters to let the escort orbit at the current location. The default value is 30 meters. --- @param DCSTypes#Time Seconds Optional parameter that lets the escort orbit at the current position for a specified time. (not implemented yet). The default value is 0 seconds, meaning, that the escort will orbit forever until a sequent command is given. +-- @param Dcs.DCSTypes#Distance Height Optional parameter that sets the height in meters to let the escort orbit at the current location. The default value is 30 meters. +-- @param Dcs.DCSTypes#Time Seconds Optional parameter that lets the escort orbit at the current position for a specified time. (not implemented yet). The default value is 0 seconds, meaning, that the escort will orbit forever until a sequent command is given. -- @param #string MenuTextFormat Optional parameter that shows the menu option text. The text string is formatted, and should contain one or two %d tokens in the string. The first for the Height, the second for the Time (if given). If no text is given, the default text will be displayed. -- @return #ESCORT function ESCORT:MenuScanForTargets( Height, Seconds, MenuTextFormat ) @@ -22951,10 +20055,10 @@ function ESCORT:MenuFlare( MenuTextFormat ) if not self.EscortMenuFlare then self.EscortMenuFlare = MENU_CLIENT:New( self.EscortClient, MenuText, self.EscortMenuReportNavigation, ESCORT._Flare, { ParamSelf = self } ) - self.EscortMenuFlareGreen = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release green flare", self.EscortMenuFlare, ESCORT._Flare, { ParamSelf = self, ParamColor = UNIT.FlareColor.Green, ParamMessage = "Released a green flare!" } ) - self.EscortMenuFlareRed = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release red flare", self.EscortMenuFlare, ESCORT._Flare, { ParamSelf = self, ParamColor = UNIT.FlareColor.Red, ParamMessage = "Released a red flare!" } ) - self.EscortMenuFlareWhite = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release white flare", self.EscortMenuFlare, ESCORT._Flare, { ParamSelf = self, ParamColor = UNIT.FlareColor.White, ParamMessage = "Released a white flare!" } ) - self.EscortMenuFlareYellow = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release yellow flare", self.EscortMenuFlare, ESCORT._Flare, { ParamSelf = self, ParamColor = UNIT.FlareColor.Yellow, ParamMessage = "Released a yellow flare!" } ) + self.EscortMenuFlareGreen = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release green flare", self.EscortMenuFlare, ESCORT._Flare, { ParamSelf = self, ParamColor = FLARECOLOR.Green, ParamMessage = "Released a green flare!" } ) + self.EscortMenuFlareRed = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release red flare", self.EscortMenuFlare, ESCORT._Flare, { ParamSelf = self, ParamColor = FLARECOLOR.Red, ParamMessage = "Released a red flare!" } ) + self.EscortMenuFlareWhite = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release white flare", self.EscortMenuFlare, ESCORT._Flare, { ParamSelf = self, ParamColor = FLARECOLOR.White, ParamMessage = "Released a white flare!" } ) + self.EscortMenuFlareYellow = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release yellow flare", self.EscortMenuFlare, ESCORT._Flare, { ParamSelf = self, ParamColor = FLARECOLOR.Yellow, ParamMessage = "Released a yellow flare!" } ) end return self @@ -22999,7 +20103,7 @@ end -- This menu will appear under **Report targets**. -- Note that if a report targets menu is not specified, no targets will be detected by the escort, and the attack and assisted attack menus will not be displayed. -- @param #ESCORT self --- @param DCSTypes#Time Seconds Optional parameter that lets the escort report their current detected targets after specified time interval in seconds. The default time is 30 seconds. +-- @param Dcs.DCSTypes#Time Seconds Optional parameter that lets the escort report their current detected targets after specified time interval in seconds. The default time is 30 seconds. -- @return #ESCORT function ESCORT:MenuReportTargets( Seconds ) self:F( { Seconds } ) @@ -23121,8 +20225,8 @@ function ESCORT._HoldPosition( MenuParam ) local EscortGroup = self.EscortGroup local EscortClient = self.EscortClient - local OrbitGroup = MenuParam.ParamOrbitGroup -- Group#GROUP - local OrbitUnit = OrbitGroup:GetUnit(1) -- Unit#UNIT + local OrbitGroup = MenuParam.ParamOrbitGroup -- Wrapper.Group#GROUP + local OrbitUnit = OrbitGroup:GetUnit(1) -- Wrapper.Unit#UNIT local OrbitHeight = MenuParam.ParamHeight local OrbitSeconds = MenuParam.ParamSeconds -- Not implemented yet @@ -23171,10 +20275,10 @@ function ESCORT._JoinUpAndFollow( MenuParam ) end --- JoinsUp and Follows a CLIENT. --- @param Escort#ESCORT self --- @param Group#GROUP EscortGroup --- @param Client#CLIENT EscortClient --- @param DCSTypes#Distance Distance +-- @param Functional.Escort#ESCORT self +-- @param Wrapper.Group#GROUP EscortGroup +-- @param Wrapper.Client#CLIENT EscortClient +-- @param Dcs.DCSTypes#Distance Distance function ESCORT:JoinUpAndFollow( EscortGroup, EscortClient, Distance ) self:F( { EscortGroup, EscortClient, Distance } ) @@ -23289,7 +20393,7 @@ function ESCORT._ScanTargets( MenuParam ) end ---- @param Group#GROUP EscortGroup +--- @param Wrapper.Group#GROUP EscortGroup function _Resume( EscortGroup ) env.info( '_Resume' ) @@ -23308,7 +20412,7 @@ function ESCORT._AttackTarget( MenuParam ) local EscortGroup = self.EscortGroup local EscortClient = self.EscortClient - local AttackUnit = MenuParam.ParamUnit -- Unit#UNIT + local AttackUnit = MenuParam.ParamUnit -- Wrapper.Unit#UNIT self.FollowScheduler:Stop() @@ -23349,7 +20453,7 @@ function ESCORT._AssistTarget( MenuParam ) local EscortGroup = self.EscortGroup local EscortClient = self.EscortClient local EscortGroupAttack = MenuParam.ParamEscortGroup - local AttackUnit = MenuParam.ParamUnit -- Unit#UNIT + local AttackUnit = MenuParam.ParamUnit -- Wrapper.Unit#UNIT self.FollowScheduler:Stop() @@ -23438,7 +20542,7 @@ end function ESCORT:RegisterRoute() self:F() - local EscortGroup = self.EscortGroup -- Group#GROUP + local EscortGroup = self.EscortGroup -- Wrapper.Group#GROUP local TaskPoints = EscortGroup:GetTaskRoute() @@ -23447,7 +20551,7 @@ function ESCORT:RegisterRoute() return TaskPoints end ---- @param Escort#ESCORT self +--- @param Functional.Escort#ESCORT self function ESCORT:_FollowScheduler() self:F( { self.FollowDistance } ) @@ -23740,7 +20844,7 @@ end -- -- === -- --- 1) @{MissileTrainer#MISSILETRAINER} class, extends @{Base#BASE} +-- 1) @{Functional.MissileTrainer#MISSILETRAINER} class, extends @{Core.Base#BASE} -- =============================================================== -- The @{#MISSILETRAINER} class uses the DCS world messaging system to be alerted of any missiles fired, and when a missile would hit your aircraft, -- the class will destroy the missile within a certain range, to avoid damage to your aircraft. @@ -23821,8 +20925,8 @@ end --- The MISSILETRAINER class -- @type MISSILETRAINER --- @field Set#SET_CLIENT DBClients --- @extends Base#BASE +-- @field Core.Set#SET_CLIENT DBClients +-- @extends Core.Base#BASE MISSILETRAINER = { ClassName = "MISSILETRAINER", TrackingMissiles = {}, @@ -23929,7 +21033,7 @@ function MISSILETRAINER:New( Distance, Briefing ) -- self.DB:ForEachClient( --- --- @param Client#CLIENT Client +-- --- @param Wrapper.Client#CLIENT Client -- function( Client ) -- -- ... actions ... @@ -24187,7 +21291,7 @@ end --- Detects if an SA site was shot with an anti radiation missile. In this case, take evasive actions based on the skill level set within the ME. -- @param #MISSILETRAINER self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event function MISSILETRAINER:_EventShot( Event ) self:F( { Event } ) @@ -24439,520 +21543,13 @@ function MISSILETRAINER:_TrackMissiles() return true end ---- This module contains the PATROLZONE class. --- --- === --- --- 1) @{Patrol#PATROLZONE} class, extends @{Base#BASE} --- =================================================== --- The @{Patrol#PATROLZONE} class implements the core functions to patrol a @{Zone}. --- --- 1.1) PATROLZONE constructor: --- ---------------------------- --- @{PatrolZone#PATROLZONE.New}(): Creates a new PATROLZONE object. --- --- 1.2) Modify the PATROLZONE parameters: --- -------------------------------------- --- The following methods are available to modify the parameters of a PATROLZONE object: --- --- * @{PatrolZone#PATROLZONE.SetGroup}(): Set the AI Patrol Group. --- * @{PatrolZone#PATROLZONE.SetSpeed}(): Set the patrol speed of the AI, for the next patrol. --- * @{PatrolZone#PATROLZONE.SetAltitude}(): Set altitude of the AI, for the next patrol. --- --- 1.3) Manage the out of fuel in the PATROLZONE: --- ---------------------------------------------- --- When the PatrolGroup is out of fuel, it is required that a new PatrolGroup is started, before the old PatrolGroup can return to the home base. --- Therefore, with a parameter and a calculation of the distance to the home base, the fuel treshold is calculated. --- When the fuel treshold is reached, the PatrolGroup will continue for a given time its patrol task in orbit, while a new PatrolGroup is targetted to the PATROLZONE. --- Once the time is finished, the old PatrolGroup will return to the base. --- Use the method @{PatrolZone#PATROLZONE.ManageFuel}() to have this proces in place. --- --- === --- --- @module PatrolZone --- @author FlightControl - - ---- PATROLZONE class --- @type PATROLZONE --- @field Group#GROUP PatrolGroup The @{Group} patrolling. --- @field Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed. --- @field DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. --- @field DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. --- @field DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Group} in km/h. --- @field DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Group} in km/h. --- @extends Base#BASE -PATROLZONE = { - ClassName = "PATROLZONE", -} - ---- Creates a new PATROLZONE object, taking a @{Group} object as a parameter. The GROUP needs to be alive. --- @param #PATROLZONE self --- @param Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed. --- @param DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. --- @param DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. --- @param DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Group} in km/h. --- @param DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Group} in km/h. --- @return #PATROLZONE self --- @usage --- -- Define a new PATROLZONE Object. This PatrolArea will patrol a group within PatrolZone between 3000 and 6000 meters, with a variying speed between 600 and 900 km/h. --- PatrolZone = ZONE:New( 'PatrolZone' ) --- PatrolGroup = GROUP:FindByName( "Patrol Group" ) --- PatrolArea = PATROLZONE:New( PatrolGroup, PatrolZone, 3000, 6000, 600, 900 ) -function PATROLZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed ) - - -- Inherits from BASE - local self = BASE:Inherit( self, BASE:New() ) - - self.PatrolZone = PatrolZone - self.PatrolFloorAltitude = PatrolFloorAltitude - self.PatrolCeilingAltitude = PatrolCeilingAltitude - self.PatrolMinSpeed = PatrolMinSpeed - self.PatrolMaxSpeed = PatrolMaxSpeed - - return self -end - ---- Set the @{Group} to act as the Patroller. --- @param #PATROLZONE self --- @param Group#GROUP PatrolGroup The @{Group} patrolling. --- @return #PATROLZONE self -function PATROLZONE:SetGroup( PatrolGroup ) - - self.PatrolGroup = PatrolGroup - self.PatrolGroupTemplateName = PatrolGroup:GetName() - self:NewPatrolRoute() - - if not self.PatrolOutOfFuelMonitor then - self.PatrolOutOfFuelMonitor = SCHEDULER:New( nil, _MonitorOutOfFuelScheduled, { self }, 1, 120, 0 ) - self.SpawnPatrolGroup = SPAWN:New( self.PatrolGroupTemplateName ) - end - - return self -end - ---- Sets (modifies) the minimum and maximum speed of the patrol. --- @param #PATROLZONE self --- @param DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Group} in km/h. --- @param DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Group} in km/h. --- @return #PATROLZONE self -function PATROLZONE:SetSpeed( PatrolMinSpeed, PatrolMaxSpeed ) - self:F2( { PatrolMinSpeed, PatrolMaxSpeed } ) - - self.PatrolMinSpeed = PatrolMinSpeed - self.PatrolMaxSpeed = PatrolMaxSpeed -end - ---- Sets the floor and ceiling altitude of the patrol. --- @param #PATROLZONE self --- @param DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. --- @param DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. --- @return #PATROLZONE self -function PATROLZONE:SetAltitude( PatrolFloorAltitude, PatrolCeilingAltitude ) - self:F2( { PatrolFloorAltitude, PatrolCeilingAltitude } ) - - self.PatrolFloorAltitude = PatrolFloorAltitude - self.PatrolCeilingAltitude = PatrolCeilingAltitude -end - - - ---- @param Group#GROUP PatrolGroup -function _NewPatrolRoute( PatrolGroup ) - - PatrolGroup:T( "NewPatrolRoute" ) - local PatrolZone = PatrolGroup:GetState( PatrolGroup, "PatrolZone" ) -- PatrolZone#PATROLZONE - PatrolZone:NewPatrolRoute() -end - ---- Defines a new patrol route using the @{PatrolZone} parameters and settings. --- @param #PATROLZONE self --- @return #PATROLZONE self -function PATROLZONE:NewPatrolRoute() - - self:F2() - - local PatrolRoute = {} - - if self.PatrolGroup:IsAlive() then - --- Determine if the PatrolGroup is within the PatrolZone. - -- If not, make a waypoint within the to that the PatrolGroup will fly at maximum speed to that point. - --- --- Calculate the current route point. --- local CurrentVec2 = self.PatrolGroup:GetVec2() --- local CurrentAltitude = self.PatrolGroup:GetUnit(1):GetAltitude() --- local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) --- local CurrentRoutePoint = CurrentPointVec3:RoutePointAir( --- POINT_VEC3.RoutePointAltType.BARO, --- POINT_VEC3.RoutePointType.TurningPoint, --- POINT_VEC3.RoutePointAction.TurningPoint, --- ToPatrolZoneSpeed, --- true --- ) --- --- PatrolRoute[#PatrolRoute+1] = CurrentRoutePoint - - self:T2( PatrolRoute ) - - if self.PatrolGroup:IsNotInZone( self.PatrolZone ) then - --- Find a random 2D point in PatrolZone. - local ToPatrolZoneVec2 = self.PatrolZone:GetRandomVec2() - self:T2( ToPatrolZoneVec2 ) - - --- Define Speed and Altitude. - local ToPatrolZoneAltitude = math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ) - local ToPatrolZoneSpeed = self.PatrolMaxSpeed - self:T2( ToPatrolZoneSpeed ) - - --- Obtain a 3D @{Point} from the 2D point + altitude. - local ToPatrolZonePointVec3 = POINT_VEC3:New( ToPatrolZoneVec2.x, ToPatrolZoneAltitude, ToPatrolZoneVec2.y ) - - --- Create a route point of type air. - local ToPatrolZoneRoutePoint = ToPatrolZonePointVec3:RoutePointAir( - POINT_VEC3.RoutePointAltType.BARO, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - ToPatrolZoneSpeed, - true - ) - - PatrolRoute[#PatrolRoute+1] = ToPatrolZoneRoutePoint - - end - - --- Define a random point in the @{Zone}. The AI will fly to that point within the zone. - - --- Find a random 2D point in PatrolZone. - local ToTargetVec2 = self.PatrolZone:GetRandomVec2() - self:T2( ToTargetVec2 ) - - --- Define Speed and Altitude. - local ToTargetAltitude = math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ) - local ToTargetSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed ) - self:T2( { self.PatrolMinSpeed, self.PatrolMaxSpeed, ToTargetSpeed } ) - - --- Obtain a 3D @{Point} from the 2D point + altitude. - local ToTargetPointVec3 = POINT_VEC3:New( ToTargetVec2.x, ToTargetAltitude, ToTargetVec2.y ) - - --- Create a route point of type air. - local ToTargetRoutePoint = ToTargetPointVec3:RoutePointAir( - POINT_VEC3.RoutePointAltType.BARO, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - ToTargetSpeed, - true - ) - - --ToTargetPointVec3:SmokeRed() - - PatrolRoute[#PatrolRoute+1] = ToTargetRoutePoint - - --- Now we're going to do something special, we're going to call a function from a waypoint action at the PatrolGroup... - self.PatrolGroup:WayPointInitialize( PatrolRoute ) - - --- Do a trick, link the NewPatrolRoute function of the PATROLGROUP object to the PatrolGroup in a temporary variable ... - self.PatrolGroup:SetState( self.PatrolGroup, "PatrolZone", self ) - self.PatrolGroup:WayPointFunction( #PatrolRoute, 1, "_NewPatrolRoute" ) - - --- NOW ROUTE THE GROUP! - self.PatrolGroup:WayPointExecute( 1, 2 ) - end - -end - ---- When the PatrolGroup is out of fuel, it is required that a new PatrolGroup is started, before the old PatrolGroup can return to the home base. --- Therefore, with a parameter and a calculation of the distance to the home base, the fuel treshold is calculated. --- When the fuel treshold is reached, the PatrolGroup will continue for a given time its patrol task in orbit, while a new PatrolGroup is targetted to the PATROLZONE. --- Once the time is finished, the old PatrolGroup will return to the base. --- @param #PATROLZONE self --- @param #number PatrolFuelTresholdPercentage The treshold in percentage (between 0 and 1) when the PatrolGroup is considered to get out of fuel. --- @param #number PatrolOutOfFuelOrbitTime The amount of seconds the out of fuel PatrolGroup will orbit before returning to the base. --- @return #PATROLZONE self -function PATROLZONE:ManageFuel( PatrolFuelTresholdPercentage, PatrolOutOfFuelOrbitTime ) - - self.PatrolManageFuel = true - self.PatrolFuelTresholdPercentage = PatrolFuelTresholdPercentage - self.PatrolOutOfFuelOrbitTime = PatrolOutOfFuelOrbitTime - - if self.PatrolGroup then - self.PatrolOutOfFuelMonitor = SCHEDULER:New( self, self._MonitorOutOfFuelScheduled, {}, 1, 120, 0 ) - self.SpawnPatrolGroup = SPAWN:New( self.PatrolGroupTemplateName ) - end - return self -end - ---- @param #PATROLZONE self -function _MonitorOutOfFuelScheduled( self ) - self:F2( "_MonitorOutOfFuelScheduled" ) - - if self.PatrolGroup and self.PatrolGroup:IsAlive() then - - local Fuel = self.PatrolGroup:GetUnit(1):GetFuel() - if Fuel < self.PatrolFuelTresholdPercentage then - local OldPatrolGroup = self.PatrolGroup - local PatrolGroupTemplate = self.PatrolGroup:GetTemplate() - - local OrbitTask = OldPatrolGroup:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed ) - local TimedOrbitTask = OldPatrolGroup:TaskControlled( OrbitTask, OldPatrolGroup:TaskCondition(nil,nil,nil,nil,self.PatrolOutOfFuelOrbitTime,nil ) ) - OldPatrolGroup:SetTask( TimedOrbitTask, 10 ) - - local NewPatrolGroup = self.SpawnPatrolGroup:Spawn() - self.PatrolGroup = NewPatrolGroup - self:NewPatrolRoute() - end - else - self.PatrolOutOfFuelMonitor:Stop() - end -end--- This module contains the AIBALANCER class. --- --- === --- --- 1) @{AIBalancer#AIBALANCER} class, extends @{Base#BASE} --- ================================================ --- The @{AIBalancer#AIBALANCER} class controls the dynamic spawning of AI GROUPS depending on a SET_CLIENT. --- There will be as many AI GROUPS spawned as there at CLIENTS in SET_CLIENT not spawned. --- --- 1.1) AIBALANCER construction method: --- ------------------------------------ --- Create a new AIBALANCER object with the @{#AIBALANCER.New} method: --- --- * @{#AIBALANCER.New}: Creates a new AIBALANCER object. --- --- 1.2) AIBALANCER returns AI to Airbases: --- --------------------------------------- --- You can configure to have the AI to return to: --- --- * @{#AIBALANCER.ReturnToHomeAirbase}: Returns the AI to the home @{Airbase#AIRBASE}. --- * @{#AIBALANCER.ReturnToNearestAirbases}: Returns the AI to the nearest friendly @{Airbase#AIRBASE}. --- --- 1.3) AIBALANCER allows AI to patrol specific zones: --- --------------------------------------------------- --- Use @{AIBalancer#AIBALANCER.SetPatrolZone}() to specify a zone where the AI needs to patrol. --- --- === --- --- ### Contributions: --- --- * **Dutch_Baron (James)** Who you can search on the Eagle Dynamics Forums. --- Working together with James has resulted in the creation of the AIBALANCER class. --- James has shared his ideas on balancing AI with air units, and together we made a first design which you can use now :-) --- --- * **SNAFU** --- Had a couple of mails with the guys to validate, if the same concept in the GCI/CAP script could be reworked within MOOSE. --- None of the script code has been used however within the new AIBALANCER moose class. --- --- ### Authors: --- --- * FlightControl - Framework Design & Programming --- --- @module AIBalancer - ---- AIBALANCER class --- @type AIBALANCER --- @field Set#SET_CLIENT SetClient --- @field Spawn#SPAWN SpawnAI --- @field #boolean ToNearestAirbase --- @field Set#SET_AIRBASE ReturnAirbaseSet --- @field DCSTypes#Distance ReturnTresholdRange --- @field #boolean ToHomeAirbase --- @field PatrolZone#PATROLZONE PatrolZone --- @extends Base#BASE -AIBALANCER = { - ClassName = "AIBALANCER", - PatrolZones = {}, - AIGroups = {}, -} - ---- Creates a new AIBALANCER object, building a set of units belonging to a coalitions, categories, countries, types or with defined prefix names. --- @param #AIBALANCER self --- @param SetClient A SET_CLIENT object that will contain the CLIENT objects to be monitored if they are alive or not (joined by a player). --- @param SpawnAI A SPAWN object that will spawn the AI units required, balancing the SetClient. --- @return #AIBALANCER self -function AIBALANCER:New( SetClient, SpawnAI ) - - -- Inherits from BASE - local self = BASE:Inherit( self, BASE:New() ) - - self.SetClient = SetClient - if type( SpawnAI ) == "table" then - if SpawnAI.ClassName and SpawnAI.ClassName == "SPAWN" then - self.SpawnAI = { SpawnAI } - else - local SpawnObjects = true - for SpawnObjectID, SpawnObject in pairs( SpawnAI ) do - if SpawnObject.ClassName and SpawnObject.ClassName == "SPAWN" then - self:E( SpawnObject.ClassName ) - else - self:E( "other object" ) - SpawnObjects = false - end - end - if SpawnObjects == true then - self.SpawnAI = SpawnAI - else - error( "No SPAWN object given in parameter SpawnAI, either as a single object or as a table of objects!" ) - end - end - end - - self.ToNearestAirbase = false - self.ReturnHomeAirbase = false - - self.AIMonitorSchedule = SCHEDULER:New( self, self._ClientAliveMonitorScheduler, {}, 1, 10, 0 ) - - return self -end - ---- Returns the AI to the nearest friendly @{Airbase#AIRBASE}. --- @param #AIBALANCER self --- @param DCSTypes#Distance ReturnTresholdRange If there is an enemy @{Client#CLIENT} within the ReturnTresholdRange given in meters, the AI will not return to the nearest @{Airbase#AIRBASE}. --- @param Set#SET_AIRBASE ReturnAirbaseSet The SET of @{Set#SET_AIRBASE}s to evaluate where to return to. -function AIBALANCER:ReturnToNearestAirbases( ReturnTresholdRange, ReturnAirbaseSet ) - - self.ToNearestAirbase = true - self.ReturnTresholdRange = ReturnTresholdRange - self.ReturnAirbaseSet = ReturnAirbaseSet -end - ---- Returns the AI to the home @{Airbase#AIRBASE}. --- @param #AIBALANCER self --- @param DCSTypes#Distance ReturnTresholdRange If there is an enemy @{Client#CLIENT} within the ReturnTresholdRange given in meters, the AI will not return to the nearest @{Airbase#AIRBASE}. -function AIBALANCER:ReturnToHomeAirbase( ReturnTresholdRange ) - - self.ToHomeAirbase = true - self.ReturnTresholdRange = ReturnTresholdRange -end - ---- Let the AI patrol a @{Zone} with a given Speed range and Altitude range. --- @param #AIBALANCER self --- @param PatrolZone#PATROLZONE PatrolZone The @{PatrolZone} where the AI needs to patrol. --- @return PatrolZone#PATROLZONE self -function AIBALANCER:SetPatrolZone( PatrolZone ) - - self.PatrolZone = PatrolZone -end - ---- @param #AIBALANCER self -function AIBALANCER:_ClientAliveMonitorScheduler() - - self.SetClient:ForEachClient( - --- @param Client#CLIENT Client - function( Client ) - local ClientAIAliveState = Client:GetState( self, 'AIAlive' ) - self:T( ClientAIAliveState ) - if Client:IsAlive() then - if ClientAIAliveState == true then - Client:SetState( self, 'AIAlive', false ) - - local AIGroup = self.AIGroups[Client.UnitName] -- Group#GROUP - --- local PatrolZone = Client:GetState( self, "PatrolZone" ) --- if PatrolZone then --- PatrolZone = nil --- Client:ClearState( self, "PatrolZone" ) --- end - - if self.ToNearestAirbase == false and self.ToHomeAirbase == false then - AIGroup:Destroy() - else - -- We test if there is no other CLIENT within the self.ReturnTresholdRange of the first unit of the AI group. - -- If there is a CLIENT, the AI stays engaged and will not return. - -- If there is no CLIENT within the self.ReturnTresholdRange, then the unit will return to the Airbase return method selected. - - local PlayerInRange = { Value = false } - local RangeZone = ZONE_RADIUS:New( 'RangeZone', AIGroup:GetVec2(), self.ReturnTresholdRange ) - - self:E( RangeZone ) - - _DATABASE:ForEachPlayer( - --- @param Unit#UNIT RangeTestUnit - function( RangeTestUnit, RangeZone, AIGroup, PlayerInRange ) - self:E( { PlayerInRange, RangeTestUnit.UnitName, RangeZone.ZoneName } ) - if RangeTestUnit:IsInZone( RangeZone ) == true then - self:E( "in zone" ) - if RangeTestUnit:GetCoalition() ~= AIGroup:GetCoalition() then - self:E( "in range" ) - PlayerInRange.Value = true - end - end - end, - - --- @param Zone#ZONE_RADIUS RangeZone - -- @param Group#GROUP AIGroup - function( RangeZone, AIGroup, PlayerInRange ) - local AIGroupTemplate = AIGroup:GetTemplate() - if PlayerInRange.Value == false then - if self.ToHomeAirbase == true then - local WayPointCount = #AIGroupTemplate.route.points - local SwitchWayPointCommand = AIGroup:CommandSwitchWayPoint( 1, WayPointCount, 1 ) - AIGroup:SetCommand( SwitchWayPointCommand ) - AIGroup:MessageToRed( "Returning to home base ...", 30 ) - else - -- Okay, we need to send this Group back to the nearest base of the Coalition of the AI. - --TODO: i need to rework the POINT_VEC2 thing. - local PointVec2 = POINT_VEC2:New( AIGroup:GetVec2().x, AIGroup:GetVec2().y ) - local ClosestAirbase = self.ReturnAirbaseSet:FindNearestAirbaseFromPointVec2( PointVec2 ) - self:T( ClosestAirbase.AirbaseName ) - AIGroup:MessageToRed( "Returning to " .. ClosestAirbase:GetName().. " ...", 30 ) - local RTBRoute = AIGroup:RouteReturnToAirbase( ClosestAirbase ) - AIGroupTemplate.route = RTBRoute - AIGroup:Respawn( AIGroupTemplate ) - end - end - end - , RangeZone, AIGroup, PlayerInRange - ) - - end - end - else - if not ClientAIAliveState or ClientAIAliveState == false then - Client:SetState( self, 'AIAlive', true ) - - - -- OK, spawn a new group from the SpawnAI objects provided. - local SpawnAICount = #self.SpawnAI - local SpawnAIIndex = math.random( 1, SpawnAICount ) - local AIGroup = self.SpawnAI[SpawnAIIndex]:Spawn() - AIGroup:E( "spawning new AIGroup" ) - --TODO: need to rework UnitName thing ... - self.AIGroups[Client.UnitName] = AIGroup - - --- Now test if the AIGroup needs to patrol a zone, otherwise let it follow its route... - if self.PatrolZone then - self.PatrolZones[#self.PatrolZones+1] = PATROLZONE:New( - self.PatrolZone.PatrolZone, - self.PatrolZone.PatrolFloorAltitude, - self.PatrolZone.PatrolCeilingAltitude, - self.PatrolZone.PatrolMinSpeed, - self.PatrolZone.PatrolMaxSpeed - ) - - if self.PatrolZone.PatrolManageFuel == true then - self.PatrolZones[#self.PatrolZones]:ManageFuel( self.PatrolZone.PatrolFuelTresholdPercentage, self.PatrolZone.PatrolOutOfFuelOrbitTime ) - end - self.PatrolZones[#self.PatrolZones]:SetGroup( AIGroup ) - - --self.PatrolZones[#self.PatrolZones+1] = PatrolZone - - --Client:SetState( self, "PatrolZone", PatrolZone ) - end - end - end - end - ) - return true -end - - - --- This module contains the AIRBASEPOLICE classes. -- -- === -- --- 1) @{AirbasePolice#AIRBASEPOLICE_BASE} class, extends @{Base#BASE} +-- 1) @{Functional.AirbasePolice#AIRBASEPOLICE_BASE} class, extends @{Core.Base#BASE} -- ================================================================== --- The @{AirbasePolice#AIRBASEPOLICE_BASE} class provides the main methods to monitor CLIENT behaviour at airbases. +-- The @{Functional.AirbasePolice#AIRBASEPOLICE_BASE} class provides the main methods to monitor CLIENT behaviour at airbases. -- CLIENTS should not be allowed to: -- -- * Don't taxi faster than 40 km/h. @@ -24960,7 +21557,7 @@ end -- * Avoid to hit other planes on the airbase. -- * Obey ground control orders. -- --- 2) @{AirbasePolice#AIRBASEPOLICE_CAUCASUS} class, extends @{AirbasePolice#AIRBASEPOLICE_BASE} +-- 2) @{Functional.AirbasePolice#AIRBASEPOLICE_CAUCASUS} class, extends @{Functional.AirbasePolice#AIRBASEPOLICE_BASE} -- ============================================================================================= -- All the airbases on the caucasus map can be monitored using this class. -- If you want to monitor specific airbases, you need to use the @{#AIRBASEPOLICE_BASE.Monitor}() method, which takes a table or airbase names. @@ -24987,7 +21584,7 @@ end -- * TbilisiLochini -- * Vaziani -- --- 3) @{AirbasePolice#AIRBASEPOLICE_NEVADA} class, extends @{AirbasePolice#AIRBASEPOLICE_BASE} +-- 3) @{Functional.AirbasePolice#AIRBASEPOLICE_NEVADA} class, extends @{Functional.AirbasePolice#AIRBASEPOLICE_BASE} -- ============================================================================================= -- All the airbases on the NEVADA map can be monitored using this class. -- If you want to monitor specific airbases, you need to use the @{#AIRBASEPOLICE_BASE.Monitor}() method, which takes a table or airbase names. @@ -25007,8 +21604,8 @@ end --- @type AIRBASEPOLICE_BASE --- @field Set#SET_CLIENT SetClient --- @extends Base#BASE +-- @field Core.Set#SET_CLIENT SetClient +-- @extends Core.Base#BASE AIRBASEPOLICE_BASE = { ClassName = "AIRBASEPOLICE_BASE", @@ -25033,21 +21630,21 @@ function AIRBASEPOLICE_BASE:New( SetClient, Airbases ) self.Airbases = Airbases for AirbaseID, Airbase in pairs( self.Airbases ) do - Airbase.ZoneBoundary = ZONE_POLYGON_BASE:New( "Boundary", Airbase.PointsBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + Airbase.ZoneBoundary = ZONE_POLYGON_BASE:New( "Boundary", Airbase.PointsBoundary ):SmokeZone(SMOKECOLOR.White):Flush() for PointsRunwayID, PointsRunway in pairs( Airbase.PointsRunways ) do - Airbase.ZoneRunways[PointsRunwayID] = ZONE_POLYGON_BASE:New( "Runway " .. PointsRunwayID, PointsRunway ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + Airbase.ZoneRunways[PointsRunwayID] = ZONE_POLYGON_BASE:New( "Runway " .. PointsRunwayID, PointsRunway ):SmokeZone(SMOKECOLOR.Red):Flush() end end -- -- Template -- local TemplateBoundary = GROUP:FindByName( "Template Boundary" ) --- self.Airbases.Template.ZoneBoundary = ZONE_POLYGON:New( "Template Boundary", TemplateBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() +-- self.Airbases.Template.ZoneBoundary = ZONE_POLYGON:New( "Template Boundary", TemplateBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local TemplateRunway1 = GROUP:FindByName( "Template Runway 1" ) --- self.Airbases.Template.ZoneRunways[1] = ZONE_POLYGON:New( "Template Runway 1", TemplateRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() +-- self.Airbases.Template.ZoneRunways[1] = ZONE_POLYGON:New( "Template Runway 1", TemplateRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() self.SetClient:ForEachClient( - --- @param Client#CLIENT Client + --- @param Wrapper.Client#CLIENT Client function( Client ) Client:SetState( self, "Speeding", false ) Client:SetState( self, "Warnings", 0) @@ -25089,7 +21686,7 @@ function AIRBASEPOLICE_BASE:_AirbaseMonitor() self.SetClient:ForEachClientInZone( Airbase.ZoneBoundary, - --- @param Client#CLIENT Client + --- @param Wrapper.Client#CLIENT Client function( Client ) self:E( Client.UnitName ) @@ -25167,7 +21764,7 @@ end --- @type AIRBASEPOLICE_CAUCASUS --- @field Set#SET_CLIENT SetClient +-- @field Core.Set#SET_CLIENT SetClient -- @extends #AIRBASEPOLICE_BASE AIRBASEPOLICE_CAUCASUS = { @@ -25697,197 +22294,197 @@ function AIRBASEPOLICE_CAUCASUS:New( SetClient ) -- -- AnapaVityazevo -- local AnapaVityazevoBoundary = GROUP:FindByName( "AnapaVityazevo Boundary" ) - -- self.Airbases.AnapaVityazevo.ZoneBoundary = ZONE_POLYGON:New( "AnapaVityazevo Boundary", AnapaVityazevoBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.AnapaVityazevo.ZoneBoundary = ZONE_POLYGON:New( "AnapaVityazevo Boundary", AnapaVityazevoBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local AnapaVityazevoRunway1 = GROUP:FindByName( "AnapaVityazevo Runway 1" ) - -- self.Airbases.AnapaVityazevo.ZoneRunways[1] = ZONE_POLYGON:New( "AnapaVityazevo Runway 1", AnapaVityazevoRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.AnapaVityazevo.ZoneRunways[1] = ZONE_POLYGON:New( "AnapaVityazevo Runway 1", AnapaVityazevoRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- Batumi -- local BatumiBoundary = GROUP:FindByName( "Batumi Boundary" ) - -- self.Airbases.Batumi.ZoneBoundary = ZONE_POLYGON:New( "Batumi Boundary", BatumiBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.Batumi.ZoneBoundary = ZONE_POLYGON:New( "Batumi Boundary", BatumiBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local BatumiRunway1 = GROUP:FindByName( "Batumi Runway 1" ) - -- self.Airbases.Batumi.ZoneRunways[1] = ZONE_POLYGON:New( "Batumi Runway 1", BatumiRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.Batumi.ZoneRunways[1] = ZONE_POLYGON:New( "Batumi Runway 1", BatumiRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- Beslan -- local BeslanBoundary = GROUP:FindByName( "Beslan Boundary" ) - -- self.Airbases.Beslan.ZoneBoundary = ZONE_POLYGON:New( "Beslan Boundary", BeslanBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.Beslan.ZoneBoundary = ZONE_POLYGON:New( "Beslan Boundary", BeslanBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local BeslanRunway1 = GROUP:FindByName( "Beslan Runway 1" ) - -- self.Airbases.Beslan.ZoneRunways[1] = ZONE_POLYGON:New( "Beslan Runway 1", BeslanRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.Beslan.ZoneRunways[1] = ZONE_POLYGON:New( "Beslan Runway 1", BeslanRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- Gelendzhik -- local GelendzhikBoundary = GROUP:FindByName( "Gelendzhik Boundary" ) - -- self.Airbases.Gelendzhik.ZoneBoundary = ZONE_POLYGON:New( "Gelendzhik Boundary", GelendzhikBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.Gelendzhik.ZoneBoundary = ZONE_POLYGON:New( "Gelendzhik Boundary", GelendzhikBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local GelendzhikRunway1 = GROUP:FindByName( "Gelendzhik Runway 1" ) - -- self.Airbases.Gelendzhik.ZoneRunways[1] = ZONE_POLYGON:New( "Gelendzhik Runway 1", GelendzhikRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.Gelendzhik.ZoneRunways[1] = ZONE_POLYGON:New( "Gelendzhik Runway 1", GelendzhikRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- Gudauta -- local GudautaBoundary = GROUP:FindByName( "Gudauta Boundary" ) - -- self.Airbases.Gudauta.ZoneBoundary = ZONE_POLYGON:New( "Gudauta Boundary", GudautaBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.Gudauta.ZoneBoundary = ZONE_POLYGON:New( "Gudauta Boundary", GudautaBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local GudautaRunway1 = GROUP:FindByName( "Gudauta Runway 1" ) - -- self.Airbases.Gudauta.ZoneRunways[1] = ZONE_POLYGON:New( "Gudauta Runway 1", GudautaRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.Gudauta.ZoneRunways[1] = ZONE_POLYGON:New( "Gudauta Runway 1", GudautaRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- Kobuleti -- local KobuletiBoundary = GROUP:FindByName( "Kobuleti Boundary" ) - -- self.Airbases.Kobuleti.ZoneBoundary = ZONE_POLYGON:New( "Kobuleti Boundary", KobuletiBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.Kobuleti.ZoneBoundary = ZONE_POLYGON:New( "Kobuleti Boundary", KobuletiBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local KobuletiRunway1 = GROUP:FindByName( "Kobuleti Runway 1" ) - -- self.Airbases.Kobuleti.ZoneRunways[1] = ZONE_POLYGON:New( "Kobuleti Runway 1", KobuletiRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.Kobuleti.ZoneRunways[1] = ZONE_POLYGON:New( "Kobuleti Runway 1", KobuletiRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- KrasnodarCenter -- local KrasnodarCenterBoundary = GROUP:FindByName( "KrasnodarCenter Boundary" ) - -- self.Airbases.KrasnodarCenter.ZoneBoundary = ZONE_POLYGON:New( "KrasnodarCenter Boundary", KrasnodarCenterBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.KrasnodarCenter.ZoneBoundary = ZONE_POLYGON:New( "KrasnodarCenter Boundary", KrasnodarCenterBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local KrasnodarCenterRunway1 = GROUP:FindByName( "KrasnodarCenter Runway 1" ) - -- self.Airbases.KrasnodarCenter.ZoneRunways[1] = ZONE_POLYGON:New( "KrasnodarCenter Runway 1", KrasnodarCenterRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.KrasnodarCenter.ZoneRunways[1] = ZONE_POLYGON:New( "KrasnodarCenter Runway 1", KrasnodarCenterRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- KrasnodarPashkovsky -- local KrasnodarPashkovskyBoundary = GROUP:FindByName( "KrasnodarPashkovsky Boundary" ) - -- self.Airbases.KrasnodarPashkovsky.ZoneBoundary = ZONE_POLYGON:New( "KrasnodarPashkovsky Boundary", KrasnodarPashkovskyBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.KrasnodarPashkovsky.ZoneBoundary = ZONE_POLYGON:New( "KrasnodarPashkovsky Boundary", KrasnodarPashkovskyBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local KrasnodarPashkovskyRunway1 = GROUP:FindByName( "KrasnodarPashkovsky Runway 1" ) - -- self.Airbases.KrasnodarPashkovsky.ZoneRunways[1] = ZONE_POLYGON:New( "KrasnodarPashkovsky Runway 1", KrasnodarPashkovskyRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.KrasnodarPashkovsky.ZoneRunways[1] = ZONE_POLYGON:New( "KrasnodarPashkovsky Runway 1", KrasnodarPashkovskyRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- local KrasnodarPashkovskyRunway2 = GROUP:FindByName( "KrasnodarPashkovsky Runway 2" ) - -- self.Airbases.KrasnodarPashkovsky.ZoneRunways[2] = ZONE_POLYGON:New( "KrasnodarPashkovsky Runway 2", KrasnodarPashkovskyRunway2 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.KrasnodarPashkovsky.ZoneRunways[2] = ZONE_POLYGON:New( "KrasnodarPashkovsky Runway 2", KrasnodarPashkovskyRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- Krymsk -- local KrymskBoundary = GROUP:FindByName( "Krymsk Boundary" ) - -- self.Airbases.Krymsk.ZoneBoundary = ZONE_POLYGON:New( "Krymsk Boundary", KrymskBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.Krymsk.ZoneBoundary = ZONE_POLYGON:New( "Krymsk Boundary", KrymskBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local KrymskRunway1 = GROUP:FindByName( "Krymsk Runway 1" ) - -- self.Airbases.Krymsk.ZoneRunways[1] = ZONE_POLYGON:New( "Krymsk Runway 1", KrymskRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.Krymsk.ZoneRunways[1] = ZONE_POLYGON:New( "Krymsk Runway 1", KrymskRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- Kutaisi -- local KutaisiBoundary = GROUP:FindByName( "Kutaisi Boundary" ) - -- self.Airbases.Kutaisi.ZoneBoundary = ZONE_POLYGON:New( "Kutaisi Boundary", KutaisiBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.Kutaisi.ZoneBoundary = ZONE_POLYGON:New( "Kutaisi Boundary", KutaisiBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local KutaisiRunway1 = GROUP:FindByName( "Kutaisi Runway 1" ) - -- self.Airbases.Kutaisi.ZoneRunways[1] = ZONE_POLYGON:New( "Kutaisi Runway 1", KutaisiRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.Kutaisi.ZoneRunways[1] = ZONE_POLYGON:New( "Kutaisi Runway 1", KutaisiRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- MaykopKhanskaya -- local MaykopKhanskayaBoundary = GROUP:FindByName( "MaykopKhanskaya Boundary" ) - -- self.Airbases.MaykopKhanskaya.ZoneBoundary = ZONE_POLYGON:New( "MaykopKhanskaya Boundary", MaykopKhanskayaBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.MaykopKhanskaya.ZoneBoundary = ZONE_POLYGON:New( "MaykopKhanskaya Boundary", MaykopKhanskayaBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local MaykopKhanskayaRunway1 = GROUP:FindByName( "MaykopKhanskaya Runway 1" ) - -- self.Airbases.MaykopKhanskaya.ZoneRunways[1] = ZONE_POLYGON:New( "MaykopKhanskaya Runway 1", MaykopKhanskayaRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.MaykopKhanskaya.ZoneRunways[1] = ZONE_POLYGON:New( "MaykopKhanskaya Runway 1", MaykopKhanskayaRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- MineralnyeVody -- local MineralnyeVodyBoundary = GROUP:FindByName( "MineralnyeVody Boundary" ) - -- self.Airbases.MineralnyeVody.ZoneBoundary = ZONE_POLYGON:New( "MineralnyeVody Boundary", MineralnyeVodyBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.MineralnyeVody.ZoneBoundary = ZONE_POLYGON:New( "MineralnyeVody Boundary", MineralnyeVodyBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local MineralnyeVodyRunway1 = GROUP:FindByName( "MineralnyeVody Runway 1" ) - -- self.Airbases.MineralnyeVody.ZoneRunways[1] = ZONE_POLYGON:New( "MineralnyeVody Runway 1", MineralnyeVodyRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.MineralnyeVody.ZoneRunways[1] = ZONE_POLYGON:New( "MineralnyeVody Runway 1", MineralnyeVodyRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- Mozdok -- local MozdokBoundary = GROUP:FindByName( "Mozdok Boundary" ) - -- self.Airbases.Mozdok.ZoneBoundary = ZONE_POLYGON:New( "Mozdok Boundary", MozdokBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.Mozdok.ZoneBoundary = ZONE_POLYGON:New( "Mozdok Boundary", MozdokBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local MozdokRunway1 = GROUP:FindByName( "Mozdok Runway 1" ) - -- self.Airbases.Mozdok.ZoneRunways[1] = ZONE_POLYGON:New( "Mozdok Runway 1", MozdokRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.Mozdok.ZoneRunways[1] = ZONE_POLYGON:New( "Mozdok Runway 1", MozdokRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- Nalchik -- local NalchikBoundary = GROUP:FindByName( "Nalchik Boundary" ) - -- self.Airbases.Nalchik.ZoneBoundary = ZONE_POLYGON:New( "Nalchik Boundary", NalchikBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.Nalchik.ZoneBoundary = ZONE_POLYGON:New( "Nalchik Boundary", NalchikBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local NalchikRunway1 = GROUP:FindByName( "Nalchik Runway 1" ) - -- self.Airbases.Nalchik.ZoneRunways[1] = ZONE_POLYGON:New( "Nalchik Runway 1", NalchikRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.Nalchik.ZoneRunways[1] = ZONE_POLYGON:New( "Nalchik Runway 1", NalchikRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- Novorossiysk -- local NovorossiyskBoundary = GROUP:FindByName( "Novorossiysk Boundary" ) - -- self.Airbases.Novorossiysk.ZoneBoundary = ZONE_POLYGON:New( "Novorossiysk Boundary", NovorossiyskBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.Novorossiysk.ZoneBoundary = ZONE_POLYGON:New( "Novorossiysk Boundary", NovorossiyskBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local NovorossiyskRunway1 = GROUP:FindByName( "Novorossiysk Runway 1" ) - -- self.Airbases.Novorossiysk.ZoneRunways[1] = ZONE_POLYGON:New( "Novorossiysk Runway 1", NovorossiyskRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.Novorossiysk.ZoneRunways[1] = ZONE_POLYGON:New( "Novorossiysk Runway 1", NovorossiyskRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- SenakiKolkhi -- local SenakiKolkhiBoundary = GROUP:FindByName( "SenakiKolkhi Boundary" ) - -- self.Airbases.SenakiKolkhi.ZoneBoundary = ZONE_POLYGON:New( "SenakiKolkhi Boundary", SenakiKolkhiBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.SenakiKolkhi.ZoneBoundary = ZONE_POLYGON:New( "SenakiKolkhi Boundary", SenakiKolkhiBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local SenakiKolkhiRunway1 = GROUP:FindByName( "SenakiKolkhi Runway 1" ) - -- self.Airbases.SenakiKolkhi.ZoneRunways[1] = ZONE_POLYGON:New( "SenakiKolkhi Runway 1", SenakiKolkhiRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.SenakiKolkhi.ZoneRunways[1] = ZONE_POLYGON:New( "SenakiKolkhi Runway 1", SenakiKolkhiRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- SochiAdler -- local SochiAdlerBoundary = GROUP:FindByName( "SochiAdler Boundary" ) - -- self.Airbases.SochiAdler.ZoneBoundary = ZONE_POLYGON:New( "SochiAdler Boundary", SochiAdlerBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.SochiAdler.ZoneBoundary = ZONE_POLYGON:New( "SochiAdler Boundary", SochiAdlerBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local SochiAdlerRunway1 = GROUP:FindByName( "SochiAdler Runway 1" ) - -- self.Airbases.SochiAdler.ZoneRunways[1] = ZONE_POLYGON:New( "SochiAdler Runway 1", SochiAdlerRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.SochiAdler.ZoneRunways[1] = ZONE_POLYGON:New( "SochiAdler Runway 1", SochiAdlerRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- local SochiAdlerRunway2 = GROUP:FindByName( "SochiAdler Runway 2" ) - -- self.Airbases.SochiAdler.ZoneRunways[2] = ZONE_POLYGON:New( "SochiAdler Runway 2", SochiAdlerRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.SochiAdler.ZoneRunways[2] = ZONE_POLYGON:New( "SochiAdler Runway 2", SochiAdlerRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- Soganlug -- local SoganlugBoundary = GROUP:FindByName( "Soganlug Boundary" ) - -- self.Airbases.Soganlug.ZoneBoundary = ZONE_POLYGON:New( "Soganlug Boundary", SoganlugBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.Soganlug.ZoneBoundary = ZONE_POLYGON:New( "Soganlug Boundary", SoganlugBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local SoganlugRunway1 = GROUP:FindByName( "Soganlug Runway 1" ) - -- self.Airbases.Soganlug.ZoneRunways[1] = ZONE_POLYGON:New( "Soganlug Runway 1", SoganlugRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.Soganlug.ZoneRunways[1] = ZONE_POLYGON:New( "Soganlug Runway 1", SoganlugRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- SukhumiBabushara -- local SukhumiBabusharaBoundary = GROUP:FindByName( "SukhumiBabushara Boundary" ) - -- self.Airbases.SukhumiBabushara.ZoneBoundary = ZONE_POLYGON:New( "SukhumiBabushara Boundary", SukhumiBabusharaBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.SukhumiBabushara.ZoneBoundary = ZONE_POLYGON:New( "SukhumiBabushara Boundary", SukhumiBabusharaBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local SukhumiBabusharaRunway1 = GROUP:FindByName( "SukhumiBabushara Runway 1" ) - -- self.Airbases.SukhumiBabushara.ZoneRunways[1] = ZONE_POLYGON:New( "SukhumiBabushara Runway 1", SukhumiBabusharaRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.SukhumiBabushara.ZoneRunways[1] = ZONE_POLYGON:New( "SukhumiBabushara Runway 1", SukhumiBabusharaRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- TbilisiLochini -- local TbilisiLochiniBoundary = GROUP:FindByName( "TbilisiLochini Boundary" ) - -- self.Airbases.TbilisiLochini.ZoneBoundary = ZONE_POLYGON:New( "TbilisiLochini Boundary", TbilisiLochiniBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.TbilisiLochini.ZoneBoundary = ZONE_POLYGON:New( "TbilisiLochini Boundary", TbilisiLochiniBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local TbilisiLochiniRunway1 = GROUP:FindByName( "TbilisiLochini Runway 1" ) - -- self.Airbases.TbilisiLochini.ZoneRunways[1] = ZONE_POLYGON:New( "TbilisiLochini Runway 1", TbilisiLochiniRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.TbilisiLochini.ZoneRunways[1] = ZONE_POLYGON:New( "TbilisiLochini Runway 1", TbilisiLochiniRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- local TbilisiLochiniRunway2 = GROUP:FindByName( "TbilisiLochini Runway 2" ) - -- self.Airbases.TbilisiLochini.ZoneRunways[2] = ZONE_POLYGON:New( "TbilisiLochini Runway 2", TbilisiLochiniRunway2 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.TbilisiLochini.ZoneRunways[2] = ZONE_POLYGON:New( "TbilisiLochini Runway 2", TbilisiLochiniRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- Vaziani -- local VazianiBoundary = GROUP:FindByName( "Vaziani Boundary" ) - -- self.Airbases.Vaziani.ZoneBoundary = ZONE_POLYGON:New( "Vaziani Boundary", VazianiBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.Vaziani.ZoneBoundary = ZONE_POLYGON:New( "Vaziani Boundary", VazianiBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local VazianiRunway1 = GROUP:FindByName( "Vaziani Runway 1" ) - -- self.Airbases.Vaziani.ZoneRunways[1] = ZONE_POLYGON:New( "Vaziani Runway 1", VazianiRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.Vaziani.ZoneRunways[1] = ZONE_POLYGON:New( "Vaziani Runway 1", VazianiRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- @@ -25895,10 +22492,10 @@ function AIRBASEPOLICE_CAUCASUS:New( SetClient ) -- Template -- local TemplateBoundary = GROUP:FindByName( "Template Boundary" ) - -- self.Airbases.Template.ZoneBoundary = ZONE_POLYGON:New( "Template Boundary", TemplateBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.Template.ZoneBoundary = ZONE_POLYGON:New( "Template Boundary", TemplateBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local TemplateRunway1 = GROUP:FindByName( "Template Runway 1" ) - -- self.Airbases.Template.ZoneRunways[1] = ZONE_POLYGON:New( "Template Runway 1", TemplateRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.Template.ZoneRunways[1] = ZONE_POLYGON:New( "Template Runway 1", TemplateRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() return self @@ -25908,7 +22505,7 @@ end --- @type AIRBASEPOLICE_NEVADA --- @extends AirbasePolice#AIRBASEPOLICE_BASE +-- @extends Functional.AirbasePolice#AIRBASEPOLICE_BASE AIRBASEPOLICE_NEVADA = { ClassName = "AIRBASEPOLICE_NEVADA", Airbases = { @@ -26091,49 +22688,49 @@ function AIRBASEPOLICE_NEVADA:New( SetClient ) -- -- Nellis -- local NellisBoundary = GROUP:FindByName( "Nellis Boundary" ) --- self.Airbases.Nellis.ZoneBoundary = ZONE_POLYGON:New( "Nellis Boundary", NellisBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() +-- self.Airbases.Nellis.ZoneBoundary = ZONE_POLYGON:New( "Nellis Boundary", NellisBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local NellisRunway1 = GROUP:FindByName( "Nellis Runway 1" ) --- self.Airbases.Nellis.ZoneRunways[1] = ZONE_POLYGON:New( "Nellis Runway 1", NellisRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() +-- self.Airbases.Nellis.ZoneRunways[1] = ZONE_POLYGON:New( "Nellis Runway 1", NellisRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- local NellisRunway2 = GROUP:FindByName( "Nellis Runway 2" ) --- self.Airbases.Nellis.ZoneRunways[2] = ZONE_POLYGON:New( "Nellis Runway 2", NellisRunway2 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() +-- self.Airbases.Nellis.ZoneRunways[2] = ZONE_POLYGON:New( "Nellis Runway 2", NellisRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- McCarran -- local McCarranBoundary = GROUP:FindByName( "McCarran Boundary" ) --- self.Airbases.McCarran.ZoneBoundary = ZONE_POLYGON:New( "McCarran Boundary", McCarranBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() +-- self.Airbases.McCarran.ZoneBoundary = ZONE_POLYGON:New( "McCarran Boundary", McCarranBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local McCarranRunway1 = GROUP:FindByName( "McCarran Runway 1" ) --- self.Airbases.McCarran.ZoneRunways[1] = ZONE_POLYGON:New( "McCarran Runway 1", McCarranRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() +-- self.Airbases.McCarran.ZoneRunways[1] = ZONE_POLYGON:New( "McCarran Runway 1", McCarranRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- local McCarranRunway2 = GROUP:FindByName( "McCarran Runway 2" ) --- self.Airbases.McCarran.ZoneRunways[2] = ZONE_POLYGON:New( "McCarran Runway 2", McCarranRunway2 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() +-- self.Airbases.McCarran.ZoneRunways[2] = ZONE_POLYGON:New( "McCarran Runway 2", McCarranRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- local McCarranRunway3 = GROUP:FindByName( "McCarran Runway 3" ) --- self.Airbases.McCarran.ZoneRunways[3] = ZONE_POLYGON:New( "McCarran Runway 3", McCarranRunway3 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() +-- self.Airbases.McCarran.ZoneRunways[3] = ZONE_POLYGON:New( "McCarran Runway 3", McCarranRunway3 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- local McCarranRunway4 = GROUP:FindByName( "McCarran Runway 4" ) --- self.Airbases.McCarran.ZoneRunways[4] = ZONE_POLYGON:New( "McCarran Runway 4", McCarranRunway4 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() +-- self.Airbases.McCarran.ZoneRunways[4] = ZONE_POLYGON:New( "McCarran Runway 4", McCarranRunway4 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- Creech -- local CreechBoundary = GROUP:FindByName( "Creech Boundary" ) --- self.Airbases.Creech.ZoneBoundary = ZONE_POLYGON:New( "Creech Boundary", CreechBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() +-- self.Airbases.Creech.ZoneBoundary = ZONE_POLYGON:New( "Creech Boundary", CreechBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local CreechRunway1 = GROUP:FindByName( "Creech Runway 1" ) --- self.Airbases.Creech.ZoneRunways[1] = ZONE_POLYGON:New( "Creech Runway 1", CreechRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() +-- self.Airbases.Creech.ZoneRunways[1] = ZONE_POLYGON:New( "Creech Runway 1", CreechRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- local CreechRunway2 = GROUP:FindByName( "Creech Runway 2" ) --- self.Airbases.Creech.ZoneRunways[2] = ZONE_POLYGON:New( "Creech Runway 2", CreechRunway2 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() +-- self.Airbases.Creech.ZoneRunways[2] = ZONE_POLYGON:New( "Creech Runway 2", CreechRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- Groom Lake -- local GroomLakeBoundary = GROUP:FindByName( "GroomLake Boundary" ) --- self.Airbases.GroomLake.ZoneBoundary = ZONE_POLYGON:New( "GroomLake Boundary", GroomLakeBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() +-- self.Airbases.GroomLake.ZoneBoundary = ZONE_POLYGON:New( "GroomLake Boundary", GroomLakeBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local GroomLakeRunway1 = GROUP:FindByName( "GroomLake Runway 1" ) --- self.Airbases.GroomLake.ZoneRunways[1] = ZONE_POLYGON:New( "GroomLake Runway 1", GroomLakeRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() +-- self.Airbases.GroomLake.ZoneRunways[1] = ZONE_POLYGON:New( "GroomLake Runway 1", GroomLakeRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- local GroomLakeRunway2 = GROUP:FindByName( "GroomLake Runway 2" ) --- self.Airbases.GroomLake.ZoneRunways[2] = ZONE_POLYGON:New( "GroomLake Runway 2", GroomLakeRunway2 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() +-- self.Airbases.GroomLake.ZoneRunways[2] = ZONE_POLYGON:New( "GroomLake Runway 2", GroomLakeRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush() end @@ -26145,14 +22742,14 @@ end -- -- === -- --- 1) @{Detection#DETECTION_BASE} class, extends @{Base#BASE} +-- 1) @{Functional.Detection#DETECTION_BASE} class, extends @{Core.Base#BASE} -- ========================================================== --- The @{Detection#DETECTION_BASE} class defines the core functions to administer detected objects. --- The @{Detection#DETECTION_BASE} class will detect objects within the battle zone for a list of @{Group}s detecting targets following (a) detection method(s). +-- The @{Functional.Detection#DETECTION_BASE} class defines the core functions to administer detected objects. +-- The @{Functional.Detection#DETECTION_BASE} class will detect objects within the battle zone for a list of @{Group}s detecting targets following (a) detection method(s). -- -- 1.1) DETECTION_BASE constructor -- ------------------------------- --- Construct a new DETECTION_BASE instance using the @{Detection#DETECTION_BASE.New}() method. +-- Construct a new DETECTION_BASE instance using the @{Functional.Detection#DETECTION_BASE.New}() method. -- -- 1.2) DETECTION_BASE initialization -- ---------------------------------- @@ -26163,46 +22760,46 @@ end -- -- Use the following functions to report the objects it detected using the methods Visual, Optical, Radar, IRST, RWR, DLINK: -- --- * @{Detection#DETECTION_BASE.InitDetectVisual}(): Detected using Visual. --- * @{Detection#DETECTION_BASE.InitDetectOptical}(): Detected using Optical. --- * @{Detection#DETECTION_BASE.InitDetectRadar}(): Detected using Radar. --- * @{Detection#DETECTION_BASE.InitDetectIRST}(): Detected using IRST. --- * @{Detection#DETECTION_BASE.InitDetectRWR}(): Detected using RWR. --- * @{Detection#DETECTION_BASE.InitDetectDLINK}(): Detected using DLINK. +-- * @{Functional.Detection#DETECTION_BASE.InitDetectVisual}(): Detected using Visual. +-- * @{Functional.Detection#DETECTION_BASE.InitDetectOptical}(): Detected using Optical. +-- * @{Functional.Detection#DETECTION_BASE.InitDetectRadar}(): Detected using Radar. +-- * @{Functional.Detection#DETECTION_BASE.InitDetectIRST}(): Detected using IRST. +-- * @{Functional.Detection#DETECTION_BASE.InitDetectRWR}(): Detected using RWR. +-- * @{Functional.Detection#DETECTION_BASE.InitDetectDLINK}(): Detected using DLINK. -- -- 1.3) Obtain objects detected by DETECTION_BASE -- ---------------------------------------------- --- DETECTION_BASE builds @{Set}s of objects detected. These @{Set#SET_BASE}s can be retrieved using the method @{Detection#DETECTION_BASE.GetDetectedSets}(). --- The method will return a list (table) of @{Set#SET_BASE} objects. +-- DETECTION_BASE builds @{Set}s of objects detected. These @{Core.Set#SET_BASE}s can be retrieved using the method @{Functional.Detection#DETECTION_BASE.GetDetectedSets}(). +-- The method will return a list (table) of @{Core.Set#SET_BASE} objects. -- -- === -- --- 2) @{Detection#DETECTION_AREAS} class, extends @{Detection#DETECTION_BASE} +-- 2) @{Functional.Detection#DETECTION_AREAS} class, extends @{Functional.Detection#DETECTION_BASE} -- =============================================================================== --- The @{Detection#DETECTION_AREAS} class will detect units within the battle zone for a list of @{Group}s detecting targets following (a) detection method(s), --- and will build a list (table) of @{Set#SET_UNIT}s containing the @{Unit#UNIT}s detected. +-- The @{Functional.Detection#DETECTION_AREAS} class will detect units within the battle zone for a list of @{Group}s detecting targets following (a) detection method(s), +-- and will build a list (table) of @{Core.Set#SET_UNIT}s containing the @{Wrapper.Unit#UNIT}s detected. -- The class is group the detected units within zones given a DetectedZoneRange parameter. -- A set with multiple detected zones will be created as there are groups of units detected. -- -- 2.1) Retrieve the Detected Unit sets and Detected Zones -- ------------------------------------------------------- --- The DetectedUnitSets methods are implemented in @{Detection#DECTECTION_BASE} and the DetectedZones methods is implemented in @{Detection#DETECTION_AREAS}. +-- The DetectedUnitSets methods are implemented in @{Functional.Detection#DECTECTION_BASE} and the DetectedZones methods is implemented in @{Functional.Detection#DETECTION_AREAS}. -- --- Retrieve the DetectedUnitSets with the method @{Detection#DETECTION_BASE.GetDetectedSets}(). A table will be return of @{Set#SET_UNIT}s. --- To understand the amount of sets created, use the method @{Detection#DETECTION_BASE.GetDetectedSetCount}(). --- If you want to obtain a specific set from the DetectedSets, use the method @{Detection#DETECTION_BASE.GetDetectedSet}() with a given index. +-- Retrieve the DetectedUnitSets with the method @{Functional.Detection#DETECTION_BASE.GetDetectedSets}(). A table will be return of @{Core.Set#SET_UNIT}s. +-- To understand the amount of sets created, use the method @{Functional.Detection#DETECTION_BASE.GetDetectedSetCount}(). +-- If you want to obtain a specific set from the DetectedSets, use the method @{Functional.Detection#DETECTION_BASE.GetDetectedSet}() with a given index. -- --- Retrieve the formed @{Zone@ZONE_UNIT}s as a result of the grouping the detected units within the DetectionZoneRange, use the method @{Detection#DETECTION_BASE.GetDetectionZones}(). --- To understand the amount of zones created, use the method @{Detection#DETECTION_BASE.GetDetectionZoneCount}(). --- If you want to obtain a specific zone from the DetectedZones, use the method @{Detection#DETECTION_BASE.GetDetectionZone}() with a given index. +-- Retrieve the formed @{Zone@ZONE_UNIT}s as a result of the grouping the detected units within the DetectionZoneRange, use the method @{Functional.Detection#DETECTION_BASE.GetDetectionZones}(). +-- To understand the amount of zones created, use the method @{Functional.Detection#DETECTION_BASE.GetDetectionZoneCount}(). +-- If you want to obtain a specific zone from the DetectedZones, use the method @{Functional.Detection#DETECTION_BASE.GetDetectionZone}() with a given index. -- -- 1.4) Flare or Smoke detected units -- ---------------------------------- --- Use the methods @{Detection#DETECTION_AREAS.FlareDetectedUnits}() or @{Detection#DETECTION_AREAS.SmokeDetectedUnits}() to flare or smoke the detected units when a new detection has taken place. +-- Use the methods @{Functional.Detection#DETECTION_AREAS.FlareDetectedUnits}() or @{Functional.Detection#DETECTION_AREAS.SmokeDetectedUnits}() to flare or smoke the detected units when a new detection has taken place. -- -- 1.5) Flare or Smoke detected zones -- ---------------------------------- --- Use the methods @{Detection#DETECTION_AREAS.FlareDetectedZones}() or @{Detection#DETECTION_AREAS.SmokeDetectedZones}() to flare or smoke the detected zones when a new detection has taken place. +-- Use the methods @{Functional.Detection#DETECTION_AREAS.FlareDetectedZones}() or @{Functional.Detection#DETECTION_AREAS.SmokeDetectedZones}() to flare or smoke the detected zones when a new detection has taken place. -- -- === -- @@ -26220,12 +22817,12 @@ end --- DETECTION_BASE class -- @type DETECTION_BASE --- @field Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Forward Air Controller role. --- @field DCSTypes#Distance DetectionRange The range till which targets are accepted to be detected. +-- @field Core.Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Forward Air Controller role. +-- @field Dcs.DCSTypes#Distance DetectionRange The range till which targets are accepted to be detected. -- @field #DETECTION_BASE.DetectedObjects DetectedObjects The list of detected objects. -- @field #table DetectedObjectsIdentified Map of the DetectedObjects identified. -- @field #number DetectionRun --- @extends Base#BASE +-- @extends Core.Base#BASE DETECTION_BASE = { ClassName = "DETECTION_BASE", DetectionSetGroup = nil, @@ -26247,8 +22844,8 @@ DETECTION_BASE = { --- DETECTION constructor. -- @param #DETECTION_BASE self --- @param Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Forward Air Controller role. --- @param DCSTypes#Distance DetectionRange The range till which targets are accepted to be detected. +-- @param Core.Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Forward Air Controller role. +-- @param Dcs.DCSTypes#Distance DetectionRange The range till which targets are accepted to be detected. -- @return #DETECTION_BASE self function DETECTION_BASE:New( DetectionSetGroup, DetectionRange ) @@ -26388,7 +22985,7 @@ function DETECTION_BASE:GetDetectedObject( ObjectName ) return nil end ---- Get the detected @{Set#SET_BASE}s. +--- Get the detected @{Core.Set#SET_BASE}s. -- @param #DETECTION_BASE self -- @return #DETECTION_BASE.DetectedSets DetectedSets function DETECTION_BASE:GetDetectedSets() @@ -26409,7 +23006,7 @@ end --- Get a SET of detected objects using a given numeric index. -- @param #DETECTION_BASE self -- @param #number Index --- @return Set#SET_BASE +-- @return Core.Set#SET_BASE function DETECTION_BASE:GetDetectedSet( Index ) local DetectionSet = self.DetectedSets[Index] @@ -26422,7 +23019,7 @@ end --- Get the detection Groups. -- @param #DETECTION_BASE self --- @return Group#GROUP +-- @return Wrapper.Group#GROUP function DETECTION_BASE:GetDetectionSetGroup() local DetectionSetGroup = self.DetectionSetGroup @@ -26456,7 +23053,7 @@ function DETECTION_BASE:Schedule( DelayTime, RepeatInterval ) end ---- Form @{Set}s of detected @{Unit#UNIT}s in an array of @{Set#SET_BASE}s. +--- Form @{Set}s of detected @{Wrapper.Unit#UNIT}s in an array of @{Core.Set#SET_BASE}s. -- @param #DETECTION_BASE self function DETECTION_BASE:_DetectionScheduler( SchedulerName ) self:F2( { SchedulerName } ) @@ -26466,7 +23063,7 @@ function DETECTION_BASE:_DetectionScheduler( SchedulerName ) self:UnIdentifyAllDetectedObjects() -- Resets the DetectedObjectsIdentified table for DetectionGroupID, DetectionGroupData in pairs( self.DetectionSetGroup:GetSet() ) do - local DetectionGroup = DetectionGroupData -- Group#GROUP + local DetectionGroup = DetectionGroupData -- Wrapper.Group#GROUP if DetectionGroup:IsAlive() then @@ -26482,7 +23079,7 @@ function DETECTION_BASE:_DetectionScheduler( SchedulerName ) ) for DetectionDetectedTargetID, DetectionDetectedTarget in pairs( DetectionDetectedTargets ) do - local DetectionObject = DetectionDetectedTarget.object -- DCSObject#Object + local DetectionObject = DetectionDetectedTarget.object -- Dcs.DCSWrapper.Object#Object self:T2( DetectionObject ) if DetectionObject and DetectionObject:isExist() and DetectionObject.id_ < 50000000 then @@ -26536,9 +23133,9 @@ end --- DETECTION_AREAS class -- @type DETECTION_AREAS --- @field DCSTypes#Distance DetectionZoneRange The range till which targets are grouped upon the first detected target. +-- @field Dcs.DCSTypes#Distance DetectionZoneRange The range till which targets are grouped upon the first detected target. -- @field #DETECTION_AREAS.DetectedAreas DetectedAreas A list of areas containing the set of @{Unit}s, @{Zone}s, the center @{Unit} within the zone, and ID of each area that was detected within a DetectionZoneRange. --- @extends Detection#DETECTION_BASE +-- @extends Functional.Detection#DETECTION_BASE DETECTION_AREAS = { ClassName = "DETECTION_AREAS", DetectedAreas = { n = 0 }, @@ -26549,21 +23146,21 @@ DETECTION_AREAS = { -- @list <#DETECTION_AREAS.DetectedArea> --- @type DETECTION_AREAS.DetectedArea --- @field Set#SET_UNIT Set -- The Set of Units in the detected area. --- @field Zone#ZONE_UNIT Zone -- The Zone of the detected area. +-- @field Core.Set#SET_UNIT Set -- The Set of Units in the detected area. +-- @field Core.Zone#ZONE_UNIT Zone -- The Zone of the detected area. -- @field #boolean Changed Documents if the detected area has changes. -- @field #table Changes A list of the changes reported on the detected area. (It is up to the user of the detected area to consume those changes). -- @field #number AreaID -- The identifier of the detected area. -- @field #boolean FriendliesNearBy Indicates if there are friendlies within the detected area. --- @field Unit#UNIT NearestFAC The nearest FAC near the Area. +-- @field Wrapper.Unit#UNIT NearestFAC The nearest FAC near the Area. --- DETECTION_AREAS constructor. --- @param Detection#DETECTION_AREAS self --- @param Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Forward Air Controller role. --- @param DCSTypes#Distance DetectionRange The range till which targets are accepted to be detected. --- @param DCSTypes#Distance DetectionZoneRange The range till which targets are grouped upon the first detected target. --- @return Detection#DETECTION_AREAS self +-- @param Functional.Detection#DETECTION_AREAS self +-- @param Core.Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Forward Air Controller role. +-- @param Dcs.DCSTypes#Distance DetectionRange The range till which targets are accepted to be detected. +-- @param Dcs.DCSTypes#Distance DetectionZoneRange The range till which targets are grouped upon the first detected target. +-- @return Functional.Detection#DETECTION_AREAS self function DETECTION_AREAS:New( DetectionSetGroup, DetectionRange, DetectionZoneRange ) -- Inherits from DETECTION_BASE @@ -26582,8 +23179,8 @@ function DETECTION_AREAS:New( DetectionSetGroup, DetectionRange, DetectionZoneRa end --- Add a detected @{#DETECTION_AREAS.DetectedArea}. --- @param Set#SET_UNIT Set -- The Set of Units in the detected area. --- @param Zone#ZONE_UNIT Zone -- The Zone of the detected area. +-- @param Core.Set#SET_UNIT Set -- The Set of Units in the detected area. +-- @param Core.Zone#ZONE_UNIT Zone -- The Zone of the detected area. -- @return #DETECTION_AREAS.DetectedArea DetectedArea function DETECTION_AREAS:AddDetectedArea( Set, Zone ) local DetectedAreas = self:GetDetectedAreas() @@ -26630,10 +23227,10 @@ function DETECTION_AREAS:GetDetectedAreaCount() return DetectedAreaCount end ---- Get the @{Set#SET_UNIT} of a detecttion area using a given numeric index. +--- Get the @{Core.Set#SET_UNIT} of a detecttion area using a given numeric index. -- @param #DETECTION_AREAS self -- @param #number Index --- @return Set#SET_UNIT DetectedSet +-- @return Core.Set#SET_UNIT DetectedSet function DETECTION_AREAS:GetDetectedSet( Index ) local DetectedSetUnit = self.DetectedAreas[Index].Set @@ -26644,10 +23241,10 @@ function DETECTION_AREAS:GetDetectedSet( Index ) return nil end ---- Get the @{Zone#ZONE_UNIT} of a detection area using a given numeric index. +--- Get the @{Core.Zone#ZONE_UNIT} of a detection area using a given numeric index. -- @param #DETECTION_AREAS self -- @param #number Index --- @return Zone#ZONE_UNIT DetectedZone +-- @return Core.Zone#ZONE_UNIT DetectedZone function DETECTION_AREAS:GetDetectedZone( Index ) local DetectedZone = self.DetectedAreas[Index].Zone @@ -26660,11 +23257,11 @@ end --- Background worker function to determine if there are friendlies nearby ... -- @param #DETECTION_AREAS self --- @param Unit#UNIT ReportUnit +-- @param Wrapper.Unit#UNIT ReportUnit function DETECTION_AREAS:ReportFriendliesNearBy( ReportGroupData ) self:F2() - local DetectedArea = ReportGroupData.DetectedArea -- Detection#DETECTION_AREAS.DetectedArea + local DetectedArea = ReportGroupData.DetectedArea -- Functional.Detection#DETECTION_AREAS.DetectedArea local DetectedSet = ReportGroupData.DetectedArea.Set local DetectedZone = ReportGroupData.DetectedArea.Zone local DetectedZoneUnit = DetectedZone.ZoneUNIT @@ -26680,15 +23277,15 @@ function DETECTION_AREAS:ReportFriendliesNearBy( ReportGroupData ) } - --- @param DCSUnit#Unit FoundDCSUnit - -- @param Group#GROUP ReportGroup + --- @param Dcs.DCSWrapper.Unit#Unit FoundDCSUnit + -- @param Wrapper.Group#GROUP ReportGroup -- @param Set#SET_GROUP ReportSetGroup local FindNearByFriendlies = function( FoundDCSUnit, ReportGroupData ) - local DetectedArea = ReportGroupData.DetectedArea -- Detection#DETECTION_AREAS.DetectedArea + local DetectedArea = ReportGroupData.DetectedArea -- Functional.Detection#DETECTION_AREAS.DetectedArea local DetectedSet = ReportGroupData.DetectedArea.Set local DetectedZone = ReportGroupData.DetectedArea.Zone - local DetectedZoneUnit = DetectedZone.ZoneUNIT -- Unit#UNIT + local DetectedZoneUnit = DetectedZone.ZoneUNIT -- Wrapper.Unit#UNIT local ReportSetGroup = ReportGroupData.ReportSetGroup local EnemyCoalition = DetectedZoneUnit:GetCoalition() @@ -26731,7 +23328,7 @@ function DETECTION_AREAS:CalculateThreatLevelA2G( DetectedArea ) local MaxThreatLevelA2G = 0 for UnitName, UnitData in pairs( DetectedArea.Set:GetSet() ) do - local ThreatUnit = UnitData -- Unit#UNIT + local ThreatUnit = UnitData -- Wrapper.Unit#UNIT local ThreatLevelA2G = ThreatUnit:GetThreatLevel() if ThreatLevelA2G > MaxThreatLevelA2G then MaxThreatLevelA2G = ThreatLevelA2G @@ -26746,7 +23343,7 @@ end --- Find the nearest FAC of the DetectedArea. -- @param #DETECTION_AREAS self -- @param #DETECTION_AREAS.DetectedArea DetectedArea --- @return Unit#UNIT The nearest FAC unit +-- @return Wrapper.Unit#UNIT The nearest FAC unit function DETECTION_AREAS:NearestFAC( DetectedArea ) local NearestFAC = nil @@ -26754,7 +23351,7 @@ function DETECTION_AREAS:NearestFAC( DetectedArea ) for FACGroupName, FACGroupData in pairs( self.DetectionSetGroup:GetSet() ) do for FACUnit, FACUnitData in pairs( FACGroupData:GetUnits() ) do - local FACUnit = FACUnitData -- Unit#UNIT + local FACUnit = FACUnitData -- Wrapper.Unit#UNIT if FACUnit:IsActive() then local Vec3 = FACUnit:GetVec3() local PointVec3 = POINT_VEC3:NewFromVec3( Vec3 ) @@ -26972,7 +23569,7 @@ function DETECTION_AREAS:CreateDetectionSets() -- Then search for a new center area unit within the set. Note that the new area unit candidate must be within the area range. for DetectedUnitName, DetectedUnitData in pairs( DetectedSet:GetSet() ) do - local DetectedUnit = DetectedUnitData -- Unit#UNIT + local DetectedUnit = DetectedUnitData -- Wrapper.Unit#UNIT local DetectedObject = self:GetDetectedObject( DetectedUnit.UnitName ) -- The DetectedObject can be nil when the DetectedUnit is not alive anymore or it is not in the DetectedObjects map. @@ -27002,7 +23599,7 @@ function DETECTION_AREAS:CreateDetectionSets() -- If a unit was not found in the set, remove it from the set. This may be added later to other existing or new sets. for DetectedUnitName, DetectedUnitData in pairs( DetectedSet:GetSet() ) do - local DetectedUnit = DetectedUnitData -- Unit#UNIT + local DetectedUnit = DetectedUnitData -- Wrapper.Unit#UNIT local DetectedObject = nil if DetectedUnit:IsAlive() then --self:E(DetectedUnit:GetName()) @@ -27052,7 +23649,7 @@ function DETECTION_AREAS:CreateDetectionSets() if DetectedObject then -- We found an unidentified unit outside of any existing detection area. - local DetectedUnit = UNIT:FindByName( DetectedUnitName ) -- Unit#UNIT + local DetectedUnit = UNIT:FindByName( DetectedUnitName ) -- Wrapper.Unit#UNIT local AddedToDetectionArea = false @@ -27102,7 +23699,7 @@ function DETECTION_AREAS:CreateDetectionSets() DetectedZone.ZoneUNIT:SmokeRed() end DetectedSet:ForEachUnit( - --- @param Unit#UNIT DetectedUnit + --- @param Wrapper.Unit#UNIT DetectedUnit function( DetectedUnit ) if DetectedUnit:IsAlive() then self:T( "Detected Set #" .. DetectedArea.AreaID .. ":" .. DetectedUnit:GetName() ) @@ -27116,51 +23713,4938 @@ function DETECTION_AREAS:CreateDetectionSets() end ) if DETECTION_AREAS._FlareDetectedZones or self._FlareDetectedZones then - DetectedZone:FlareZone( POINT_VEC3.SmokeColor.White, 30, math.random( 0,90 ) ) + DetectedZone:FlareZone( SMOKECOLOR.White, 30, math.random( 0,90 ) ) end if DETECTION_AREAS._SmokeDetectedZones or self._SmokeDetectedZones then - DetectedZone:SmokeZone( POINT_VEC3.SmokeColor.White, 30 ) + DetectedZone:SmokeZone( SMOKECOLOR.White, 30 ) end end end ---- This module contains the DETECTION_MANAGER class and derived classes. +--- This module contains the AI_BALANCER class. -- -- === -- --- 1) @{DetectionManager#DETECTION_MANAGER} class, extends @{Base#BASE} +-- 1) @{AI.AI_Balancer#AI_BALANCER} class, extends @{Core.Fsm#FSM_SET} +-- =================================================================================== +-- The @{AI.AI_Balancer#AI_BALANCER} class monitors and manages as many AI GROUPS as there are +-- CLIENTS in a SET_CLIENT collection not occupied by players. +-- The AI_BALANCER class manages internally a collection of AI management objects, which govern the behaviour +-- of the underlying AI GROUPS. +-- +-- The parent class @{Core.Fsm#FSM_SET} manages the functionality to control the Finite State Machine (FSM) +-- and calls for each event the state transition methods providing the internal @{Core.Fsm#FSM_SET.Set} object containing the +-- SET_GROUP and additional event parameters provided during the event. +-- +-- 1.1) AI_BALANCER construction method +-- --------------------------------------- +-- Create a new AI_BALANCER object with the @{#AI_BALANCER.New} method: +-- +-- * @{#AI_BALANCER.New}: Creates a new AI_BALANCER object. +-- +-- 1.2) +-- ---- +-- * Add +-- * Remove +-- +-- 1.2) AI_BALANCER returns AI to Airbases +-- ------------------------------------------ +-- You can configure to have the AI to return to: +-- +-- * @{#AI_BALANCER.ReturnToHomeAirbase}: Returns the AI to the home @{Wrapper.Airbase#AIRBASE}. +-- * @{#AI_BALANCER.ReturnToNearestAirbases}: Returns the AI to the nearest friendly @{Wrapper.Airbase#AIRBASE}. +-- -- +-- === +-- +-- **API CHANGE HISTORY** +-- ====================== +-- +-- The underlying change log documents the API changes. Please read this carefully. The following notation is used: +-- +-- * **Added** parts are expressed in bold type face. +-- * _Removed_ parts are expressed in italic type face. +-- +-- Hereby the change log: +-- +-- 2016-08-17: SPAWN:**InitCleanUp**( SpawnCleanUpInterval ) replaces SPAWN:_CleanUp_( SpawnCleanUpInterval ) +-- +-- * Want to ensure that the methods starting with **Init** are the first called methods before any _Spawn_ method is called! +-- * This notation makes it now more clear which methods are initialization methods and which methods are Spawn enablement methods. +-- +-- === +-- +-- AUTHORS and CONTRIBUTIONS +-- ========================= +-- +-- ### Contributions: +-- +-- * **Dutch_Baron (James)**: Who you can search on the Eagle Dynamics Forums. +-- Working together with James has resulted in the creation of the AI_BALANCER class. +-- James has shared his ideas on balancing AI with air units, and together we made a first design which you can use now :-) +-- +-- * **SNAFU**: +-- Had a couple of mails with the guys to validate, if the same concept in the GCI/CAP script could be reworked within MOOSE. +-- None of the script code has been used however within the new AI_BALANCER moose class. +-- +-- ### Authors: +-- +-- * FlightControl: Framework Design & Programming +-- +-- @module AI_Balancer + + + +--- AI_BALANCER class +-- @type AI_BALANCER +-- @field Core.Set#SET_CLIENT SetClient +-- @extends Core.Fsm#FSM_SET +AI_BALANCER = { + ClassName = "AI_BALANCER", + PatrolZones = {}, + AIGroups = {}, +} + +--- Creates a new AI_BALANCER object +-- @param #AI_BALANCER self +-- @param Core.Set#SET_CLIENT SetClient A SET\_CLIENT object that will contain the CLIENT objects to be monitored if they are alive or not (joined by a player). +-- @param Functional.Spawn#SPAWN SpawnAI The default Spawn object to spawn new AI Groups when needed. +-- @return #AI_BALANCER +-- @usage +-- -- Define a new AI_BALANCER Object. +function AI_BALANCER:New( SetClient, SpawnAI ) + + -- Inherits from BASE + local self = BASE:Inherit( self, FSM_SET:New( SET_GROUP:New() ) ) -- Core.Fsm#FSM_SET + + self:SetStartState( "None" ) + self:AddTransition( "*", "Start", "Monitoring" ) + self:AddTransition( "*", "Monitor", "Monitoring" ) + self:AddTransition( "*", "Spawn", "Spawning" ) + self:AddTransition( "Spawning", "Spawned", "Spawned" ) + self:AddTransition( "*", "Destroy", "Destroying" ) + self:AddTransition( "*", "Return", "Returning" ) + self:AddTransition( "*", "End", "End" ) + self:AddTransition( "*", "Dead", "End" ) + + + + self.SetClient = SetClient + self.SpawnAI = SpawnAI + self.ToNearestAirbase = false + self.ToHomeAirbase = false + + self:__Start( 1 ) + + return self +end + +--- Returns the AI to the nearest friendly @{Wrapper.Airbase#AIRBASE}. +-- @param #AI_BALANCER self +-- @param Dcs.DCSTypes#Distance ReturnTresholdRange If there is an enemy @{Wrapper.Client#CLIENT} within the ReturnTresholdRange given in meters, the AI will not return to the nearest @{Wrapper.Airbase#AIRBASE}. +-- @param Core.Set#SET_AIRBASE ReturnAirbaseSet The SET of @{Core.Set#SET_AIRBASE}s to evaluate where to return to. +function AI_BALANCER:ReturnToNearestAirbases( ReturnTresholdRange, ReturnAirbaseSet ) + + self.ToNearestAirbase = true + self.ReturnTresholdRange = ReturnTresholdRange + self.ReturnAirbaseSet = ReturnAirbaseSet +end + +--- Returns the AI to the home @{Wrapper.Airbase#AIRBASE}. +-- @param #AI_BALANCER self +-- @param Dcs.DCSTypes#Distance ReturnTresholdRange If there is an enemy @{Wrapper.Client#CLIENT} within the ReturnTresholdRange given in meters, the AI will not return to the nearest @{Wrapper.Airbase#AIRBASE}. +function AI_BALANCER:ReturnToHomeAirbase( ReturnTresholdRange ) + + self.ToHomeAirbase = true + self.ReturnTresholdRange = ReturnTresholdRange +end + +--- @param #AI_BALANCER self +-- @param Core.Set#SET_GROUP SetGroup +-- @param #string ClientName +-- @param Wrapper.Group#GROUP AIGroup +function AI_BALANCER:onenterSpawning( SetGroup, Event, From, To, ClientName ) + + -- OK, Spawn a new group from the default SpawnAI object provided. + local AIGroup = self.SpawnAI:Spawn() + AIGroup:E( "Spawning new AIGroup" ) + --TODO: need to rework UnitName thing ... + + SetGroup:Add( ClientName, AIGroup ) + + -- Fire the Spawned event. The first parameter is the AIGroup just Spawned. + -- Mission designers can catch this event to bind further actions to the AIGroup. + self:Spawned( AIGroup ) +end + +--- @param #AI_BALANCER self +-- @param Core.Set#SET_GROUP SetGroup +-- @param Wrapper.Group#GROUP AIGroup +function AI_BALANCER:onenterDestroying( SetGroup, Event, From, To, AIGroup ) + + AIGroup:Destroy() +end + +--- @param #AI_BALANCER self +-- @param Core.Set#SET_GROUP SetGroup +-- @param Wrapper.Group#GROUP AIGroup +function AI_BALANCER:onenterReturning( SetGroup, Event, From, To, AIGroup ) + + local AIGroupTemplate = AIGroup:GetTemplate() + if self.ToHomeAirbase == true then + local WayPointCount = #AIGroupTemplate.route.points + local SwitchWayPointCommand = AIGroup:CommandSwitchWayPoint( 1, WayPointCount, 1 ) + AIGroup:SetCommand( SwitchWayPointCommand ) + AIGroup:MessageToRed( "Returning to home base ...", 30 ) + else + -- Okay, we need to send this Group back to the nearest base of the Coalition of the AI. + --TODO: i need to rework the POINT_VEC2 thing. + local PointVec2 = POINT_VEC2:New( AIGroup:GetVec2().x, AIGroup:GetVec2().y ) + local ClosestAirbase = self.ReturnAirbaseSet:FindNearestAirbaseFromPointVec2( PointVec2 ) + self:T( ClosestAirbase.AirbaseName ) + AIGroup:MessageToRed( "Returning to " .. ClosestAirbase:GetName().. " ...", 30 ) + local RTBRoute = AIGroup:RouteReturnToAirbase( ClosestAirbase ) + AIGroupTemplate.route = RTBRoute + AIGroup:Respawn( AIGroupTemplate ) + end + +end + + +--- @param #AI_BALANCER self +function AI_BALANCER:onenterMonitoring( SetGroup ) + + self.SetClient:ForEachClient( + --- @param Wrapper.Client#CLIENT Client + function( Client ) + self:E(Client.ClientName) + + local AIGroup = self.Set:Get( Client.UnitName ) -- Wrapper.Group#GROUP + if Client:IsAlive() then + + if AIGroup and AIGroup:IsAlive() == true then + + if self.ToNearestAirbase == false and self.ToHomeAirbase == false then + self:Destroy( AIGroup ) + else + -- We test if there is no other CLIENT within the self.ReturnTresholdRange of the first unit of the AI group. + -- If there is a CLIENT, the AI stays engaged and will not return. + -- If there is no CLIENT within the self.ReturnTresholdRange, then the unit will return to the Airbase return method selected. + + local PlayerInRange = { Value = false } + local RangeZone = ZONE_RADIUS:New( 'RangeZone', AIGroup:GetVec2(), self.ReturnTresholdRange ) + + self:E( RangeZone ) + + _DATABASE:ForEachPlayer( + --- @param Wrapper.Unit#UNIT RangeTestUnit + function( RangeTestUnit, RangeZone, AIGroup, PlayerInRange ) + self:E( { PlayerInRange, RangeTestUnit.UnitName, RangeZone.ZoneName } ) + if RangeTestUnit:IsInZone( RangeZone ) == true then + self:E( "in zone" ) + if RangeTestUnit:GetCoalition() ~= AIGroup:GetCoalition() then + self:E( "in range" ) + PlayerInRange.Value = true + end + end + end, + + --- @param Core.Zone#ZONE_RADIUS RangeZone + -- @param Wrapper.Group#GROUP AIGroup + function( RangeZone, AIGroup, PlayerInRange ) + if PlayerInRange.Value == false then + self:Return( AIGroup ) + end + end + , RangeZone, AIGroup, PlayerInRange + ) + + end + self.Set:Remove( Client.UnitName ) + end + else + if not AIGroup or not AIGroup:IsAlive() == true then + self:E("client not alive") + self:Spawn( Client.UnitName ) + self:E("text after spawn") + end + end + return true + end + ) + + self:__Monitor( 10 ) +end + + + +--- (AI) (FSM) Make AI patrol routes or zones. +-- +-- === +-- +-- 1) @{#AI_PATROLZONE} class, extends @{Core.Fsm#FSM_CONTROLLABLE} +-- ================================================================ +-- The @{#AI_PATROLZONE} class implements the core functions to patrol a @{Zone} by an AIR @{Controllable} @{Group}. +-- The patrol algorithm works that for each airplane patrolling, upon arrival at the patrol zone, +-- a random point is selected as the route point within the 3D space, within the given boundary limits. +-- The airplane will fly towards the random 3D point within the patrol zone, using a random speed within the given altitude and speed limits. +-- Upon arrival at the random 3D point, a new 3D random point will be selected within the patrol zone using the given limits. +-- This cycle will continue until a fuel treshold has been reached by the airplane. +-- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. +-- +-- 1.1) AI_PATROLZONE constructor: +-- ---------------------------- +-- +-- * @{#AI_PATROLZONE.New}(): Creates a new AI_PATROLZONE object. +-- +-- 1.2) AI_PATROLZONE state machine: +-- ---------------------------------- +-- The AI_PATROLZONE is a state machine: it manages the different events and states of the AIControllable it is controlling. +-- +-- ### 1.2.1) AI_PATROLZONE Events: +-- +-- * @{#AI_PATROLZONE.Route}( AIControllable ): A new 3D route point is selected and the AIControllable will fly towards that point with the given speed. +-- * @{#AI_PATROLZONE.Patrol}( AIControllable ): The AIControllable reports it is patrolling. This event is called every 30 seconds. +-- * @{#AI_PATROLZONE.RTB}( AIControllable ): The AIControllable will report return to base. +-- * @{#AI_PATROLZONE.End}( AIControllable ): The end of the AI_PATROLZONE process. +-- * @{#AI_PATROLZONE.Dead}( AIControllable ): The AIControllable is dead. The AI_PATROLZONE process will be ended. +-- +-- ### 1.2.2) AI_PATROLZONE States: +-- +-- * **Route**: A new 3D route point is selected and the AIControllable will fly towards that point with the given speed. +-- * **Patrol**: The AIControllable is patrolling. This state is set every 30 seconds, so every 30 seconds, a state transition method can be used. +-- * **RTB**: The AIControllable reports it wants to return to the base. +-- * **Dead**: The AIControllable is dead ... +-- * **End**: The process has come to an end. +-- +-- ### 1.2.3) AI_PATROLZONE state transition methods: +-- +-- State transition functions can be set **by the mission designer** customizing or improving the behaviour of the state. +-- There are 2 moments when state transition methods will be called by the state machine: +-- +-- * **Before** the state transition. +-- The state transition method needs to start with the name **OnBefore + the name of the state**. +-- If the state transition method returns false, then the processing of the state transition will not be done! +-- If you want to change the behaviour of the AIControllable at this event, return false, +-- but then you'll need to specify your own logic using the AIControllable! +-- +-- * **After** the state transition. +-- The state transition method needs to start with the name **OnAfter + the name of the state**. +-- These state transition methods need to provide a return value, which is specified at the function description. +-- +-- An example how to manage a state transition for an AI_PATROLZONE object **Patrol** for the state **RTB**: +-- +-- local PatrolZoneGroup = GROUP:FindByName( "Patrol Zone" ) +-- local PatrolZone = ZONE_POLYGON:New( "PatrolZone", PatrolZoneGroup ) +-- +-- local PatrolSpawn = SPAWN:New( "Patrol Group" ) +-- local PatrolGroup = PatrolSpawn:Spawn() +-- +-- local Patrol = AI_PATROLZONE:New( PatrolZone, 3000, 6000, 300, 600 ) +-- Patrol:SetControllable( PatrolGroup ) +-- Patrol:ManageFuel( 0.2, 60 ) +-- +-- **OnBefore**RTB( AIGroup ) will be called by the AI_PATROLZONE object when the AIGroup reports RTB, but **before** the RTB default action is processed by the AI_PATROLZONE object. +-- +-- --- State transition function for the AI_PATROLZONE **Patrol** object +-- -- @param #AI_PATROLZONE self +-- -- @param Wrapper.Controllable#CONTROLLABLE AIGroup +-- -- @return #boolean If false is returned, then the OnAfter state transition method will not be called. +-- function Patrol:OnBeforeRTB( AIGroup ) +-- AIGroup:MessageToRed( "Returning to base", 20 ) +-- end +-- +-- **OnAfter**RTB( AIGroup ) will be called by the AI_PATROLZONE object when the AIGroup reports RTB, but **after** the RTB default action was processed by the AI_PATROLZONE object. +-- +-- --- State transition function for the AI_PATROLZONE **Patrol** object +-- -- @param #AI_PATROLZONE self +-- -- @param Wrapper.Controllable#CONTROLLABLE AIGroup +-- -- @return #Wrapper.Controllable#CONTROLLABLE The new AIGroup object that is set to be patrolling the zone. +-- function Patrol:OnAfterRTB( AIGroup ) +-- return PatrolSpawn:Spawn() +-- end +-- +-- 1.3) Manage the AI_PATROLZONE parameters: +-- ------------------------------------------ +-- The following methods are available to modify the parameters of a AI_PATROLZONE object: +-- +-- * @{#AI_PATROLZONE.SetControllable}(): Set the AIControllable. +-- * @{#AI_PATROLZONE.GetControllable}(): Get the AIControllable. +-- * @{#AI_PATROLZONE.SetSpeed}(): Set the patrol speed of the AI, for the next patrol. +-- * @{#AI_PATROLZONE.SetAltitude}(): Set altitude of the AI, for the next patrol. +-- +-- 1.3) Manage the out of fuel in the AI_PATROLZONE: +-- ---------------------------------------------- +-- When the AIControllable is out of fuel, it is required that a new AIControllable is started, before the old AIControllable can return to the home base. +-- Therefore, with a parameter and a calculation of the distance to the home base, the fuel treshold is calculated. +-- When the fuel treshold is reached, the AIControllable will continue for a given time its patrol task in orbit, while a new AIControllable is targetted to the AI_PATROLZONE. +-- Once the time is finished, the old AIControllable will return to the base. +-- Use the method @{#AI_PATROLZONE.ManageFuel}() to have this proces in place. +-- +-- ==== +-- +-- **API CHANGE HISTORY** +-- ====================== +-- +-- The underlying change log documents the API changes. Please read this carefully. The following notation is used: +-- +-- * **Added** parts are expressed in bold type face. +-- * _Removed_ parts are expressed in italic type face. +-- +-- Hereby the change log: +-- +-- 2016-09-01: Initial class and API. +-- +-- === +-- +-- AUTHORS and CONTRIBUTIONS +-- ========================= +-- +-- ### Contributions: +-- +-- * **DutchBaron**: Testing. +-- * **Pikey**: Testing and API concept review. +-- +-- ### Authors: +-- +-- * **FlightControl**: Design & Programming. +-- +-- +-- @module Patrol + +-- State Transition Functions + +--- OnBefore State Transition Function +-- @function [parent=#AI_PATROLZONE] OnBeforeRoute +-- @param #AI_PATROLZONE self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable +-- @return #boolean + +--- OnAfter State Transition Function +-- @function [parent=#AI_PATROLZONE] OnAfterRoute +-- @param #AI_PATROLZONE self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable + + + +--- AI_PATROLZONE class +-- @type AI_PATROLZONE +-- @field Wrapper.Controllable#CONTROLLABLE AIControllable The @{Controllable} patrolling. +-- @field Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed. +-- @field Dcs.DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. +-- @field Dcs.DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. +-- @field Dcs.DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Controllable} in km/h. +-- @field Dcs.DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Controllable} in km/h. +-- @extends Core.Fsm#FSM_CONTROLLABLE +AI_PATROLZONE = { + ClassName = "AI_PATROLZONE", +} + + + +--- Creates a new AI_PATROLZONE object +-- @param #AI_PATROLZONE self +-- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed. +-- @param Dcs.DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. +-- @param Dcs.DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. +-- @param Dcs.DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Controllable} in km/h. +-- @param Dcs.DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Controllable} in km/h. +-- @return #AI_PATROLZONE self +-- @usage +-- -- Define a new AI_PATROLZONE Object. This PatrolArea will patrol an AIControllable within PatrolZone between 3000 and 6000 meters, with a variying speed between 600 and 900 km/h. +-- PatrolZone = ZONE:New( 'PatrolZone' ) +-- PatrolSpawn = SPAWN:New( 'Patrol Group' ) +-- PatrolArea = AI_PATROLZONE:New( PatrolZone, 3000, 6000, 600, 900 ) +function AI_PATROLZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed ) + + -- Inherits from BASE + local self = BASE:Inherit( self, FSM_CONTROLLABLE:New() ) -- Core.Fsm#FSM_CONTROLLABLE + + self:SetStartState( "None" ) + self:AddTransition( "*", "Start", "Route" ) + self:AddTransition( "*", "Route", "Route" ) + self:AddTransition( { "Patrol", "Route" }, "Patrol", "Patrol" ) + self:AddTransition( "Patrol", "RTB", "RTB" ) + self:AddTransition( "*", "End", "End" ) + self:AddTransition( "*", "Dead", "End" ) + + self.PatrolZone = PatrolZone + self.PatrolFloorAltitude = PatrolFloorAltitude + self.PatrolCeilingAltitude = PatrolCeilingAltitude + self.PatrolMinSpeed = PatrolMinSpeed + self.PatrolMaxSpeed = PatrolMaxSpeed + + return self +end + + + + +--- Sets (modifies) the minimum and maximum speed of the patrol. +-- @param #AI_PATROLZONE self +-- @param Dcs.DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Controllable} in km/h. +-- @param Dcs.DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Controllable} in km/h. +-- @return #AI_PATROLZONE self +function AI_PATROLZONE:SetSpeed( PatrolMinSpeed, PatrolMaxSpeed ) + self:F2( { PatrolMinSpeed, PatrolMaxSpeed } ) + + self.PatrolMinSpeed = PatrolMinSpeed + self.PatrolMaxSpeed = PatrolMaxSpeed +end + + + +--- Sets the floor and ceiling altitude of the patrol. +-- @param #AI_PATROLZONE self +-- @param Dcs.DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. +-- @param Dcs.DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. +-- @return #AI_PATROLZONE self +function AI_PATROLZONE:SetAltitude( PatrolFloorAltitude, PatrolCeilingAltitude ) + self:F2( { PatrolFloorAltitude, PatrolCeilingAltitude } ) + + self.PatrolFloorAltitude = PatrolFloorAltitude + self.PatrolCeilingAltitude = PatrolCeilingAltitude +end + + + +--- @param Wrapper.Controllable#CONTROLLABLE AIControllable +function _NewPatrolRoute( AIControllable ) + + AIControllable:T( "NewPatrolRoute" ) + local PatrolZone = AIControllable:GetState( AIControllable, "PatrolZone" ) -- PatrolCore.Zone#AI_PATROLZONE + PatrolZone:__Route( 1 ) +end + + + + +--- When the AIControllable is out of fuel, it is required that a new AIControllable is started, before the old AIControllable can return to the home base. +-- Therefore, with a parameter and a calculation of the distance to the home base, the fuel treshold is calculated. +-- When the fuel treshold is reached, the AIControllable will continue for a given time its patrol task in orbit, while a new AIControllable is targetted to the AI_PATROLZONE. +-- Once the time is finished, the old AIControllable will return to the base. +-- @param #AI_PATROLZONE self +-- @param #number PatrolFuelTresholdPercentage The treshold in percentage (between 0 and 1) when the AIControllable is considered to get out of fuel. +-- @param #number PatrolOutOfFuelOrbitTime The amount of seconds the out of fuel AIControllable will orbit before returning to the base. +-- @return #AI_PATROLZONE self +function AI_PATROLZONE:ManageFuel( PatrolFuelTresholdPercentage, PatrolOutOfFuelOrbitTime ) + + self.PatrolManageFuel = true + self.PatrolFuelTresholdPercentage = PatrolFuelTresholdPercentage + self.PatrolOutOfFuelOrbitTime = PatrolOutOfFuelOrbitTime + + return self +end + +--- Defines a new patrol route using the @{Process_PatrolZone} parameters and settings. +-- @param #AI_PATROLZONE self +-- @return #AI_PATROLZONE self +function AI_PATROLZONE:onenterRoute() + + self:F2() + + local PatrolRoute = {} + + if self.Controllable:IsAlive() then + --- Determine if the AIControllable is within the PatrolZone. + -- If not, make a waypoint within the to that the AIControllable will fly at maximum speed to that point. + +-- --- Calculate the current route point. +-- local CurrentVec2 = self.Controllable:GetVec2() +-- local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude() +-- local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) +-- local CurrentRoutePoint = CurrentPointVec3:RoutePointAir( +-- POINT_VEC3.RoutePointAltType.BARO, +-- POINT_VEC3.RoutePointType.TurningPoint, +-- POINT_VEC3.RoutePointAction.TurningPoint, +-- ToPatrolZoneSpeed, +-- true +-- ) +-- +-- PatrolRoute[#PatrolRoute+1] = CurrentRoutePoint + + self:T2( PatrolRoute ) + + if self.Controllable:IsNotInZone( self.PatrolZone ) then + --- Find a random 2D point in PatrolZone. + local ToPatrolZoneVec2 = self.PatrolZone:GetRandomVec2() + self:T2( ToPatrolZoneVec2 ) + + --- Define Speed and Altitude. + local ToPatrolZoneAltitude = math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ) + local ToPatrolZoneSpeed = self.PatrolMaxSpeed + self:T2( ToPatrolZoneSpeed ) + + --- Obtain a 3D @{Point} from the 2D point + altitude. + local ToPatrolZonePointVec3 = POINT_VEC3:New( ToPatrolZoneVec2.x, ToPatrolZoneAltitude, ToPatrolZoneVec2.y ) + + --- Create a route point of type air. + local ToPatrolZoneRoutePoint = ToPatrolZonePointVec3:RoutePointAir( + POINT_VEC3.RoutePointAltType.BARO, + POINT_VEC3.RoutePointType.TurningPoint, + POINT_VEC3.RoutePointAction.TurningPoint, + ToPatrolZoneSpeed, + true + ) + + PatrolRoute[#PatrolRoute+1] = ToPatrolZoneRoutePoint + + end + + --- Define a random point in the @{Zone}. The AI will fly to that point within the zone. + + --- Find a random 2D point in PatrolZone. + local ToTargetVec2 = self.PatrolZone:GetRandomVec2() + self:T2( ToTargetVec2 ) + + --- Define Speed and Altitude. + local ToTargetAltitude = math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ) + local ToTargetSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed ) + self:T2( { self.PatrolMinSpeed, self.PatrolMaxSpeed, ToTargetSpeed } ) + + --- Obtain a 3D @{Point} from the 2D point + altitude. + local ToTargetPointVec3 = POINT_VEC3:New( ToTargetVec2.x, ToTargetAltitude, ToTargetVec2.y ) + + --- Create a route point of type air. + local ToTargetRoutePoint = ToTargetPointVec3:RoutePointAir( + POINT_VEC3.RoutePointAltType.BARO, + POINT_VEC3.RoutePointType.TurningPoint, + POINT_VEC3.RoutePointAction.TurningPoint, + ToTargetSpeed, + true + ) + + --ToTargetPointVec3:SmokeRed() + + PatrolRoute[#PatrolRoute+1] = ToTargetRoutePoint + + --- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable... + self.Controllable:WayPointInitialize( PatrolRoute ) + + --- Do a trick, link the NewPatrolRoute function of the PATROLGROUP object to the AIControllable in a temporary variable ... + self.Controllable:SetState( self.Controllable, "PatrolZone", self ) + self.Controllable:WayPointFunction( #PatrolRoute, 1, "_NewPatrolRoute" ) + + --- NOW ACT_ROUTE THE GROUP! + self.Controllable:WayPointExecute( 1 ) + + self:__Patrol( 30 ) + end + +end + + +--- @param #AI_PATROLZONE self +function AI_PATROLZONE:onenterPatrol() + self:F2() + + if self.Controllable and self.Controllable:IsAlive() then + + local Fuel = self.Controllable:GetUnit(1):GetFuel() + if Fuel < self.PatrolFuelTresholdPercentage then + local OldAIControllable = self.Controllable + local AIControllableTemplate = self.Controllable:GetTemplate() + + local OrbitTask = OldAIControllable:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed ) + local TimedOrbitTask = OldAIControllable:TaskControlled( OrbitTask, OldAIControllable:TaskCondition(nil,nil,nil,nil,self.PatrolOutOfFuelOrbitTime,nil ) ) + OldAIControllable:SetTask( TimedOrbitTask, 10 ) + + self:RTB() + else + self:__Patrol( 30 ) -- Execute the Patrol event after 30 seconds. + end + end + +end +--- Management of logical cargo objects, that can be transported from and to transportation carriers. +-- +-- === +-- +-- Cargo can be of various forms, always are composed out of ONE object ( one unit or one static or one slingload crate ): +-- +-- * AI_CARGO_UNIT, represented by a @{Unit} in a @{Group}: Cargo can be represented by a Unit in a Group. Destruction of the Unit will mean that the cargo is lost. +-- * CARGO_STATIC, represented by a @{Static}: Cargo can be represented by a Static. Destruction of the Static will mean that the cargo is lost. +-- * AI_CARGO_PACKAGE, contained in a @{Unit} of a @{Group}: Cargo can be contained within a Unit of a Group. The cargo can be **delivered** by the @{Unit}. If the Unit is destroyed, the cargo will be destroyed also. +-- * AI_CARGO_PACKAGE, Contained in a @{Static}: Cargo can be contained within a Static. The cargo can be **collected** from the @Static. If the @{Static} is destroyed, the cargo will be destroyed. +-- * CARGO_SLINGLOAD, represented by a @{Cargo} that is transportable: Cargo can be represented by a Cargo object that is transportable. Destruction of the Cargo will mean that the cargo is lost. +-- +-- * AI_CARGO_GROUPED, represented by a Group of CARGO_UNITs. +-- +-- 1) @{AI.AI_Cargo#AI_CARGO} class, extends @{Core.Fsm#FSM_PROCESS} +-- ========================================================================== +-- The @{#AI_CARGO} class defines the core functions that defines a cargo object within MOOSE. +-- A cargo is a logical object defined that is available for transport, and has a life status within a simulation. +-- +-- The AI_CARGO is a state machine: it manages the different events and states of the cargo. +-- All derived classes from AI_CARGO follow the same state machine, expose the same cargo event functions, and provide the same cargo states. +-- +-- ## 1.2.1) AI_CARGO Events: +-- +-- * @{#AI_CARGO.Board}( ToCarrier ): Boards the cargo to a carrier. +-- * @{#AI_CARGO.Load}( ToCarrier ): Loads the cargo into a carrier, regardless of its position. +-- * @{#AI_CARGO.UnBoard}( ToPointVec2 ): UnBoard the cargo from a carrier. This will trigger a movement of the cargo to the option ToPointVec2. +-- * @{#AI_CARGO.UnLoad}( ToPointVec2 ): UnLoads the cargo from a carrier. +-- * @{#AI_CARGO.Dead}( Controllable ): The cargo is dead. The cargo process will be ended. +-- +-- ## 1.2.2) AI_CARGO States: +-- +-- * **UnLoaded**: The cargo is unloaded from a carrier. +-- * **Boarding**: The cargo is currently boarding (= running) into a carrier. +-- * **Loaded**: The cargo is loaded into a carrier. +-- * **UnBoarding**: The cargo is currently unboarding (=running) from a carrier. +-- * **Dead**: The cargo is dead ... +-- * **End**: The process has come to an end. +-- +-- ## 1.2.3) AI_CARGO state transition methods: +-- +-- State transition functions can be set **by the mission designer** customizing or improving the behaviour of the state. +-- There are 2 moments when state transition methods will be called by the state machine: +-- +-- * **Before** the state transition. +-- The state transition method needs to start with the name **OnBefore + the name of the state**. +-- If the state transition method returns false, then the processing of the state transition will not be done! +-- If you want to change the behaviour of the AIControllable at this event, return false, +-- but then you'll need to specify your own logic using the AIControllable! +-- +-- * **After** the state transition. +-- The state transition method needs to start with the name **OnAfter + the name of the state**. +-- These state transition methods need to provide a return value, which is specified at the function description. +-- +-- 2) #AI_CARGO_UNIT class +-- ==================== +-- The AI_CARGO_UNIT class defines a cargo that is represented by a UNIT object within the simulator, and can be transported by a carrier. +-- Use the event functions as described above to Load, UnLoad, Board, UnBoard the AI_CARGO_UNIT objects to and from carriers. +-- +-- 5) #AI_CARGO_GROUPED class +-- ======================= +-- The AI_CARGO_GROUPED class defines a cargo that is represented by a group of UNIT objects within the simulator, and can be transported by a carrier. +-- Use the event functions as described above to Load, UnLoad, Board, UnBoard the AI_CARGO_UNIT objects to and from carriers. +-- +-- This module is still under construction, but is described above works already, and will keep working ... +-- +-- @module Cargo + +-- Events + +-- Board + +--- Boards the cargo to a Carrier. The event will create a movement (= running or driving) of the cargo to the Carrier. +-- The cargo must be in the **UnLoaded** state. +-- @function [parent=#AI_CARGO] Board +-- @param #AI_CARGO self +-- @param Wrapper.Controllable#CONTROLLABLE ToCarrier The Carrier that will hold the cargo. + +--- Boards the cargo to a Carrier. The event will create a movement (= running or driving) of the cargo to the Carrier. +-- The cargo must be in the **UnLoaded** state. +-- @function [parent=#AI_CARGO] __Board +-- @param #AI_CARGO self +-- @param #number DelaySeconds The amount of seconds to delay the action. +-- @param Wrapper.Controllable#CONTROLLABLE ToCarrier The Carrier that will hold the cargo. + + +-- UnBoard + +--- UnBoards the cargo to a Carrier. The event will create a movement (= running or driving) of the cargo from the Carrier. +-- The cargo must be in the **Loaded** state. +-- @function [parent=#AI_CARGO] UnBoard +-- @param #AI_CARGO self +-- @param Core.Point#POINT_VEC2 ToPointVec2 (optional) @{Core.Point#POINT_VEC2) to where the cargo should run after onboarding. If not provided, the cargo will run to 60 meters behind the Carrier location. + +--- UnBoards the cargo to a Carrier. The event will create a movement (= running or driving) of the cargo from the Carrier. +-- The cargo must be in the **Loaded** state. +-- @function [parent=#AI_CARGO] __UnBoard +-- @param #AI_CARGO self +-- @param #number DelaySeconds The amount of seconds to delay the action. +-- @param Core.Point#POINT_VEC2 ToPointVec2 (optional) @{Core.Point#POINT_VEC2) to where the cargo should run after onboarding. If not provided, the cargo will run to 60 meters behind the Carrier location. + + +-- Load + +--- Loads the cargo to a Carrier. The event will load the cargo into the Carrier regardless of its position. There will be no movement simulated of the cargo loading. +-- The cargo must be in the **UnLoaded** state. +-- @function [parent=#AI_CARGO] Load +-- @param #AI_CARGO self +-- @param Wrapper.Controllable#CONTROLLABLE ToCarrier The Carrier that will hold the cargo. + +--- Loads the cargo to a Carrier. The event will load the cargo into the Carrier regardless of its position. There will be no movement simulated of the cargo loading. +-- The cargo must be in the **UnLoaded** state. +-- @function [parent=#AI_CARGO] __Load +-- @param #AI_CARGO self +-- @param #number DelaySeconds The amount of seconds to delay the action. +-- @param Wrapper.Controllable#CONTROLLABLE ToCarrier The Carrier that will hold the cargo. + + +-- UnLoad + +--- UnLoads the cargo to a Carrier. The event will unload the cargo from the Carrier. There will be no movement simulated of the cargo loading. +-- The cargo must be in the **Loaded** state. +-- @function [parent=#AI_CARGO] UnLoad +-- @param #AI_CARGO self +-- @param Core.Point#POINT_VEC2 ToPointVec2 (optional) @{Core.Point#POINT_VEC2) to where the cargo will be placed after unloading. If not provided, the cargo will be placed 60 meters behind the Carrier location. + +--- UnLoads the cargo to a Carrier. The event will unload the cargo from the Carrier. There will be no movement simulated of the cargo loading. +-- The cargo must be in the **Loaded** state. +-- @function [parent=#AI_CARGO] __UnLoad +-- @param #AI_CARGO self +-- @param #number DelaySeconds The amount of seconds to delay the action. +-- @param Core.Point#POINT_VEC2 ToPointVec2 (optional) @{Core.Point#POINT_VEC2) to where the cargo will be placed after unloading. If not provided, the cargo will be placed 60 meters behind the Carrier location. + +-- State Transition Functions + +-- UnLoaded + +--- @function [parent=#AI_CARGO] OnBeforeUnLoaded +-- @param #AI_CARGO self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable +-- @return #boolean + +--- @function [parent=#AI_CARGO] OnAfterUnLoaded +-- @param #AI_CARGO self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable + +-- Loaded + +--- @function [parent=#AI_CARGO] OnBeforeLoaded +-- @param #AI_CARGO self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable +-- @return #boolean + +--- @function [parent=#AI_CARGO] OnAfterLoaded +-- @param #AI_CARGO self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable + +-- Boarding + +--- @function [parent=#AI_CARGO] OnBeforeBoarding +-- @param #AI_CARGO self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable +-- @return #boolean + +--- @function [parent=#AI_CARGO] OnAfterBoarding +-- @param #AI_CARGO self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable + +-- UnBoarding + +--- @function [parent=#AI_CARGO] OnBeforeUnBoarding +-- @param #AI_CARGO self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable +-- @return #boolean + +--- @function [parent=#AI_CARGO] OnAfterUnBoarding +-- @param #AI_CARGO self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable + + +-- TODO: Find all Carrier objects and make the type of the Carriers Wrapper.Unit#UNIT in the documentation. + +CARGOS = {} + +do -- AI_CARGO + + --- @type AI_CARGO + -- @extends Core.Fsm#FSM_PROCESS + -- @field #string Type A string defining the type of the cargo. eg. Engineers, Equipment, Screwdrivers. + -- @field #string Name A string defining the name of the cargo. The name is the unique identifier of the cargo. + -- @field #number Weight A number defining the weight of the cargo. The weight is expressed in kg. + -- @field #number ReportRadius (optional) A number defining the radius in meters when the cargo is signalling or reporting to a Carrier. + -- @field #number NearRadius (optional) A number defining the radius in meters when the cargo is near to a Carrier, so that it can be loaded. + -- @field Wrapper.Controllable#CONTROLLABLE CargoObject The alive DCS object representing the cargo. This value can be nil, meaning, that the cargo is not represented anywhere... + -- @field Wrapper.Controllable#CONTROLLABLE CargoCarrier The alive DCS object carrying the cargo. This value can be nil, meaning, that the cargo is not contained anywhere... + -- @field #boolean Slingloadable This flag defines if the cargo can be slingloaded. + -- @field #boolean Moveable This flag defines if the cargo is moveable. + -- @field #boolean Representable This flag defines if the cargo can be represented by a DCS Unit. + -- @field #boolean Containable This flag defines if the cargo can be contained within a DCS Unit. + AI_CARGO = { + ClassName = "AI_CARGO", + Type = nil, + Name = nil, + Weight = nil, + CargoObject = nil, + CargoCarrier = nil, + Representable = false, + Slingloadable = false, + Moveable = false, + Containable = false, + } + +--- @type AI_CARGO.CargoObjects +-- @map < #string, Wrapper.Positionable#POSITIONABLE > The alive POSITIONABLE objects representing the the cargo. + + +--- AI_CARGO Constructor. This class is an abstract class and should not be instantiated. +-- @param #AI_CARGO self +-- @param #string Type +-- @param #string Name +-- @param #number Weight +-- @param #number ReportRadius (optional) +-- @param #number NearRadius (optional) +-- @return #AI_CARGO +function AI_CARGO:New( Type, Name, Weight, ReportRadius, NearRadius ) + + local self = BASE:Inherit( self, FSM:New() ) -- Core.Fsm#FSM_CONTROLLABLE + self:F( { Type, Name, Weight, ReportRadius, NearRadius } ) + + self:SetStartState( "UnLoaded" ) + self:AddTransition( "UnLoaded", "Board", "Boarding" ) + self:AddTransition( "Boarding", "Boarding", "Boarding" ) + self:AddTransition( "Boarding", "Load", "Loaded" ) + self:AddTransition( "UnLoaded", "Load", "Loaded" ) + self:AddTransition( "Loaded", "UnBoard", "UnBoarding" ) + self:AddTransition( "UnBoarding", "UnBoarding", "UnBoarding" ) + self:AddTransition( "UnBoarding", "UnLoad", "UnLoaded" ) + self:AddTransition( "Loaded", "UnLoad", "UnLoaded" ) + + + self.Type = Type + self.Name = Name + self.Weight = Weight + self.ReportRadius = ReportRadius + self.NearRadius = NearRadius + self.CargoObject = nil + self.CargoCarrier = nil + self.Representable = false + self.Slingloadable = false + self.Moveable = false + self.Containable = false + + + self.CargoScheduler = SCHEDULER:New() + + CARGOS[self.Name] = self + + return self +end + + +--- Template method to spawn a new representation of the AI_CARGO in the simulator. +-- @param #AI_CARGO self +-- @return #AI_CARGO +function AI_CARGO:Spawn( PointVec2 ) + self:F() + +end + + +--- Check if CargoCarrier is near the Cargo to be Loaded. +-- @param #AI_CARGO self +-- @param Core.Point#POINT_VEC2 PointVec2 +-- @return #boolean +function AI_CARGO:IsNear( PointVec2 ) + self:F( { PointVec2 } ) + + local Distance = PointVec2:DistanceFromPointVec2( self.CargoObject:GetPointVec2() ) + self:T( Distance ) + + if Distance <= self.NearRadius then + return true + else + return false + end +end + +end + +do -- AI_CARGO_REPRESENTABLE + + --- @type AI_CARGO_REPRESENTABLE + -- @extends #AI_CARGO + AI_CARGO_REPRESENTABLE = { + ClassName = "AI_CARGO_REPRESENTABLE" + } + +--- AI_CARGO_REPRESENTABLE Constructor. +-- @param #AI_CARGO_REPRESENTABLE self +-- @param Wrapper.Controllable#Controllable CargoObject +-- @param #string Type +-- @param #string Name +-- @param #number Weight +-- @param #number ReportRadius (optional) +-- @param #number NearRadius (optional) +-- @return #AI_CARGO_REPRESENTABLE +function AI_CARGO_REPRESENTABLE:New( CargoObject, Type, Name, Weight, ReportRadius, NearRadius ) + local self = BASE:Inherit( self, AI_CARGO:New( Type, Name, Weight, ReportRadius, NearRadius ) ) -- #AI_CARGO + self:F( { Type, Name, Weight, ReportRadius, NearRadius } ) + + return self +end + +--- Route a cargo unit to a PointVec2. +-- @param #AI_CARGO_REPRESENTABLE self +-- @param Core.Point#POINT_VEC2 ToPointVec2 +-- @param #number Speed +-- @return #AI_CARGO_REPRESENTABLE +function AI_CARGO_REPRESENTABLE:RouteTo( ToPointVec2, Speed ) + self:F2( ToPointVec2 ) + + local Points = {} + + local PointStartVec2 = self.CargoObject:GetPointVec2() + + Points[#Points+1] = PointStartVec2:RoutePointGround( Speed ) + Points[#Points+1] = ToPointVec2:RoutePointGround( Speed ) + + local TaskRoute = self.CargoObject:TaskRoute( Points ) + self.CargoObject:SetTask( TaskRoute, 2 ) + return self +end + +end -- AI_CARGO + +do -- AI_CARGO_UNIT + + --- @type AI_CARGO_UNIT + -- @extends #AI_CARGO_REPRESENTABLE + AI_CARGO_UNIT = { + ClassName = "AI_CARGO_UNIT" + } + +--- AI_CARGO_UNIT Constructor. +-- @param #AI_CARGO_UNIT self +-- @param Wrapper.Unit#UNIT CargoUnit +-- @param #string Type +-- @param #string Name +-- @param #number Weight +-- @param #number ReportRadius (optional) +-- @param #number NearRadius (optional) +-- @return #AI_CARGO_UNIT +function AI_CARGO_UNIT:New( CargoUnit, Type, Name, Weight, ReportRadius, NearRadius ) + local self = BASE:Inherit( self, AI_CARGO_REPRESENTABLE:New( CargoUnit, Type, Name, Weight, ReportRadius, NearRadius ) ) -- #AI_CARGO_UNIT + self:F( { Type, Name, Weight, ReportRadius, NearRadius } ) + + self:T( CargoUnit ) + self.CargoObject = CargoUnit + + self:T( self.ClassName ) + + return self +end + +--- Enter UnBoarding State. +-- @param #AI_CARGO_UNIT self +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Core.Point#POINT_VEC2 ToPointVec2 +function AI_CARGO_UNIT:onenterUnBoarding( Event, From, To, ToPointVec2 ) + self:F() + + local Angle = 180 + local Speed = 10 + local DeployDistance = 5 + local RouteDistance = 60 + + if From == "Loaded" then + + local CargoCarrierPointVec2 = self.CargoCarrier:GetPointVec2() + local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. + local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) + local CargoDeployPointVec2 = CargoCarrierPointVec2:Translate( DeployDistance, CargoDeployHeading ) + local CargoRoutePointVec2 = CargoCarrierPointVec2:Translate( RouteDistance, CargoDeployHeading ) + + -- if there is no ToPointVec2 given, then use the CargoRoutePointVec2 + ToPointVec2 = ToPointVec2 or CargoRoutePointVec2 + + local FromPointVec2 = CargoCarrierPointVec2 + + -- Respawn the group... + if self.CargoObject then + self.CargoObject:ReSpawn( CargoDeployPointVec2:GetVec3(), CargoDeployHeading ) + self.CargoCarrier = nil + + local Points = {} + Points[#Points+1] = FromPointVec2:RoutePointGround( Speed ) + Points[#Points+1] = ToPointVec2:RoutePointGround( Speed ) + + local TaskRoute = self.CargoObject:TaskRoute( Points ) + self.CargoObject:SetTask( TaskRoute, 1 ) + + self:__UnBoarding( 1, ToPointVec2 ) + end + end + +end + +--- Leave UnBoarding State. +-- @param #AI_CARGO_UNIT self +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Core.Point#POINT_VEC2 ToPointVec2 +function AI_CARGO_UNIT:onleaveUnBoarding( Event, From, To, ToPointVec2 ) + self:F( { ToPointVec2, Event, From, To } ) + + local Angle = 180 + local Speed = 10 + local Distance = 5 + + if From == "UnBoarding" then + if self:IsNear( ToPointVec2 ) then + return true + else + self:__UnBoarding( 1, ToPointVec2 ) + end + return false + end + +end + +--- UnBoard Event. +-- @param #AI_CARGO_UNIT self +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Core.Point#POINT_VEC2 ToPointVec2 +function AI_CARGO_UNIT:onafterUnBoarding( Event, From, To, ToPointVec2 ) + self:F( { ToPointVec2, Event, From, To } ) + + self.CargoInAir = self.CargoObject:InAir() + + self:T( self.CargoInAir ) + + -- Only unboard the cargo when the carrier is not in the air. + -- (eg. cargo can be on a oil derrick, moving the cargo on the oil derrick will drop the cargo on the sea). + if not self.CargoInAir then + + end + + self:__UnLoad( 1, ToPointVec2 ) + +end + + + +--- Enter UnLoaded State. +-- @param #AI_CARGO_UNIT self +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Core.Point#POINT_VEC2 +function AI_CARGO_UNIT:onenterUnLoaded( Event, From, To, ToPointVec2 ) + self:F( { ToPointVec2, Event, From, To } ) + + local Angle = 180 + local Speed = 10 + local Distance = 5 + + if From == "Loaded" then + local StartPointVec2 = self.CargoCarrier:GetPointVec2() + local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. + local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) + local CargoDeployPointVec2 = StartPointVec2:Translate( Distance, CargoDeployHeading ) + + ToPointVec2 = ToPointVec2 or POINT_VEC2:New( CargoDeployPointVec2:GetX(), CargoDeployPointVec2:GetY() ) + + -- Respawn the group... + if self.CargoObject then + self.CargoObject:ReSpawn( ToPointVec2:GetVec3(), 0 ) + self.CargoCarrier = nil + end + + end + + if self.OnUnLoadedCallBack then + self.OnUnLoadedCallBack( self, unpack( self.OnUnLoadedParameters ) ) + self.OnUnLoadedCallBack = nil + end + +end + + + +--- Enter Boarding State. +-- @param #AI_CARGO_UNIT self +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Wrapper.Unit#UNIT CargoCarrier +function AI_CARGO_UNIT:onenterBoarding( Event, From, To, CargoCarrier ) + self:F( { CargoCarrier.UnitName, Event, From, To } ) + + local Speed = 10 + local Angle = 180 + local Distance = 5 + + if From == "UnLoaded" then + local CargoCarrierPointVec2 = CargoCarrier:GetPointVec2() + local CargoCarrierHeading = CargoCarrier:GetHeading() -- Get Heading of object in degrees. + local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) + local CargoDeployPointVec2 = CargoCarrierPointVec2:Translate( Distance, CargoDeployHeading ) + + local Points = {} + + local PointStartVec2 = self.CargoObject:GetPointVec2() + + Points[#Points+1] = PointStartVec2:RoutePointGround( Speed ) + Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) + + local TaskRoute = self.CargoObject:TaskRoute( Points ) + self.CargoObject:SetTask( TaskRoute, 2 ) + end + +end + +--- Leave Boarding State. +-- @param #AI_CARGO_UNIT self +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Wrapper.Unit#UNIT CargoCarrier +function AI_CARGO_UNIT:onleaveBoarding( Event, From, To, CargoCarrier ) + self:F( { CargoCarrier.UnitName, Event, From, To } ) + + if self:IsNear( CargoCarrier:GetPointVec2() ) then + self:__Load( 1, CargoCarrier ) + return true + else + self:__Boarding( 1, CargoCarrier ) + end + return false +end + +--- Loaded State. +-- @param #AI_CARGO_UNIT self +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Wrapper.Unit#UNIT CargoCarrier +function AI_CARGO_UNIT:onenterLoaded( Event, From, To, CargoCarrier ) + self:F() + + self.CargoCarrier = CargoCarrier + + -- Only destroy the CargoObject is if there is a CargoObject (packages don't have CargoObjects). + if self.CargoObject then + self:T("Destroying") + self.CargoObject:Destroy() + end +end + + +--- Board Event. +-- @param #AI_CARGO_UNIT self +-- @param #string Event +-- @param #string From +-- @param #string To +function AI_CARGO_UNIT:onafterBoard( Event, From, To, CargoCarrier ) + self:F() + + self.CargoInAir = self.CargoObject:InAir() + + self:T( self.CargoInAir ) + + -- Only move the group to the carrier when the cargo is not in the air + -- (eg. cargo can be on a oil derrick, moving the cargo on the oil derrick will drop the cargo on the sea). + if not self.CargoInAir then + self:Load( CargoCarrier ) + end + +end + +end + +do -- AI_CARGO_PACKAGE + + --- @type AI_CARGO_PACKAGE + -- @extends #AI_CARGO_REPRESENTABLE + AI_CARGO_PACKAGE = { + ClassName = "AI_CARGO_PACKAGE" + } + +--- AI_CARGO_PACKAGE Constructor. +-- @param #AI_CARGO_PACKAGE self +-- @param Wrapper.Unit#UNIT CargoCarrier The UNIT carrying the package. +-- @param #string Type +-- @param #string Name +-- @param #number Weight +-- @param #number ReportRadius (optional) +-- @param #number NearRadius (optional) +-- @return #AI_CARGO_PACKAGE +function AI_CARGO_PACKAGE:New( CargoCarrier, Type, Name, Weight, ReportRadius, NearRadius ) + local self = BASE:Inherit( self, AI_CARGO_REPRESENTABLE:New( CargoCarrier, Type, Name, Weight, ReportRadius, NearRadius ) ) -- #AI_CARGO_PACKAGE + self:F( { Type, Name, Weight, ReportRadius, NearRadius } ) + + self:T( CargoCarrier ) + self.CargoCarrier = CargoCarrier + + return self +end + +--- Board Event. +-- @param #AI_CARGO_PACKAGE self +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Wrapper.Unit#UNIT CargoCarrier +-- @param #number Speed +-- @param #number BoardDistance +-- @param #number Angle +function AI_CARGO_PACKAGE:onafterOnBoard( Event, From, To, CargoCarrier, Speed, BoardDistance, LoadDistance, Angle ) + self:F() + + self.CargoInAir = self.CargoCarrier:InAir() + + self:T( self.CargoInAir ) + + -- Only move the CargoCarrier to the New CargoCarrier when the New CargoCarrier is not in the air. + if not self.CargoInAir then + + local Points = {} + + local StartPointVec2 = self.CargoCarrier:GetPointVec2() + local CargoCarrierHeading = CargoCarrier:GetHeading() -- Get Heading of object in degrees. + local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) + self:T( { CargoCarrierHeading, CargoDeployHeading } ) + local CargoDeployPointVec2 = CargoCarrier:GetPointVec2():Translate( BoardDistance, CargoDeployHeading ) + + Points[#Points+1] = StartPointVec2:RoutePointGround( Speed ) + Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) + + local TaskRoute = self.CargoCarrier:TaskRoute( Points ) + self.CargoCarrier:SetTask( TaskRoute, 1 ) + end + + self:Boarded( CargoCarrier, Speed, BoardDistance, LoadDistance, Angle ) + +end + +--- Check if CargoCarrier is near the Cargo to be Loaded. +-- @param #AI_CARGO_PACKAGE self +-- @param Wrapper.Unit#UNIT CargoCarrier +-- @return #boolean +function AI_CARGO_PACKAGE:IsNear( CargoCarrier ) + self:F() + + local CargoCarrierPoint = CargoCarrier:GetPointVec2() + + local Distance = CargoCarrierPoint:DistanceFromPointVec2( self.CargoCarrier:GetPointVec2() ) + self:T( Distance ) + + if Distance <= self.NearRadius then + return true + else + return false + end +end + +--- Boarded Event. +-- @param #AI_CARGO_PACKAGE self +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Wrapper.Unit#UNIT CargoCarrier +function AI_CARGO_PACKAGE:onafterOnBoarded( Event, From, To, CargoCarrier, Speed, BoardDistance, LoadDistance, Angle ) + self:F() + + if self:IsNear( CargoCarrier ) then + self:__Load( 1, CargoCarrier, Speed, LoadDistance, Angle ) + else + self:__Boarded( 1, CargoCarrier, Speed, BoardDistance, LoadDistance, Angle ) + end +end + +--- UnBoard Event. +-- @param #AI_CARGO_PACKAGE self +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param #number Speed +-- @param #number UnLoadDistance +-- @param #number UnBoardDistance +-- @param #number Radius +-- @param #number Angle +function AI_CARGO_PACKAGE:onafterUnBoard( Event, From, To, CargoCarrier, Speed, UnLoadDistance, UnBoardDistance, Radius, Angle ) + self:F() + + self.CargoInAir = self.CargoCarrier:InAir() + + self:T( self.CargoInAir ) + + -- Only unboard the cargo when the carrier is not in the air. + -- (eg. cargo can be on a oil derrick, moving the cargo on the oil derrick will drop the cargo on the sea). + if not self.CargoInAir then + + self:_Next( self.FsmP.UnLoad, UnLoadDistance, Angle ) + + local Points = {} + + local StartPointVec2 = CargoCarrier:GetPointVec2() + local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. + local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) + self:T( { CargoCarrierHeading, CargoDeployHeading } ) + local CargoDeployPointVec2 = StartPointVec2:Translate( UnBoardDistance, CargoDeployHeading ) + + Points[#Points+1] = StartPointVec2:RoutePointGround( Speed ) + Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) + + local TaskRoute = CargoCarrier:TaskRoute( Points ) + CargoCarrier:SetTask( TaskRoute, 1 ) + end + + self:__UnBoarded( 1 , CargoCarrier, Speed ) + +end + +--- UnBoarded Event. +-- @param #AI_CARGO_PACKAGE self +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Wrapper.Unit#UNIT CargoCarrier +function AI_CARGO_PACKAGE:onafterUnBoarded( Event, From, To, CargoCarrier, Speed ) + self:F() + + if self:IsNear( CargoCarrier ) then + self:__UnLoad( 1, CargoCarrier, Speed ) + else + self:__UnBoarded( 1, CargoCarrier, Speed ) + end +end + +--- Load Event. +-- @param #AI_CARGO_PACKAGE self +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Wrapper.Unit#UNIT CargoCarrier +-- @param #number Speed +-- @param #number LoadDistance +-- @param #number Angle +function AI_CARGO_PACKAGE:onafterLoad( Event, From, To, CargoCarrier, Speed, LoadDistance, Angle ) + self:F() + + self.CargoCarrier = CargoCarrier + + local StartPointVec2 = self.CargoCarrier:GetPointVec2() + local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. + local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) + local CargoDeployPointVec2 = StartPointVec2:Translate( LoadDistance, CargoDeployHeading ) + + local Points = {} + Points[#Points+1] = StartPointVec2:RoutePointGround( Speed ) + Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) + + local TaskRoute = self.CargoCarrier:TaskRoute( Points ) + self.CargoCarrier:SetTask( TaskRoute, 1 ) + +end + +--- UnLoad Event. +-- @param #AI_CARGO_PACKAGE self +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param #number Distance +-- @param #number Angle +function AI_CARGO_PACKAGE:onafterUnLoad( Event, From, To, CargoCarrier, Speed, Distance, Angle ) + self:F() + + local StartPointVec2 = self.CargoCarrier:GetPointVec2() + local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. + local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) + local CargoDeployPointVec2 = StartPointVec2:Translate( Distance, CargoDeployHeading ) + + self.CargoCarrier = CargoCarrier + + local Points = {} + Points[#Points+1] = StartPointVec2:RoutePointGround( Speed ) + Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) + + local TaskRoute = self.CargoCarrier:TaskRoute( Points ) + self.CargoCarrier:SetTask( TaskRoute, 1 ) + +end + + +end + +do -- AI_CARGO_GROUP + + --- @type AI_CARGO_GROUP + -- @extends AI.AI_Cargo#AI_CARGO + -- @field Set#SET_BASE CargoSet A set of cargo objects. + -- @field #string Name A string defining the name of the cargo group. The name is the unique identifier of the cargo. + AI_CARGO_GROUP = { + ClassName = "AI_CARGO_GROUP", + } + +--- AI_CARGO_GROUP constructor. +-- @param #AI_CARGO_GROUP self +-- @param Core.Set#Set_BASE CargoSet +-- @param #string Type +-- @param #string Name +-- @param #number Weight +-- @param #number ReportRadius (optional) +-- @param #number NearRadius (optional) +-- @return #AI_CARGO_GROUP +function AI_CARGO_GROUP:New( CargoSet, Type, Name, ReportRadius, NearRadius ) + local self = BASE:Inherit( self, AI_CARGO:New( Type, Name, 0, ReportRadius, NearRadius ) ) -- #AI_CARGO_GROUP + self:F( { Type, Name, ReportRadius, NearRadius } ) + + self.CargoSet = CargoSet + + + return self +end + +end -- AI_CARGO_GROUP + +do -- AI_CARGO_GROUPED + + --- @type AI_CARGO_GROUPED + -- @extends AI.AI_Cargo#AI_CARGO_GROUP + AI_CARGO_GROUPED = { + ClassName = "AI_CARGO_GROUPED", + } + +--- AI_CARGO_GROUPED constructor. +-- @param #AI_CARGO_GROUPED self +-- @param Core.Set#Set_BASE CargoSet +-- @param #string Type +-- @param #string Name +-- @param #number Weight +-- @param #number ReportRadius (optional) +-- @param #number NearRadius (optional) +-- @return #AI_CARGO_GROUPED +function AI_CARGO_GROUPED:New( CargoSet, Type, Name, ReportRadius, NearRadius ) + local self = BASE:Inherit( self, AI_CARGO_GROUP:New( CargoSet, Type, Name, ReportRadius, NearRadius ) ) -- #AI_CARGO_GROUPED + self:F( { Type, Name, ReportRadius, NearRadius } ) + + return self +end + +--- Enter Boarding State. +-- @param #AI_CARGO_GROUPED self +-- @param Wrapper.Unit#UNIT CargoCarrier +-- @param #string Event +-- @param #string From +-- @param #string To +function AI_CARGO_GROUPED:onenterBoarding( Event, From, To, CargoCarrier ) + self:F( { CargoCarrier.UnitName, Event, From, To } ) + + if From == "UnLoaded" then + + -- For each Cargo object within the AI_CARGO_GROUPED, route each object to the CargoLoadPointVec2 + self.CargoSet:ForEach( + function( Cargo ) + Cargo:__Board( 1, CargoCarrier ) + end + ) + + self:__Boarding( 1, CargoCarrier ) + end + +end + +--- Enter Loaded State. +-- @param #AI_CARGO_GROUPED self +-- @param Wrapper.Unit#UNIT CargoCarrier +-- @param #string Event +-- @param #string From +-- @param #string To +function AI_CARGO_GROUPED:onenterLoaded( Event, From, To, CargoCarrier ) + self:F( { CargoCarrier.UnitName, Event, From, To } ) + + if From == "UnLoaded" then + -- For each Cargo object within the AI_CARGO_GROUPED, load each cargo to the CargoCarrier. + for CargoID, Cargo in pairs( self.CargoSet:GetSet() ) do + Cargo:Load( CargoCarrier ) + end + end +end + +--- Leave Boarding State. +-- @param #AI_CARGO_GROUPED self +-- @param Wrapper.Unit#UNIT CargoCarrier +-- @param #string Event +-- @param #string From +-- @param #string To +function AI_CARGO_GROUPED:onleaveBoarding( Event, From, To, CargoCarrier ) + self:F( { CargoCarrier.UnitName, Event, From, To } ) + + local Boarded = true + + -- For each Cargo object within the AI_CARGO_GROUPED, route each object to the CargoLoadPointVec2 + for CargoID, Cargo in pairs( self.CargoSet:GetSet() ) do + self:T( Cargo.current ) + if not Cargo:is( "Loaded" ) then + Boarded = false + end + end + + if not Boarded then + self:__Boarding( 1, CargoCarrier ) + else + self:__Load( 1, CargoCarrier ) + end + return Boarded +end + +--- Enter UnBoarding State. +-- @param #AI_CARGO_GROUPED self +-- @param Core.Point#POINT_VEC2 ToPointVec2 +-- @param #string Event +-- @param #string From +-- @param #string To +function AI_CARGO_GROUPED:onenterUnBoarding( Event, From, To, ToPointVec2 ) + self:F() + + local Timer = 1 + + if From == "Loaded" then + + -- For each Cargo object within the AI_CARGO_GROUPED, route each object to the CargoLoadPointVec2 + self.CargoSet:ForEach( + function( Cargo ) + Cargo:__UnBoard( Timer, ToPointVec2 ) + Timer = Timer + 10 + end + ) + + self:__UnBoarding( 1, ToPointVec2 ) + end + +end + +--- Leave UnBoarding State. +-- @param #AI_CARGO_GROUPED self +-- @param Core.Point#POINT_VEC2 ToPointVec2 +-- @param #string Event +-- @param #string From +-- @param #string To +function AI_CARGO_GROUPED:onleaveUnBoarding( Event, From, To, ToPointVec2 ) + self:F( { ToPointVec2, Event, From, To } ) + + local Angle = 180 + local Speed = 10 + local Distance = 5 + + if From == "UnBoarding" then + local UnBoarded = true + + -- For each Cargo object within the AI_CARGO_GROUPED, route each object to the CargoLoadPointVec2 + for CargoID, Cargo in pairs( self.CargoSet:GetSet() ) do + self:T( Cargo.current ) + if not Cargo:is( "UnLoaded" ) then + UnBoarded = false + end + end + + if UnBoarded then + return true + else + self:__UnBoarding( 1, ToPointVec2 ) + end + + return false + end + +end + +--- UnBoard Event. +-- @param #AI_CARGO_GROUPED self +-- @param Core.Point#POINT_VEC2 ToPointVec2 +-- @param #string Event +-- @param #string From +-- @param #string To +function AI_CARGO_GROUPED:onafterUnBoarding( Event, From, To, ToPointVec2 ) + self:F( { ToPointVec2, Event, From, To } ) + + self:__UnLoad( 1, ToPointVec2 ) +end + + + +--- Enter UnLoaded State. +-- @param #AI_CARGO_GROUPED self +-- @param Core.Point#POINT_VEC2 +-- @param #string Event +-- @param #string From +-- @param #string To +function AI_CARGO_GROUPED:onenterUnLoaded( Event, From, To, ToPointVec2 ) + self:F( { ToPointVec2, Event, From, To } ) + + if From == "Loaded" then + + -- For each Cargo object within the AI_CARGO_GROUPED, route each object to the CargoLoadPointVec2 + self.CargoSet:ForEach( + function( Cargo ) + Cargo:UnLoad( ToPointVec2 ) + end + ) + + end + +end + +end -- AI_CARGO_GROUPED + + + +--- (SP) (MP) (FSM) Accept or reject process for player (task) assignments. +-- +-- === +-- +-- # @{#ACT_ASSIGN} FSM template class, extends @{Core.Fsm#FSM_PROCESS} +-- +-- ## ACT_ASSIGN state machine: +-- +-- This class is a state machine: it manages a process that is triggered by events causing state transitions to occur. +-- All derived classes from this class will start with the class name, followed by a \_. See the relevant derived class descriptions below. +-- Each derived class follows exactly the same process, using the same events and following the same state transitions, +-- but will have **different implementation behaviour** upon each event or state transition. +-- +-- ### ACT_ASSIGN **Events**: +-- +-- These are the events defined in this class: +-- +-- * **Start**: Start the tasking acceptance process. +-- * **Assign**: Assign the task. +-- * **Reject**: Reject the task.. +-- +-- ### ACT_ASSIGN **Event methods**: +-- +-- Event methods are available (dynamically allocated by the state machine), that accomodate for state transitions occurring in the process. +-- There are two types of event methods, which you can use to influence the normal mechanisms in the state machine: +-- +-- * **Immediate**: The event method has exactly the name of the event. +-- * **Delayed**: The event method starts with a __ + the name of the event. The first parameter of the event method is a number value, expressing the delay in seconds when the event will be executed. +-- +-- ### ACT_ASSIGN **States**: +-- +-- * **UnAssigned**: The player has not accepted the task. +-- * **Assigned (*)**: The player has accepted the task. +-- * **Rejected (*)**: The player has not accepted the task. +-- * **Waiting**: The process is awaiting player feedback. +-- * **Failed (*)**: The process has failed. +-- +-- (*) End states of the process. +-- +-- ### ACT_ASSIGN state transition methods: +-- +-- State transition functions can be set **by the mission designer** customizing or improving the behaviour of the state. +-- There are 2 moments when state transition methods will be called by the state machine: +-- +-- * **Before** the state transition. +-- The state transition method needs to start with the name **OnBefore + the name of the state**. +-- If the state transition method returns false, then the processing of the state transition will not be done! +-- If you want to change the behaviour of the AIControllable at this event, return false, +-- but then you'll need to specify your own logic using the AIControllable! +-- +-- * **After** the state transition. +-- The state transition method needs to start with the name **OnAfter + the name of the state**. +-- These state transition methods need to provide a return value, which is specified at the function description. +-- +-- === +-- +-- # 1) @{#ACT_ASSIGN_ACCEPT} class, extends @{Fsm.Assign#ACT_ASSIGN} +-- +-- The ACT_ASSIGN_ACCEPT class accepts by default a task for a player. No player intervention is allowed to reject the task. +-- +-- ## 1.1) ACT_ASSIGN_ACCEPT constructor: +-- +-- * @{#ACT_ASSIGN_ACCEPT.New}(): Creates a new ACT_ASSIGN_ACCEPT object. +-- +-- === +-- +-- # 2) @{#ACT_ASSIGN_MENU_ACCEPT} class, extends @{Fsm.Assign#ACT_ASSIGN} +-- +-- The ACT_ASSIGN_MENU_ACCEPT class accepts a task when the player accepts the task through an added menu option. +-- This assignment type is useful to conditionally allow the player to choose whether or not he would accept the task. +-- The assignment type also allows to reject the task. +-- +-- ## 2.1) ACT_ASSIGN_MENU_ACCEPT constructor: +-- ----------------------------------------- +-- +-- * @{#ACT_ASSIGN_MENU_ACCEPT.New}(): Creates a new ACT_ASSIGN_MENU_ACCEPT object. +-- +-- === +-- +-- @module Assign + + +do -- ACT_ASSIGN + + --- ACT_ASSIGN class + -- @type ACT_ASSIGN + -- @field Tasking.Task#TASK Task + -- @field Wrapper.Unit#UNIT ProcessUnit + -- @field Core.Zone#ZONE_BASE TargetZone + -- @extends Core.Fsm#FSM_PROCESS + ACT_ASSIGN = { + ClassName = "ACT_ASSIGN", + } + + + --- Creates a new task assignment state machine. The process will accept the task by default, no player intervention accepted. + -- @param #ACT_ASSIGN self + -- @return #ACT_ASSIGN The task acceptance process. + function ACT_ASSIGN:New() + + -- Inherits from BASE + local self = BASE:Inherit( self, FSM_PROCESS:New( "ACT_ASSIGN" ) ) -- Core.Fsm#FSM_PROCESS + + self:AddTransition( "UnAssigned", "Start", "Waiting" ) + self:AddTransition( "Waiting", "Assign", "Assigned" ) + self:AddTransition( "Waiting", "Reject", "Rejected" ) + self:AddTransition( "*", "Fail", "Failed" ) + + self:AddEndState( "Assigned" ) + self:AddEndState( "Rejected" ) + self:AddEndState( "Failed" ) + + self:SetStartState( "UnAssigned" ) + + return self + end + +end -- ACT_ASSIGN + + + +do -- ACT_ASSIGN_ACCEPT + + --- ACT_ASSIGN_ACCEPT class + -- @type ACT_ASSIGN_ACCEPT + -- @field Tasking.Task#TASK Task + -- @field Wrapper.Unit#UNIT ProcessUnit + -- @field Core.Zone#ZONE_BASE TargetZone + -- @extends #ACT_ASSIGN + ACT_ASSIGN_ACCEPT = { + ClassName = "ACT_ASSIGN_ACCEPT", + } + + + --- Creates a new task assignment state machine. The process will accept the task by default, no player intervention accepted. + -- @param #ACT_ASSIGN_ACCEPT self + -- @param #string TaskBriefing + function ACT_ASSIGN_ACCEPT:New( TaskBriefing ) + + local self = BASE:Inherit( self, ACT_ASSIGN:New() ) -- #ACT_ASSIGN_ACCEPT + + self.TaskBriefing = TaskBriefing + + return self + end + + function ACT_ASSIGN_ACCEPT:Init( FsmAssign ) + + self.TaskBriefing = FsmAssign.TaskBriefing + end + + --- StateMachine callback function + -- @param #ACT_ASSIGN_ACCEPT self + -- @param Wrapper.Unit#UNIT ProcessUnit + -- @param #string Event + -- @param #string From + -- @param #string To + function ACT_ASSIGN_ACCEPT:onafterStart( ProcessUnit, Event, From, To ) + self:E( { ProcessUnit, Event, From, To } ) + + self:__Assign( 1 ) + end + + --- StateMachine callback function + -- @param #ACT_ASSIGN_ACCEPT self + -- @param Wrapper.Unit#UNIT ProcessUnit + -- @param #string Event + -- @param #string From + -- @param #string To + function ACT_ASSIGN_ACCEPT:onenterAssigned( ProcessUnit, Event, From, To ) + env.info( "in here" ) + self:E( { ProcessUnit, Event, From, To } ) + + local ProcessGroup = ProcessUnit:GetGroup() + + self:Message( "You are assigned to the task " .. self.Task:GetName() ) + + self.Task:Assign() + end + +end -- ACT_ASSIGN_ACCEPT + + +do -- ACT_ASSIGN_MENU_ACCEPT + + --- ACT_ASSIGN_MENU_ACCEPT class + -- @type ACT_ASSIGN_MENU_ACCEPT + -- @field Tasking.Task#TASK Task + -- @field Wrapper.Unit#UNIT ProcessUnit + -- @field Core.Zone#ZONE_BASE TargetZone + -- @extends #ACT_ASSIGN + ACT_ASSIGN_MENU_ACCEPT = { + ClassName = "ACT_ASSIGN_MENU_ACCEPT", + } + + --- Init. + -- @param #ACT_ASSIGN_MENU_ACCEPT self + -- @param #string TaskName + -- @param #string TaskBriefing + -- @return #ACT_ASSIGN_MENU_ACCEPT self + function ACT_ASSIGN_MENU_ACCEPT:New( TaskName, TaskBriefing ) + + -- Inherits from BASE + local self = BASE:Inherit( self, ACT_ASSIGN:New() ) -- #ACT_ASSIGN_MENU_ACCEPT + + self.TaskName = TaskName + self.TaskBriefing = TaskBriefing + + return self + end + + function ACT_ASSIGN_MENU_ACCEPT:Init( FsmAssign ) + + self.TaskName = FsmAssign.TaskName + self.TaskBriefing = FsmAssign.TaskBriefing + end + + + --- Creates a new task assignment state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator. + -- @param #ACT_ASSIGN_MENU_ACCEPT self + -- @param #string TaskName + -- @param #string TaskBriefing + -- @return #ACT_ASSIGN_MENU_ACCEPT self + function ACT_ASSIGN_MENU_ACCEPT:Init( TaskName, TaskBriefing ) + + self.TaskBriefing = TaskBriefing + self.TaskName = TaskName + + return self + end + + --- StateMachine callback function + -- @param #ACT_ASSIGN_MENU_ACCEPT self + -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit + -- @param #string Event + -- @param #string From + -- @param #string To + function ACT_ASSIGN_MENU_ACCEPT:onafterStart( ProcessUnit, Event, From, To ) + self:E( { ProcessUnit, Event, From, To } ) + + self:Message( "Access the radio menu to accept the task. You have 30 seconds or the assignment will be cancelled." ) + + local ProcessGroup = ProcessUnit:GetGroup() + + self.Menu = MENU_GROUP:New( ProcessGroup, "Task " .. self.TaskName .. " acceptance" ) + self.MenuAcceptTask = MENU_GROUP_COMMAND:New( ProcessGroup, "Accept task " .. self.TaskName, self.Menu, self.MenuAssign, self ) + self.MenuRejectTask = MENU_GROUP_COMMAND:New( ProcessGroup, "Reject task " .. self.TaskName, self.Menu, self.MenuReject, self ) + end + + --- Menu function. + -- @param #ACT_ASSIGN_MENU_ACCEPT self + function ACT_ASSIGN_MENU_ACCEPT:MenuAssign() + self:E( ) + + self:__Assign( 1 ) + end + + --- Menu function. + -- @param #ACT_ASSIGN_MENU_ACCEPT self + function ACT_ASSIGN_MENU_ACCEPT:MenuReject() + self:E( ) + + self:__Reject( 1 ) + end + + --- StateMachine callback function + -- @param #ACT_ASSIGN_MENU_ACCEPT self + -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit + -- @param #string Event + -- @param #string From + -- @param #string To + function ACT_ASSIGN_MENU_ACCEPT:onafterAssign( ProcessUnit, Event, From, To ) + self:E( { ProcessUnit.UnitNameEvent, From, To } ) + + self.Menu:Remove() + end + + --- StateMachine callback function + -- @param #ACT_ASSIGN_MENU_ACCEPT self + -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit + -- @param #string Event + -- @param #string From + -- @param #string To + function ACT_ASSIGN_MENU_ACCEPT:onafterReject( ProcessUnit, Event, From, To ) + self:E( { ProcessUnit.UnitName, Event, From, To } ) + + self.Menu:Remove() + --TODO: need to resolve this problem ... it has to do with the events ... + --self.Task:UnAssignFromUnit( ProcessUnit )needs to become a callback funtion call upon the event + ProcessUnit:Destroy() + end + +end -- ACT_ASSIGN_MENU_ACCEPT +--- (SP) (MP) (FSM) Route AI or players through waypoints or to zones. +-- +-- === +-- +-- # @{#ACT_ROUTE} FSM class, extends @{Core.Fsm#FSM_PROCESS} +-- +-- ## ACT_ROUTE state machine: +-- +-- This class is a state machine: it manages a process that is triggered by events causing state transitions to occur. +-- All derived classes from this class will start with the class name, followed by a \_. See the relevant derived class descriptions below. +-- Each derived class follows exactly the same process, using the same events and following the same state transitions, +-- but will have **different implementation behaviour** upon each event or state transition. +-- +-- ### ACT_ROUTE **Events**: +-- +-- These are the events defined in this class: +-- +-- * **Start**: The process is started. The process will go into the Report state. +-- * **Report**: The process is reporting to the player the route to be followed. +-- * **Route**: The process is routing the controllable. +-- * **Pause**: The process is pausing the route of the controllable. +-- * **Arrive**: The controllable has arrived at a route point. +-- * **More**: There are more route points that need to be followed. The process will go back into the Report state. +-- * **NoMore**: There are no more route points that need to be followed. The process will go into the Success state. +-- +-- ### ACT_ROUTE **Event methods**: +-- +-- Event methods are available (dynamically allocated by the state machine), that accomodate for state transitions occurring in the process. +-- There are two types of event methods, which you can use to influence the normal mechanisms in the state machine: +-- +-- * **Immediate**: The event method has exactly the name of the event. +-- * **Delayed**: The event method starts with a __ + the name of the event. The first parameter of the event method is a number value, expressing the delay in seconds when the event will be executed. +-- +-- ### ACT_ROUTE **States**: +-- +-- * **None**: The controllable did not receive route commands. +-- * **Arrived (*)**: The controllable has arrived at a route point. +-- * **Aborted (*)**: The controllable has aborted the route path. +-- * **Routing**: The controllable is understay to the route point. +-- * **Pausing**: The process is pausing the routing. AI air will go into hover, AI ground will stop moving. Players can fly around. +-- * **Success (*)**: All route points were reached. +-- * **Failed (*)**: The process has failed. +-- +-- (*) End states of the process. +-- +-- ### ACT_ROUTE state transition methods: +-- +-- State transition functions can be set **by the mission designer** customizing or improving the behaviour of the state. +-- There are 2 moments when state transition methods will be called by the state machine: +-- +-- * **Before** the state transition. +-- The state transition method needs to start with the name **OnBefore + the name of the state**. +-- If the state transition method returns false, then the processing of the state transition will not be done! +-- If you want to change the behaviour of the AIControllable at this event, return false, +-- but then you'll need to specify your own logic using the AIControllable! +-- +-- * **After** the state transition. +-- The state transition method needs to start with the name **OnAfter + the name of the state**. +-- These state transition methods need to provide a return value, which is specified at the function description. +-- +-- === +-- +-- # 1) @{#ACT_ROUTE_ZONE} class, extends @{Fsm.Route#ACT_ROUTE} +-- +-- The ACT_ROUTE_ZONE class implements the core functions to route an AIR @{Controllable} player @{Unit} to a @{Zone}. +-- The player receives on perioding times messages with the coordinates of the route to follow. +-- Upon arrival at the zone, a confirmation of arrival is sent, and the process will be ended. +-- +-- # 1.1) ACT_ROUTE_ZONE constructor: +-- +-- * @{#ACT_ROUTE_ZONE.New}(): Creates a new ACT_ROUTE_ZONE object. +-- +-- === +-- +-- @module Route + + +do -- ACT_ROUTE + + --- ACT_ROUTE class + -- @type ACT_ROUTE + -- @field Tasking.Task#TASK TASK + -- @field Wrapper.Unit#UNIT ProcessUnit + -- @field Core.Zone#ZONE_BASE TargetZone + -- @extends Core.Fsm#FSM_PROCESS + ACT_ROUTE = { + ClassName = "ACT_ROUTE", + } + + + --- Creates a new routing state machine. The process will route a CLIENT to a ZONE until the CLIENT is within that ZONE. + -- @param #ACT_ROUTE self + -- @return #ACT_ROUTE self + function ACT_ROUTE:New() + + -- Inherits from BASE + local self = BASE:Inherit( self, FSM_PROCESS:New( "ACT_ROUTE" ) ) -- Core.Fsm#FSM_PROCESS + + self:AddTransition( "None", "Start", "Routing" ) + self:AddTransition( "*", "Report", "Reporting" ) + self:AddTransition( "*", "Route", "Routing" ) + self:AddTransition( "Routing", "Pause", "Pausing" ) + self:AddTransition( "*", "Abort", "Aborted" ) + self:AddTransition( "Routing", "Arrive", "Arrived" ) + self:AddTransition( "Arrived", "Success", "Success" ) + self:AddTransition( "*", "Fail", "Failed" ) + self:AddTransition( "", "", "" ) + self:AddTransition( "", "", "" ) + + self:AddEndState( "Arrived" ) + self:AddEndState( "Failed" ) + + self:SetStartState( "None" ) + + return self + end + + --- Task Events + + --- StateMachine callback function + -- @param #ACT_ROUTE self + -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit + -- @param #string Event + -- @param #string From + -- @param #string To + function ACT_ROUTE:onafterStart( ProcessUnit, Event, From, To ) + + + self:__Route( 1 ) + end + + --- Check if the controllable has arrived. + -- @param #ACT_ROUTE self + -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit + -- @return #boolean + function ACT_ROUTE:onfuncHasArrived( ProcessUnit ) + return false + end + + --- StateMachine callback function + -- @param #ACT_ROUTE self + -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit + -- @param #string Event + -- @param #string From + -- @param #string To + function ACT_ROUTE:onbeforeRoute( ProcessUnit, Event, From, To ) + + if ProcessUnit:IsAlive() then + local HasArrived = self:onfuncHasArrived( ProcessUnit ) -- Polymorphic + if self.DisplayCount >= self.DisplayInterval then + self:T( { HasArrived = HasArrived } ) + if not HasArrived then + self:__Report( 1 ) + end + self.DisplayCount = 1 + else + self.DisplayCount = self.DisplayCount + 1 + end + + self:T( { DisplayCount = self.DisplayCount } ) + + if HasArrived then + self:__Arrive( 1 ) + else + self:__Route( 1 ) + end + + return HasArrived -- if false, then the event will not be executed... + end + + return false + + end + +end -- ACT_ROUTE + + + +do -- ACT_ROUTE_ZONE + + --- ACT_ROUTE_ZONE class + -- @type ACT_ROUTE_ZONE + -- @field Tasking.Task#TASK TASK + -- @field Wrapper.Unit#UNIT ProcessUnit + -- @field Core.Zone#ZONE_BASE TargetZone + -- @extends #ACT_ROUTE + ACT_ROUTE_ZONE = { + ClassName = "ACT_ROUTE_ZONE", + } + + + --- Creates a new routing state machine. The task will route a controllable to a ZONE until the controllable is within that ZONE. + -- @param #ACT_ROUTE_ZONE self + -- @param Core.Zone#ZONE_BASE TargetZone + function ACT_ROUTE_ZONE:New( TargetZone ) + local self = BASE:Inherit( self, ACT_ROUTE:New() ) -- #ACT_ROUTE_ZONE + + self.TargetZone = TargetZone + + self.DisplayInterval = 30 + self.DisplayCount = 30 + self.DisplayMessage = true + self.DisplayTime = 10 -- 10 seconds is the default + + return self + end + + function ACT_ROUTE_ZONE:Init( FsmRoute ) + + self.TargetZone = FsmRoute.TargetZone + self.DisplayInterval = 30 + self.DisplayCount = 30 + self.DisplayMessage = true + self.DisplayTime = 10 -- 10 seconds is the default + end + + --- Method override to check if the controllable has arrived. + -- @param #ACT_ROUTE self + -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit + -- @return #boolean + function ACT_ROUTE_ZONE:onfuncHasArrived( ProcessUnit ) + + if ProcessUnit:IsInZone( self.TargetZone ) then + local RouteText = "You have arrived within the zone." + self:Message( RouteText ) + end + + return ProcessUnit:IsInZone( self.TargetZone ) + end + + --- Task Events + + --- StateMachine callback function + -- @param #ACT_ROUTE_ZONE self + -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit + -- @param #string Event + -- @param #string From + -- @param #string To + function ACT_ROUTE_ZONE:onenterReporting( ProcessUnit, Event, From, To ) + + local ZoneVec2 = self.TargetZone:GetVec2() + local ZonePointVec2 = POINT_VEC2:New( ZoneVec2.x, ZoneVec2.y ) + local TaskUnitVec2 = ProcessUnit:GetVec2() + local TaskUnitPointVec2 = POINT_VEC2:New( TaskUnitVec2.x, TaskUnitVec2.y ) + local RouteText = "Route to " .. TaskUnitPointVec2:GetBRText( ZonePointVec2 ) .. " km to target." + self:Message( RouteText ) + end + +end -- ACT_ROUTE_ZONE +--- (SP) (MP) (FSM) Account for (Detect, count and report) DCS events occuring on DCS objects (units). +-- +-- === +-- +-- # @{#ACT_ACCOUNT} FSM class, extends @{Core.Fsm#FSM_PROCESS} +-- +-- ## ACT_ACCOUNT state machine: +-- +-- This class is a state machine: it manages a process that is triggered by events causing state transitions to occur. +-- All derived classes from this class will start with the class name, followed by a \_. See the relevant derived class descriptions below. +-- Each derived class follows exactly the same process, using the same events and following the same state transitions, +-- but will have **different implementation behaviour** upon each event or state transition. +-- +-- ### ACT_ACCOUNT **Events**: +-- +-- These are the events defined in this class: +-- +-- * **Start**: The process is started. The process will go into the Report state. +-- * **Event**: A relevant event has occured that needs to be accounted for. The process will go into the Account state. +-- * **Report**: The process is reporting to the player the accounting status of the DCS events. +-- * **More**: There are more DCS events that need to be accounted for. The process will go back into the Report state. +-- * **NoMore**: There are no more DCS events that need to be accounted for. The process will go into the Success state. +-- +-- ### ACT_ACCOUNT **Event methods**: +-- +-- Event methods are available (dynamically allocated by the state machine), that accomodate for state transitions occurring in the process. +-- There are two types of event methods, which you can use to influence the normal mechanisms in the state machine: +-- +-- * **Immediate**: The event method has exactly the name of the event. +-- * **Delayed**: The event method starts with a __ + the name of the event. The first parameter of the event method is a number value, expressing the delay in seconds when the event will be executed. +-- +-- ### ACT_ACCOUNT **States**: +-- +-- * **Assigned**: The player is assigned to the task. This is the initialization state for the process. +-- * **Waiting**: the process is waiting for a DCS event to occur within the simulator. This state is set automatically. +-- * **Report**: The process is Reporting to the players in the group of the unit. This state is set automatically every 30 seconds. +-- * **Account**: The relevant DCS event has occurred, and is accounted for. +-- * **Success (*)**: All DCS events were accounted for. +-- * **Failed (*)**: The process has failed. +-- +-- (*) End states of the process. +-- +-- ### ACT_ACCOUNT state transition methods: +-- +-- State transition functions can be set **by the mission designer** customizing or improving the behaviour of the state. +-- There are 2 moments when state transition methods will be called by the state machine: +-- +-- * **Before** the state transition. +-- The state transition method needs to start with the name **OnBefore + the name of the state**. +-- If the state transition method returns false, then the processing of the state transition will not be done! +-- If you want to change the behaviour of the AIControllable at this event, return false, +-- but then you'll need to specify your own logic using the AIControllable! +-- +-- * **After** the state transition. +-- The state transition method needs to start with the name **OnAfter + the name of the state**. +-- These state transition methods need to provide a return value, which is specified at the function description. +-- +-- # 1) @{#ACT_ACCOUNT_DEADS} FSM class, extends @{Fsm.Account#ACT_ACCOUNT} +-- +-- The ACT_ACCOUNT_DEADS class accounts (detects, counts and reports) successful kills of DCS units. +-- The process is given a @{Set} of units that will be tracked upon successful destruction. +-- The process will end after each target has been successfully destroyed. +-- Each successful dead will trigger an Account state transition that can be scored, modified or administered. +-- +-- +-- ## ACT_ACCOUNT_DEADS constructor: +-- +-- * @{#ACT_ACCOUNT_DEADS.New}(): Creates a new ACT_ACCOUNT_DEADS object. +-- +-- === +-- +-- @module Account + + +do -- ACT_ACCOUNT + + --- ACT_ACCOUNT class + -- @type ACT_ACCOUNT + -- @field Set#SET_UNIT TargetSetUnit + -- @extends Core.Fsm#FSM_PROCESS + ACT_ACCOUNT = { + ClassName = "ACT_ACCOUNT", + TargetSetUnit = nil, + } + + --- Creates a new DESTROY process. + -- @param #ACT_ACCOUNT self + -- @return #ACT_ACCOUNT + function ACT_ACCOUNT:New() + + -- Inherits from BASE + local self = BASE:Inherit( self, FSM_PROCESS:New() ) -- Core.Fsm#FSM_PROCESS + + self:AddTransition( "Assigned", "Start", "Waiting") + self:AddTransition( "*", "Wait", "Waiting") + self:AddTransition( "*", "Report", "Report") + self:AddTransition( "*", "Event", "Account") + self:AddTransition( "Account", "More", "Wait") + self:AddTransition( "Account", "NoMore", "Accounted") + self:AddTransition( "*", "Fail", "Failed") + + self:AddEndState( "Accounted" ) + self:AddEndState( "Failed" ) + + self:SetStartState( "Assigned" ) + + return self + end + + --- Process Events + + --- StateMachine callback function + -- @param #ACT_ACCOUNT self + -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit + -- @param #string Event + -- @param #string From + -- @param #string To + function ACT_ACCOUNT:onafterStart( ProcessUnit, Event, From, To ) + + self:EventOnDead( self.onfuncEventDead ) + + self:__Wait( 1 ) + end + + + --- StateMachine callback function + -- @param #ACT_ACCOUNT self + -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit + -- @param #string Event + -- @param #string From + -- @param #string To + function ACT_ACCOUNT:onenterWaiting( ProcessUnit, Event, From, To ) + + if self.DisplayCount >= self.DisplayInterval then + self:Report() + self.DisplayCount = 1 + else + self.DisplayCount = self.DisplayCount + 1 + end + + return true -- Process always the event. + end + + --- StateMachine callback function + -- @param #ACT_ACCOUNT self + -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit + -- @param #string Event + -- @param #string From + -- @param #string To + function ACT_ACCOUNT:onafterEvent( ProcessUnit, Event, From, To, Event ) + + self:__NoMore( 1 ) + end + +end -- ACT_ACCOUNT + +do -- ACT_ACCOUNT_DEADS + + --- ACT_ACCOUNT_DEADS class + -- @type ACT_ACCOUNT_DEADS + -- @field Set#SET_UNIT TargetSetUnit + -- @extends #ACT_ACCOUNT + ACT_ACCOUNT_DEADS = { + ClassName = "ACT_ACCOUNT_DEADS", + TargetSetUnit = nil, + } + + + --- Creates a new DESTROY process. + -- @param #ACT_ACCOUNT_DEADS self + -- @param Set#SET_UNIT TargetSetUnit + -- @param #string TaskName + function ACT_ACCOUNT_DEADS:New( TargetSetUnit, TaskName ) + -- Inherits from BASE + local self = BASE:Inherit( self, ACT_ACCOUNT:New() ) -- #ACT_ACCOUNT_DEADS + + self.TargetSetUnit = TargetSetUnit + self.TaskName = TaskName + + self.DisplayInterval = 30 + self.DisplayCount = 30 + self.DisplayMessage = true + self.DisplayTime = 10 -- 10 seconds is the default + self.DisplayCategory = "HQ" -- Targets is the default display category + + return self + end + + function ACT_ACCOUNT_DEADS:Init( FsmAccount ) + + self.TargetSetUnit = FsmAccount.TargetSetUnit + self.TaskName = FsmAccount.TaskName + end + + + + function ACT_ACCOUNT_DEADS:_Destructor() + self:E("_Destructor") + + self:EventRemoveAll() + + end + + --- Process Events + + --- StateMachine callback function + -- @param #ACT_ACCOUNT_DEADS self + -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit + -- @param #string Event + -- @param #string From + -- @param #string To + function ACT_ACCOUNT_DEADS:onenterReport( ProcessUnit, Event, From, To ) + self:E( { ProcessUnit, Event, From, To } ) + + self:Message( "Your group with assigned " .. self.TaskName .. " task has " .. self.TargetSetUnit:GetUnitTypesText() .. " targets left to be destroyed." ) + end + + + --- StateMachine callback function + -- @param #ACT_ACCOUNT_DEADS self + -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit + -- @param #string Event + -- @param #string From + -- @param #string To + function ACT_ACCOUNT_DEADS:onenterAccount( ProcessUnit, Event, From, To, EventData ) + self:T( { ProcessUnit, EventData, Event, From, To } ) + + self:T({self.Controllable}) + + self.TargetSetUnit:Flush() + + if self.TargetSetUnit:FindUnit( EventData.IniUnitName ) then + local TaskGroup = ProcessUnit:GetGroup() + self.TargetSetUnit:RemoveUnitsByName( EventData.IniUnitName ) + self:Message( "You hit a target. Your group with assigned " .. self.TaskName .. " task has " .. self.TargetSetUnit:Count() .. " targets ( " .. self.TargetSetUnit:GetUnitTypesText() .. " ) left to be destroyed." ) + end + end + + --- StateMachine callback function + -- @param #ACT_ACCOUNT_DEADS self + -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit + -- @param #string Event + -- @param #string From + -- @param #string To + function ACT_ACCOUNT_DEADS:onafterEvent( ProcessUnit, Event, From, To, EventData ) + + if self.TargetSetUnit:Count() > 0 then + self:__More( 1 ) + else + self:__NoMore( 1 ) + end + end + + --- DCS Events + + --- @param #ACT_ACCOUNT_DEADS self + -- @param Event#EVENTDATA EventData + function ACT_ACCOUNT_DEADS:onfuncEventDead( EventData ) + self:T( { "EventDead", EventData } ) + + if EventData.IniDCSUnit then + self:__Event( 1, EventData ) + end + end + +end -- ACT_ACCOUNT DEADS +--- (SP) (MP) (FSM) Route AI or players through waypoints or to zones. +-- +-- === +-- +-- # @{#ACT_ASSIST} FSM class, extends @{Core.Fsm#FSM_PROCESS} +-- +-- ## ACT_ASSIST state machine: +-- +-- This class is a state machine: it manages a process that is triggered by events causing state transitions to occur. +-- All derived classes from this class will start with the class name, followed by a \_. See the relevant derived class descriptions below. +-- Each derived class follows exactly the same process, using the same events and following the same state transitions, +-- but will have **different implementation behaviour** upon each event or state transition. +-- +-- ### ACT_ASSIST **Events**: +-- +-- These are the events defined in this class: +-- +-- * **Start**: The process is started. +-- * **Next**: The process is smoking the targets in the given zone. +-- +-- ### ACT_ASSIST **Event methods**: +-- +-- Event methods are available (dynamically allocated by the state machine), that accomodate for state transitions occurring in the process. +-- There are two types of event methods, which you can use to influence the normal mechanisms in the state machine: +-- +-- * **Immediate**: The event method has exactly the name of the event. +-- * **Delayed**: The event method starts with a __ + the name of the event. The first parameter of the event method is a number value, expressing the delay in seconds when the event will be executed. +-- +-- ### ACT_ASSIST **States**: +-- +-- * **None**: The controllable did not receive route commands. +-- * **AwaitSmoke (*)**: The process is awaiting to smoke the targets in the zone. +-- * **Smoking (*)**: The process is smoking the targets in the zone. +-- * **Failed (*)**: The process has failed. +-- +-- (*) End states of the process. +-- +-- ### ACT_ASSIST state transition methods: +-- +-- State transition functions can be set **by the mission designer** customizing or improving the behaviour of the state. +-- There are 2 moments when state transition methods will be called by the state machine: +-- +-- * **Before** the state transition. +-- The state transition method needs to start with the name **OnBefore + the name of the state**. +-- If the state transition method returns false, then the processing of the state transition will not be done! +-- If you want to change the behaviour of the AIControllable at this event, return false, +-- but then you'll need to specify your own logic using the AIControllable! +-- +-- * **After** the state transition. +-- The state transition method needs to start with the name **OnAfter + the name of the state**. +-- These state transition methods need to provide a return value, which is specified at the function description. +-- +-- === +-- +-- # 1) @{#ACT_ASSIST_SMOKE_TARGETS_ZONE} class, extends @{Fsm.Route#ACT_ASSIST} +-- +-- The ACT_ASSIST_SMOKE_TARGETS_ZONE class implements the core functions to smoke targets in a @{Zone}. +-- The targets are smoked within a certain range around each target, simulating a realistic smoking behaviour. +-- At random intervals, a new target is smoked. +-- +-- # 1.1) ACT_ASSIST_SMOKE_TARGETS_ZONE constructor: +-- +-- * @{#ACT_ASSIST_SMOKE_TARGETS_ZONE.New}(): Creates a new ACT_ASSIST_SMOKE_TARGETS_ZONE object. +-- +-- === +-- +-- @module Smoke + +do -- ACT_ASSIST + + --- ACT_ASSIST class + -- @type ACT_ASSIST + -- @extends Core.Fsm#FSM_PROCESS + ACT_ASSIST = { + ClassName = "ACT_ASSIST", + } + + --- Creates a new target smoking state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator. + -- @param #ACT_ASSIST self + -- @return #ACT_ASSIST + function ACT_ASSIST:New() + + -- Inherits from BASE + local self = BASE:Inherit( self, FSM_PROCESS:New( "ACT_ASSIST" ) ) -- Core.Fsm#FSM_PROCESS + + self:AddTransition( "None", "Start", "AwaitSmoke" ) + self:AddTransition( "AwaitSmoke", "Next", "Smoking" ) + self:AddTransition( "Smoking", "Next", "AwaitSmoke" ) + self:AddTransition( "*", "Stop", "Success" ) + self:AddTransition( "*", "Fail", "Failed" ) + + self:AddEndState( "Failed" ) + self:AddEndState( "Success" ) + + self:SetStartState( "None" ) + + return self + end + + --- Task Events + + --- StateMachine callback function + -- @param #ACT_ASSIST self + -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit + -- @param #string Event + -- @param #string From + -- @param #string To + function ACT_ASSIST:onafterStart( ProcessUnit, Event, From, To ) + + local ProcessGroup = ProcessUnit:GetGroup() + local MissionMenu = self:GetMission():GetMissionMenu( ProcessGroup ) + + local function MenuSmoke( MenuParam ) + self:E( MenuParam ) + local self = MenuParam.self + local SmokeColor = MenuParam.SmokeColor + self.SmokeColor = SmokeColor + self:__Next( 1 ) + end + + self.Menu = MENU_GROUP:New( ProcessGroup, "Target acquisition", MissionMenu ) + self.MenuSmokeBlue = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop blue smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Blue } ) + self.MenuSmokeGreen = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop green smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Green } ) + self.MenuSmokeOrange = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop Orange smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Orange } ) + self.MenuSmokeRed = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop Red smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Red } ) + self.MenuSmokeWhite = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop White smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.White } ) + end + +end + +do -- ACT_ASSIST_SMOKE_TARGETS_ZONE + + --- ACT_ASSIST_SMOKE_TARGETS_ZONE class + -- @type ACT_ASSIST_SMOKE_TARGETS_ZONE + -- @field Set#SET_UNIT TargetSetUnit + -- @field Core.Zone#ZONE_BASE TargetZone + -- @extends #ACT_ASSIST + ACT_ASSIST_SMOKE_TARGETS_ZONE = { + ClassName = "ACT_ASSIST_SMOKE_TARGETS_ZONE", + } + +-- function ACT_ASSIST_SMOKE_TARGETS_ZONE:_Destructor() +-- self:E("_Destructor") +-- +-- self.Menu:Remove() +-- self:EventRemoveAll() +-- end + + --- Creates a new target smoking state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator. + -- @param #ACT_ASSIST_SMOKE_TARGETS_ZONE self + -- @param Set#SET_UNIT TargetSetUnit + -- @param Core.Zone#ZONE_BASE TargetZone + function ACT_ASSIST_SMOKE_TARGETS_ZONE:New( TargetSetUnit, TargetZone ) + local self = BASE:Inherit( self, ACT_ASSIST:New() ) -- #ACT_ASSIST + + self.TargetSetUnit = TargetSetUnit + self.TargetZone = TargetZone + + return self + end + + function ACT_ASSIST_SMOKE_TARGETS_ZONE:Init( FsmSmoke ) + + self.TargetSetUnit = FsmSmoke.TargetSetUnit + self.TargetZone = FsmSmoke.TargetZone + end + + --- Creates a new target smoking state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator. + -- @param #ACT_ASSIST_SMOKE_TARGETS_ZONE self + -- @param Set#SET_UNIT TargetSetUnit + -- @param Core.Zone#ZONE_BASE TargetZone + -- @return #ACT_ASSIST_SMOKE_TARGETS_ZONE self + function ACT_ASSIST_SMOKE_TARGETS_ZONE:Init( TargetSetUnit, TargetZone ) + + self.TargetSetUnit = TargetSetUnit + self.TargetZone = TargetZone + + return self + end + + --- StateMachine callback function + -- @param #ACT_ASSIST_SMOKE_TARGETS_ZONE self + -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit + -- @param #string Event + -- @param #string From + -- @param #string To + function ACT_ASSIST_SMOKE_TARGETS_ZONE:onenterSmoking( ProcessUnit, Event, From, To ) + + self.TargetSetUnit:ForEachUnit( + --- @param Wrapper.Unit#UNIT SmokeUnit + function( SmokeUnit ) + if math.random( 1, ( 100 * self.TargetSetUnit:Count() ) / 4 ) <= 100 then + SCHEDULER:New( self, + function() + if SmokeUnit:IsAlive() then + SmokeUnit:Smoke( self.SmokeColor, 150 ) + end + end, {}, math.random( 10, 60 ) + ) + end + end + ) + + end + +end--- A COMMANDCENTER is the owner of multiple missions within MOOSE. +-- A COMMANDCENTER governs multiple missions, the tasking and the reporting. +-- @module CommandCenter + + + +--- The REPORT class +-- @type REPORT +-- @extends Core.Base#BASE +REPORT = { + ClassName = "REPORT", +} + +--- Create a new REPORT. +-- @param #REPORT self +-- @param #string Title +-- @return #REPORT +function REPORT:New( Title ) + + local self = BASE:Inherit( self, BASE:New() ) + + self.Report = {} + self.Report[#self.Report+1] = Title + + return self +end + +--- Add a new line to a REPORT. +-- @param #REPORT self +-- @param #string Text +-- @return #REPORT +function REPORT:Add( Text ) + self.Report[#self.Report+1] = Text + return self.Report[#self.Report+1] +end + +function REPORT:Text() + return table.concat( self.Report, "\n" ) +end + +--- The COMMANDCENTER class +-- @type COMMANDCENTER +-- @field Wrapper.Group#GROUP HQ +-- @field Dcs.DCSCoalitionWrapper.Object#coalition CommandCenterCoalition +-- @list Missions +-- @extends Core.Base#BASE +COMMANDCENTER = { + ClassName = "COMMANDCENTER", + CommandCenterName = "", + CommandCenterCoalition = nil, + CommandCenterPositionable = nil, + Name = "", +} +--- The constructor takes an IDENTIFIABLE as the HQ command center. +-- @param #COMMANDCENTER self +-- @param Wrapper.Positionable#POSITIONABLE CommandCenterPositionable +-- @param #string CommandCenterName +-- @return #COMMANDCENTER +function COMMANDCENTER:New( CommandCenterPositionable, CommandCenterName ) + + local self = BASE:Inherit( self, BASE:New() ) + + self.CommandCenterPositionable = CommandCenterPositionable + self.CommandCenterName = CommandCenterName or CommandCenterPositionable:GetName() + self.CommandCenterCoalition = CommandCenterPositionable:GetCoalition() + + self.Missions = setmetatable( {}, { __mode = "v" } ) + + self:EventOnBirth( + --- @param #COMMANDCENTER self + --- @param Core.Event#EVENTDATA EventData + function( self, EventData ) + self:E( { EventData } ) + local EventGroup = GROUP:Find( EventData.IniDCSGroup ) + if EventGroup and self:HasGroup( EventGroup ) then + local MenuReporting = MENU_GROUP:New( EventGroup, "Reporting", self.CommandCenterMenu ) + local MenuMissionsSummary = MENU_GROUP_COMMAND:New( EventGroup, "Missions Summary Report", MenuReporting, self.ReportSummary, self, EventGroup ) + local MenuMissionsDetails = MENU_GROUP_COMMAND:New( EventGroup, "Missions Details Report", MenuReporting, self.ReportDetails, self, EventGroup ) + self:ReportSummary( EventGroup ) + end + local PlayerUnit = EventData.IniUnit + for MissionID, Mission in pairs( self:GetMissions() ) do + local Mission = Mission -- Tasking.Mission#MISSION + Mission:JoinUnit( PlayerUnit ) + Mission:ReportDetails() + end + + end + ) + + -- When a player enters a client or a unit, the CommandCenter will check for each Mission and each Task in the Mission if the player has things to do. + -- For these elements, it will= + -- - Set the correct menu. + -- - Assign the PlayerUnit to the Task if required. + -- - Send a message to the other players in the group that this player has joined. + self:EventOnPlayerEnterUnit( + --- @param #COMMANDCENTER self + -- @param Core.Event#EVENTDATA EventData + function( self, EventData ) + local PlayerUnit = EventData.IniUnit + for MissionID, Mission in pairs( self:GetMissions() ) do + local Mission = Mission -- Tasking.Mission#MISSION + Mission:JoinUnit( PlayerUnit ) + Mission:ReportDetails() + end + end + ) + + -- Handle when a player leaves a slot and goes back to spectators ... + -- The PlayerUnit will be UnAssigned from the Task. + -- When there is no Unit left running the Task, the Task goes into Abort... + self:EventOnPlayerLeaveUnit( + --- @param #TASK self + -- @param Core.Event#EVENTDATA EventData + function( self, EventData ) + local PlayerUnit = EventData.IniUnit + for MissionID, Mission in pairs( self:GetMissions() ) do + Mission:AbortUnit( PlayerUnit ) + end + end + ) + + -- Handle when a player leaves a slot and goes back to spectators ... + -- The PlayerUnit will be UnAssigned from the Task. + -- When there is no Unit left running the Task, the Task goes into Abort... + self:EventOnCrash( + --- @param #TASK self + -- @param Core.Event#EVENTDATA EventData + function( self, EventData ) + local PlayerUnit = EventData.IniUnit + for MissionID, Mission in pairs( self:GetMissions() ) do + Mission:CrashUnit( PlayerUnit ) + end + end + ) + + return self +end + +--- Gets the name of the HQ command center. +-- @param #COMMANDCENTER self +-- @return #string +function COMMANDCENTER:GetName() + + return self.HQName +end + +--- Gets the POSITIONABLE of the HQ command center. +-- @param #COMMANDCENTER self +-- @return Wrapper.Positionable#POSITIONABLE +function COMMANDCENTER:GetPositionable() + return self.CommandCenterPositionable +end + +--- Get the Missions governed by the HQ command center. +-- @param #COMMANDCENTER self +-- @return #list +function COMMANDCENTER:GetMissions() + + return self.Missions +end + +--- Add a MISSION to be governed by the HQ command center. +-- @param #COMMANDCENTER self +-- @param Tasking.Mission#MISSION Mission +-- @return Tasking.Mission#MISSION +function COMMANDCENTER:AddMission( Mission ) + + self.Missions[Mission] = Mission + + return Mission +end + +--- Removes a MISSION to be governed by the HQ command center. +-- The given Mission is not nilified. +-- @param #COMMANDCENTER self +-- @param Tasking.Mission#MISSION Mission +-- @return Tasking.Mission#MISSION +function COMMANDCENTER:RemoveMission( Mission ) + + self.Missions[Mission] = nil + + return Mission +end + +--- Sets the menu structure of the Missions governed by the HQ command center. +-- @param #COMMANDCENTER self +function COMMANDCENTER:SetMenu() + self:F() + + self.CommandCenterMenu = self.CommandCenterMenu or MENU_COALITION:New( self.CommandCenterCoalition, "HQ" ) + + for MissionID, Mission in pairs( self:GetMissions() ) do + local Mission = Mission -- Tasking.Mission#MISSION + Mission:SetMenu() + end +end + + +--- Checks of the COMMANDCENTER has a GROUP. +-- @param #COMMANDCENTER self +-- @param Wrapper.Group#GROUP +-- @return #boolean +function COMMANDCENTER:HasGroup( MissionGroup ) + + local Has = false + + for MissionID, Mission in pairs( self.Missions ) do + local Mission = Mission -- Tasking.Mission#MISSION + if Mission:HasGroup( MissionGroup ) then + Has = true + break + end + end + + return Has +end + +--- Send a CC message to a GROUP. +-- @param #COMMANDCENTER self +function COMMANDCENTER:MessageToGroup( Message, TaskGroup ) + + self:GetPositionable():MessageToGroup( Message , 20, TaskGroup ) + +end + +--- Send a CC message to the coalition of the CC. +-- @param #COMMANDCENTER self +function COMMANDCENTER:MessageToCoalition( Message ) + + local CCCoalition = self:GetPositionable():GetCoalition() + self:GetPositionable():MessageToBlue( Message , 20, CCCoalition ) + +end + +--- Report the status of all MISSIONs to a GROUP. +-- Each Mission is listed, with an indication how many Tasks are still to be completed. +-- @param #COMMANDCENTER self +function COMMANDCENTER:ReportSummary( ReportGroup ) + self:E( ReportGroup ) + + local Report = REPORT:New() + + for MissionID, Mission in pairs( self.Missions ) do + local Mission = Mission -- Tasking.Mission#MISSION + Report:Add( " - " .. Mission:ReportOverview() ) + end + + self:GetPositionable():MessageToGroup( Report:Text(), 30, ReportGroup ) + +end + +--- Report the status of a Task to a Group. +-- Report the details of a Mission, listing the Mission, and all the Task details. +-- @param #COMMANDCENTER self +function COMMANDCENTER:ReportDetails( ReportGroup, Task ) + self:E( ReportGroup ) + + local Report = REPORT:New() + + for MissionID, Mission in pairs( self.Missions ) do + local Mission = Mission -- Tasking.Mission#MISSION + Report:Add( " - " .. Mission:ReportDetails() ) + end + + self:GetPositionable():MessageToGroup( Report:Text(), 30, ReportGroup ) +end + +--- A MISSION is the main owner of a Mission orchestration within MOOSE . The Mission framework orchestrates @{CLIENT}s, @{TASK}s, @{STAGE}s etc. +-- A @{CLIENT} needs to be registered within the @{MISSION} through the function @{AddClient}. A @{TASK} needs to be registered within the @{MISSION} through the function @{AddTask}. +-- @module Mission + +--- The MISSION class +-- @type MISSION +-- @field #MISSION.Clients _Clients +-- @field Core.Menu#MENU_COALITION MissionMenu +-- @field #string MissionBriefing +-- @extends Core.Fsm#FSM +MISSION = { + ClassName = "MISSION", + Name = "", + MissionStatus = "PENDING", + _Clients = {}, + TaskMenus = {}, + TaskCategoryMenus = {}, + TaskTypeMenus = {}, + _ActiveTasks = {}, + GoalFunction = nil, + MissionReportTrigger = 0, + MissionProgressTrigger = 0, + MissionReportShow = false, + MissionReportFlash = false, + MissionTimeInterval = 0, + MissionCoalition = "", + SUCCESS = 1, + FAILED = 2, + REPEAT = 3, + _GoalTasks = {} +} + +--- This is the main MISSION declaration method. Each Mission is like the master or a Mission orchestration between, Clients, Tasks, Stages etc. +-- @param #MISSION self +-- @param Tasking.CommandCenter#COMMANDCENTER CommandCenter +-- @param #string MissionName is the name of the mission. This name will be used to reference the status of each mission by the players. +-- @param #string MissionPriority is a string indicating the "priority" of the Mission. f.e. "Primary", "Secondary" or "First", "Second". It is free format and up to the Mission designer to choose. There are no rules behind this field. +-- @param #string MissionBriefing is a string indicating the mission briefing to be shown when a player joins a @{CLIENT}. +-- @param Dcs.DCSCoalitionWrapper.Object#coalition MissionCoalition is a string indicating the coalition or party to which this mission belongs to. It is free format and can be chosen freely by the mission designer. Note that this field is not to be confused with the coalition concept of the ME. Examples of a Mission Coalition could be "NATO", "CCCP", "Intruders", "Terrorists"... +-- @return #MISSION self +function MISSION:New( CommandCenter, MissionName, MissionPriority, MissionBriefing, MissionCoalition ) + + local self = BASE:Inherit( self, FSM:New() ) -- Core.Fsm#FSM + + self:SetStartState( "Idle" ) + + self:AddTransition( "Idle", "Start", "Ongoing" ) + self:AddTransition( "Ongoing", "Stop", "Idle" ) + self:AddTransition( "Ongoing", "Complete", "Completed" ) + self:AddTransition( "*", "Fail", "Failed" ) + + self:T( { MissionName, MissionPriority, MissionBriefing, MissionCoalition } ) + + self.CommandCenter = CommandCenter + CommandCenter:AddMission( self ) + + self.Name = MissionName + self.MissionPriority = MissionPriority + self.MissionBriefing = MissionBriefing + self.MissionCoalition = MissionCoalition + + self.Tasks = {} + + return self +end + +--- FSM function for a MISSION +-- @param #MISSION self +-- @param #string Event +-- @param #string From +-- @param #string To +function MISSION:onbeforeComplete( Event, From, To ) + + for TaskID, Task in pairs( self:GetTasks() ) do + local Task = Task -- Tasking.Task#TASK + if not Task:IsStateSuccess() and not Task:IsStateFailed() and not Task:IsStateAborted() and not Task:IsStateCancelled() then + return false -- Mission cannot be completed. Other Tasks are still active. + end + end + return true -- Allow Mission completion. +end + +--- FSM function for a MISSION +-- @param #MISSION self +-- @param #string Event +-- @param #string From +-- @param #string To +function MISSION:onenterCompleted( Event, From, To ) + + self:GetCommandCenter():MessageToCoalition( "Mission " .. self:GetName() .. " has been completed! Good job guys!" ) +end + +--- Gets the mission name. +-- @param #MISSION self +-- @return #MISSION self +function MISSION:GetName() + return self.Name +end + +--- Add a Unit to join the Mission. +-- For each Task within the Mission, the Unit is joined with the Task. +-- If the Unit was not part of a Task in the Mission, false is returned. +-- If the Unit is part of a Task in the Mission, true is returned. +-- @param #MISSION self +-- @param Wrapper.Unit#UNIT PlayerUnit The CLIENT or UNIT of the Player joining the Mission. +-- @return #boolean true if Unit is part of a Task in the Mission. +function MISSION:JoinUnit( PlayerUnit ) + self:F( { PlayerUnit = PlayerUnit } ) + + local PlayerUnitAdded = false + + for TaskID, Task in pairs( self:GetTasks() ) do + local Task = Task -- Tasking.Task#TASK + if Task:JoinUnit( PlayerUnit ) then + PlayerUnitAdded = true + end + end + + return PlayerUnitAdded +end + +--- Aborts a PlayerUnit from the Mission. +-- For each Task within the Mission, the PlayerUnit is removed from Task where it is assigned. +-- If the Unit was not part of a Task in the Mission, false is returned. +-- If the Unit is part of a Task in the Mission, true is returned. +-- @param #MISSION self +-- @param Wrapper.Unit#UNIT PlayerUnit The CLIENT or UNIT of the Player joining the Mission. +-- @return #boolean true if Unit is part of a Task in the Mission. +function MISSION:AbortUnit( PlayerUnit ) + self:F( { PlayerUnit = PlayerUnit } ) + + local PlayerUnitRemoved = false + + for TaskID, Task in pairs( self:GetTasks() ) do + if Task:AbortUnit( PlayerUnit ) then + PlayerUnitRemoved = true + end + end + + return PlayerUnitRemoved +end + +--- Handles a crash of a PlayerUnit from the Mission. +-- For each Task within the Mission, the PlayerUnit is removed from Task where it is assigned. +-- If the Unit was not part of a Task in the Mission, false is returned. +-- If the Unit is part of a Task in the Mission, true is returned. +-- @param #MISSION self +-- @param Wrapper.Unit#UNIT PlayerUnit The CLIENT or UNIT of the Player crashing. +-- @return #boolean true if Unit is part of a Task in the Mission. +function MISSION:CrashUnit( PlayerUnit ) + self:F( { PlayerUnit = PlayerUnit } ) + + local PlayerUnitRemoved = false + + for TaskID, Task in pairs( self:GetTasks() ) do + if Task:CrashUnit( PlayerUnit ) then + PlayerUnitRemoved = true + end + end + + return PlayerUnitRemoved +end + +--- Add a scoring to the mission. +-- @param #MISSION self +-- @return #MISSION self +function MISSION:AddScoring( Scoring ) + self.Scoring = Scoring + return self +end + +--- Get the scoring object of a mission. +-- @param #MISSION self +-- @return #SCORING Scoring +function MISSION:GetScoring() + return self.Scoring +end + +--- Get the groups for which TASKS are given in the mission +-- @param #MISSION self +-- @return Core.Set#SET_GROUP +function MISSION:GetGroups() + + local SetGroup = SET_GROUP:New() + + for TaskID, Task in pairs( self:GetTasks() ) do + local Task = Task -- Tasking.Task#TASK + local GroupSet = Task:GetGroups() + GroupSet:ForEachGroup( + function( TaskGroup ) + SetGroup:Add( TaskGroup, TaskGroup ) + end + ) + end + + return SetGroup + +end + + +--- Sets the Planned Task menu. +-- @param #MISSION self +-- @param Core.Menu#MENU_COALITION CommandCenterMenu +function MISSION:SetMenu() + self:F() + + for _, Task in pairs( self:GetTasks() ) do + local Task = Task -- Tasking.Task#TASK + Task:SetMenu() + end +end + + +--- Gets the COMMANDCENTER. +-- @param #MISSION self +-- @return Tasking.CommandCenter#COMMANDCENTER +function MISSION:GetCommandCenter() + return self.CommandCenter +end + +--- Sets the Assigned Task menu. +-- @param #MISSION self +-- @param Tasking.Task#TASK Task +-- @param #string MenuText The menu text. +-- @return #MISSION self +function MISSION:SetAssignedMenu( Task ) + + for _, Task in pairs( self.Tasks ) do + local Task = Task -- Tasking.Task#TASK + Task:RemoveMenu() + Task:SetAssignedMenu() + end + +end + +--- Removes a Task menu. +-- @param #MISSION self +-- @param Tasking.Task#TASK Task +-- @return #MISSION self +function MISSION:RemoveTaskMenu( Task ) + + Task:RemoveMenu() +end + + +--- Gets the mission menu for the coalition. +-- @param #MISSION self +-- @param Wrapper.Group#GROUP TaskGroup +-- @return Core.Menu#MENU_COALITION self +function MISSION:GetMissionMenu( TaskGroup ) + + local CommandCenter = self:GetCommandCenter() + local CommandCenterMenu = CommandCenter.CommandCenterMenu + + local MissionName = self:GetName() + + local TaskGroupName = TaskGroup:GetName() + local MissionMenu = MENU_GROUP:New( TaskGroup, MissionName, CommandCenterMenu ) + + return MissionMenu +end + + +--- Clears the mission menu for the coalition. +-- @param #MISSION self +-- @return #MISSION self +function MISSION:ClearMissionMenu() + self.MissionMenu:Remove() + self.MissionMenu = nil +end + +--- Get the TASK identified by the TaskNumber from the Mission. This function is useful in GoalFunctions. +-- @param #string TaskName The Name of the @{Task} within the @{Mission}. +-- @return Tasking.Task#TASK The Task +-- @return #nil Returns nil if no task was found. +function MISSION:GetTask( TaskName ) + self:F( { TaskName } ) + + return self.Tasks[TaskName] +end + + +--- Register a @{Task} to be completed within the @{Mission}. +-- Note that there can be multiple @{Task}s registered to be completed. +-- Each Task can be set a certain Goals. The Mission will not be completed until all Goals are reached. +-- @param #MISSION self +-- @param Tasking.Task#TASK Task is the @{Task} object. +-- @return Tasking.Task#TASK The task added. +function MISSION:AddTask( Task ) + + local TaskName = Task:GetTaskName() + self:F( TaskName ) + + self.Tasks[TaskName] = self.Tasks[TaskName] or { n = 0 } + + self.Tasks[TaskName] = Task + + self:GetCommandCenter():SetMenu() + + return Task +end + +--- Removes a @{Task} to be completed within the @{Mission}. +-- Note that there can be multiple @{Task}s registered to be completed. +-- Each Task can be set a certain Goals. The Mission will not be completed until all Goals are reached. +-- @param #MISSION self +-- @param Tasking.Task#TASK Task is the @{Task} object. +-- @return #nil The cleaned Task reference. +function MISSION:RemoveTask( Task ) + + local TaskName = Task:GetTaskName() + + self:F( TaskName ) + self.Tasks[TaskName] = self.Tasks[TaskName] or { n = 0 } + + -- Ensure everything gets garbarge collected. + self.Tasks[TaskName] = nil + Task = nil + + collectgarbage() + + self:GetCommandCenter():SetMenu() + + return nil +end + +--- Return the next @{Task} ID to be completed within the @{Mission}. +-- @param #MISSION self +-- @param Tasking.Task#TASK Task is the @{Task} object. +-- @return Tasking.Task#TASK The task added. +function MISSION:GetNextTaskID( Task ) + + local TaskName = Task:GetTaskName() + self:F( TaskName ) + self.Tasks[TaskName] = self.Tasks[TaskName] or { n = 0 } + + self.Tasks[TaskName].n = self.Tasks[TaskName].n + 1 + + return self.Tasks[TaskName].n +end + + + +--- old stuff + +--- Returns if a Mission has completed. +-- @return bool +function MISSION:IsCompleted() + self:F() + return self.MissionStatus == "ACCOMPLISHED" +end + +--- Set a Mission to completed. +function MISSION:Completed() + self:F() + self.MissionStatus = "ACCOMPLISHED" + self:StatusToClients() +end + +--- Returns if a Mission is ongoing. +-- treturn bool +function MISSION:IsOngoing() + self:F() + return self.MissionStatus == "ONGOING" +end + +--- Set a Mission to ongoing. +function MISSION:Ongoing() + self:F() + self.MissionStatus = "ONGOING" + --self:StatusToClients() +end + +--- Returns if a Mission is pending. +-- treturn bool +function MISSION:IsPending() + self:F() + return self.MissionStatus == "PENDING" +end + +--- Set a Mission to pending. +function MISSION:Pending() + self:F() + self.MissionStatus = "PENDING" + self:StatusToClients() +end + +--- Returns if a Mission has failed. +-- treturn bool +function MISSION:IsFailed() + self:F() + return self.MissionStatus == "FAILED" +end + +--- Set a Mission to failed. +function MISSION:Failed() + self:F() + self.MissionStatus = "FAILED" + self:StatusToClients() +end + +--- Send the status of the MISSION to all Clients. +function MISSION:StatusToClients() + self:F() + if self.MissionReportFlash then + for ClientID, Client in pairs( self._Clients ) do + Client:Message( self.MissionCoalition .. ' "' .. self.Name .. '": ' .. self.MissionStatus .. '! ( ' .. self.MissionPriority .. ' mission ) ', 10, "Mission Command: Mission Status") + end + end +end + +function MISSION:HasGroup( TaskGroup ) + local Has = false + + for TaskID, Task in pairs( self:GetTasks() ) do + local Task = Task -- Tasking.Task#TASK + if Task:HasGroup( TaskGroup ) then + Has = true + break + end + end + + return Has +end + +--- Create a summary report of the Mission (one line). +-- @param #MISSION self +-- @return #string +function MISSION:ReportSummary() + + local Report = REPORT:New() + + -- List the name of the mission. + local Name = self:GetName() + + -- Determine the status of the mission. + local Status = self:GetState() + + -- Determine how many tasks are remaining. + local TasksRemaining = 0 + for TaskID, Task in pairs( self:GetTasks() ) do + local Task = Task -- Tasking.Task#TASK + if Task:IsStateSuccess() or Task:IsStateFailed() then + else + TasksRemaining = TasksRemaining + 1 + end + end + + Report:Add( "Mission " .. Name .. " - " .. Status .. " - " .. TasksRemaining .. " tasks remaining." ) + + return Report:Text() +end + +--- Create a overview report of the Mission (multiple lines). +-- @param #MISSION self +-- @return #string +function MISSION:ReportOverview() + + local Report = REPORT:New() + + -- List the name of the mission. + local Name = self:GetName() + + -- Determine the status of the mission. + local Status = self:GetState() + + Report:Add( "Mission " .. Name .. " - State '" .. Status .. "'" ) + + -- Determine how many tasks are remaining. + local TasksRemaining = 0 + for TaskID, Task in pairs( self:GetTasks() ) do + local Task = Task -- Tasking.Task#TASK + Report:Add( "- " .. Task:ReportSummary() ) + end + + return Report:Text() +end + +--- Create a detailed report of the Mission, listing all the details of the Task. +-- @param #MISSION self +-- @return #string +function MISSION:ReportDetails() + + local Report = REPORT:New() + + -- List the name of the mission. + local Name = self:GetName() + + -- Determine the status of the mission. + local Status = self:GetState() + + Report:Add( "Mission " .. Name .. " - State '" .. Status .. "'" ) + + -- Determine how many tasks are remaining. + local TasksRemaining = 0 + for TaskID, Task in pairs( self:GetTasks() ) do + local Task = Task -- Tasking.Task#TASK + Report:Add( Task:ReportDetails() ) + end + + return Report:Text() +end + +--- Report the status of all MISSIONs to all active Clients. +function MISSION:ReportToAll() + self:F() + + local AlivePlayers = '' + for ClientID, Client in pairs( self._Clients ) do + if Client:GetDCSGroup() then + if Client:GetClientGroupDCSUnit() then + if Client:GetClientGroupDCSUnit():getLife() > 0.0 then + if AlivePlayers == '' then + AlivePlayers = ' Players: ' .. Client:GetClientGroupDCSUnit():getPlayerName() + else + AlivePlayers = AlivePlayers .. ' / ' .. Client:GetClientGroupDCSUnit():getPlayerName() + end + end + end + end + end + local Tasks = self:GetTasks() + local TaskText = "" + for TaskID, TaskData in pairs( Tasks ) do + TaskText = TaskText .. " - Task " .. TaskID .. ": " .. TaskData.Name .. ": " .. TaskData:GetGoalProgress() .. "\n" + end + MESSAGE:New( self.MissionCoalition .. ' "' .. self.Name .. '": ' .. self.MissionStatus .. ' ( ' .. self.MissionPriority .. ' mission )' .. AlivePlayers .. "\n" .. TaskText:gsub("\n$",""), 10, "Mission Command: Mission Report" ):ToAll() +end + + +--- Add a goal function to a MISSION. Goal functions are called when a @{TASK} within a mission has been completed. +-- @param function GoalFunction is the function defined by the mission designer to evaluate whether a certain goal has been reached after a @{TASK} finishes within the @{MISSION}. A GoalFunction must accept 2 parameters: Mission, Client, which contains the current MISSION object and the current CLIENT object respectively. +-- @usage +-- PatriotActivation = { +-- { "US SAM Patriot Zerti", false }, +-- { "US SAM Patriot Zegduleti", false }, +-- { "US SAM Patriot Gvleti", false } +-- } +-- +-- function DeployPatriotTroopsGoal( Mission, Client ) +-- +-- +-- -- Check if the cargo is all deployed for mission success. +-- for CargoID, CargoData in pairs( Mission._Cargos ) do +-- if Group.getByName( CargoData.CargoGroupName ) then +-- CargoGroup = Group.getByName( CargoData.CargoGroupName ) +-- if CargoGroup then +-- -- Check if the cargo is ready to activate +-- CurrentLandingZoneID = routines.IsUnitInZones( CargoGroup:getUnits()[1], Mission:GetTask( 2 ).LandingZones ) -- The second task is the Deploytask to measure mission success upon +-- if CurrentLandingZoneID then +-- if PatriotActivation[CurrentLandingZoneID][2] == false then +-- -- Now check if this is a new Mission Task to be completed... +-- trigger.action.setGroupAIOn( Group.getByName( PatriotActivation[CurrentLandingZoneID][1] ) ) +-- PatriotActivation[CurrentLandingZoneID][2] = true +-- MessageToBlue( "Mission Command: Message to all airborne units! The " .. PatriotActivation[CurrentLandingZoneID][1] .. " is armed. Our air defenses are now stronger.", 60, "BLUE/PatriotDefense" ) +-- MessageToRed( "Mission Command: Our satellite systems are detecting additional NATO air defenses. To all airborne units: Take care!!!", 60, "RED/PatriotDefense" ) +-- Mission:GetTask( 2 ):AddGoalCompletion( "Patriots activated", PatriotActivation[CurrentLandingZoneID][1], 1 ) -- Register Patriot activation as part of mission goal. +-- end +-- end +-- end +-- end +-- end +-- end +-- +-- local Mission = MISSIONSCHEDULER.AddMission( 'NATO Transport Troops', 'Operational', 'Transport 3 groups of air defense engineers from our barracks "Gold" and "Titan" to each patriot battery control center to activate our air defenses.', 'NATO' ) +-- Mission:AddGoalFunction( DeployPatriotTroopsGoal ) +function MISSION:AddGoalFunction( GoalFunction ) + self:F() + self.GoalFunction = GoalFunction +end + +--- Register a new @{CLIENT} to participate within the mission. +-- @param CLIENT Client is the @{CLIENT} object. The object must have been instantiated with @{CLIENT:New}. +-- @return CLIENT +-- @usage +-- Add a number of Client objects to the Mission. +-- Mission:AddClient( CLIENT:FindByName( 'US UH-1H*HOT-Deploy Troops 1', 'Transport 3 groups of air defense engineers from our barracks "Gold" and "Titan" to each patriot battery control center to activate our air defenses.' ):Transport() ) +-- Mission:AddClient( CLIENT:FindByName( 'US UH-1H*RAMP-Deploy Troops 3', 'Transport 3 groups of air defense engineers from our barracks "Gold" and "Titan" to each patriot battery control center to activate our air defenses.' ):Transport() ) +-- Mission:AddClient( CLIENT:FindByName( 'US UH-1H*HOT-Deploy Troops 2', 'Transport 3 groups of air defense engineers from our barracks "Gold" and "Titan" to each patriot battery control center to activate our air defenses.' ):Transport() ) +-- Mission:AddClient( CLIENT:FindByName( 'US UH-1H*RAMP-Deploy Troops 4', 'Transport 3 groups of air defense engineers from our barracks "Gold" and "Titan" to each patriot battery control center to activate our air defenses.' ):Transport() ) +function MISSION:AddClient( Client ) + self:F( { Client } ) + + local Valid = true + + if Valid then + self._Clients[Client.ClientName] = Client + end + + return Client +end + +--- Find a @{CLIENT} object within the @{MISSION} by its ClientName. +-- @param CLIENT ClientName is a string defining the Client Group as defined within the ME. +-- @return CLIENT +-- @usage +-- -- Seach for Client "Bomber" within the Mission. +-- local BomberClient = Mission:FindClient( "Bomber" ) +function MISSION:FindClient( ClientName ) + self:F( { self._Clients[ClientName] } ) + return self._Clients[ClientName] +end + + +--- Get all the TASKs from the Mission. This function is useful in GoalFunctions. +-- @return {TASK,...} Structure of TASKS with the @{TASK} number as the key. +-- @usage +-- -- Get Tasks from the Mission. +-- Tasks = Mission:GetTasks() +-- env.info( "Task 2 Completion = " .. Tasks[2]:GetGoalPercentage() .. "%" ) +function MISSION:GetTasks() + self:F() + + return self.Tasks +end + + +--[[ + _TransportExecuteStage: Defines the different stages of Transport unload/load execution. This table is internal and is used to control the validity of Transport load/unload timing. + + - _TransportExecuteStage.EXECUTING + - _TransportExecuteStage.SUCCESS + - _TransportExecuteStage.FAILED + +--]] +_TransportExecuteStage = { + NONE = 0, + EXECUTING = 1, + SUCCESS = 2, + FAILED = 3 +} + + +--- The MISSIONSCHEDULER is an OBJECT and is the main scheduler of ALL active MISSIONs registered within this scheduler. It's workings are considered internal and is automatically created when the Mission.lua file is included. +-- @type MISSIONSCHEDULER +-- @field #MISSIONSCHEDULER.MISSIONS Missions +MISSIONSCHEDULER = { + Missions = {}, + MissionCount = 0, + TimeIntervalCount = 0, + TimeIntervalShow = 150, + TimeSeconds = 14400, + TimeShow = 5 +} + +--- @type MISSIONSCHEDULER.MISSIONS +-- @list <#MISSION> Mission + +--- This is the main MISSIONSCHEDULER Scheduler function. It is considered internal and is automatically created when the Mission.lua file is included. +function MISSIONSCHEDULER.Scheduler() + + + -- loop through the missions in the TransportTasks + for MissionName, MissionData in pairs( MISSIONSCHEDULER.Missions ) do + + local Mission = MissionData -- #MISSION + + if not Mission:IsCompleted() then + + -- This flag will monitor if for this mission, there are clients alive. If this flag is still false at the end of the loop, the mission status will be set to Pending (if not Failed or Completed). + local ClientsAlive = false + + for ClientID, ClientData in pairs( Mission._Clients ) do + + local Client = ClientData -- Wrapper.Client#CLIENT + + if Client:IsAlive() then + + -- There is at least one Client that is alive... So the Mission status is set to Ongoing. + ClientsAlive = true + + -- If this Client was not registered as Alive before: + -- 1. We register the Client as Alive. + -- 2. We initialize the Client Tasks and make a link to the original Mission Task. + -- 3. We initialize the Cargos. + -- 4. We flag the Mission as Ongoing. + if not Client.ClientAlive then + Client.ClientAlive = true + Client.ClientBriefingShown = false + for TaskNumber, Task in pairs( Mission._Tasks ) do + -- Note that this a deepCopy. Each client must have their own Tasks with own Stages!!! + Client._Tasks[TaskNumber] = routines.utils.deepCopy( Mission._Tasks[TaskNumber] ) + -- Each MissionTask must point to the original Mission. + Client._Tasks[TaskNumber].MissionTask = Mission._Tasks[TaskNumber] + Client._Tasks[TaskNumber].Cargos = Mission._Tasks[TaskNumber].Cargos + Client._Tasks[TaskNumber].LandingZones = Mission._Tasks[TaskNumber].LandingZones + end + + Mission:Ongoing() + end + + + -- For each Client, check for each Task the state and evolve the mission. + -- This flag will indicate if the Task of the Client is Complete. + local TaskComplete = false + + for TaskNumber, Task in pairs( Client._Tasks ) do + + if not Task.Stage then + Task:SetStage( 1 ) + end + + + local TransportTime = timer.getTime() + + if not Task:IsDone() then + + if Task:Goal() then + Task:ShowGoalProgress( Mission, Client ) + end + + --env.info( 'Scheduler: Mission = ' .. Mission.Name .. ' / Client = ' .. Client.ClientName .. ' / Task = ' .. Task.Name .. ' / Stage = ' .. Task.ActiveStage .. ' - ' .. Task.Stage.Name .. ' - ' .. Task.Stage.StageType ) + + -- Action + if Task:StageExecute() then + Task.Stage:Execute( Mission, Client, Task ) + end + + -- Wait until execution is finished + if Task.ExecuteStage == _TransportExecuteStage.EXECUTING then + Task.Stage:Executing( Mission, Client, Task ) + end + + -- Validate completion or reverse to earlier stage + if Task.Time + Task.Stage.WaitTime <= TransportTime then + Task:SetStage( Task.Stage:Validate( Mission, Client, Task ) ) + end + + if Task:IsDone() then + --env.info( 'Scheduler: Mission '.. Mission.Name .. ' Task ' .. Task.Name .. ' Stage ' .. Task.Stage.Name .. ' done. TaskComplete = ' .. string.format ( "%s", TaskComplete and "true" or "false" ) ) + TaskComplete = true -- when a task is not yet completed, a mission cannot be completed + + else + -- break only if this task is not yet done, so that future task are not yet activated. + TaskComplete = false -- when a task is not yet completed, a mission cannot be completed + --env.info( 'Scheduler: Mission "'.. Mission.Name .. '" Task "' .. Task.Name .. '" Stage "' .. Task.Stage.Name .. '" break. TaskComplete = ' .. string.format ( "%s", TaskComplete and "true" or "false" ) ) + break + end + + if TaskComplete then + + if Mission.GoalFunction ~= nil then + Mission.GoalFunction( Mission, Client ) + end + if MISSIONSCHEDULER.Scoring then + MISSIONSCHEDULER.Scoring:_AddMissionTaskScore( Client:GetClientGroupDCSUnit(), Mission.Name, 25 ) + end + +-- if not Mission:IsCompleted() then +-- end + end + end + end + + local MissionComplete = true + for TaskNumber, Task in pairs( Mission._Tasks ) do + if Task:Goal() then +-- Task:ShowGoalProgress( Mission, Client ) + if Task:IsGoalReached() then + else + MissionComplete = false + end + else + MissionComplete = false -- If there is no goal, the mission should never be ended. The goal status will be set somewhere else. + end + end + + if MissionComplete then + Mission:Completed() + if MISSIONSCHEDULER.Scoring then + MISSIONSCHEDULER.Scoring:_AddMissionScore( Mission.Name, 100 ) + end + else + if TaskComplete then + -- Reset for new tasking of active client + Client.ClientAlive = false -- Reset the client tasks. + end + end + + + else + if Client.ClientAlive then + env.info( 'Scheduler: Client "' .. Client.ClientName .. '" is inactive.' ) + Client.ClientAlive = false + + -- This is tricky. If we sanitize Client._Tasks before sanitizing Client._Tasks[TaskNumber].MissionTask, then the original MissionTask will be sanitized, and will be lost within the garbage collector. + -- So first sanitize Client._Tasks[TaskNumber].MissionTask, after that, sanitize only the whole _Tasks structure... + --Client._Tasks[TaskNumber].MissionTask = nil + --Client._Tasks = nil + end + end + end + + -- If all Clients of this Mission are not activated, then the Mission status needs to be put back into Pending status. + -- But only if the Mission was Ongoing. In case the Mission is Completed or Failed, the Mission status may not be changed. In these cases, this will be the last run of this Mission in the Scheduler. + if ClientsAlive == false then + if Mission:IsOngoing() then + -- Mission status back to pending... + Mission:Pending() + end + end + end + + Mission:StatusToClients() + + if Mission:ReportTrigger() then + Mission:ReportToAll() + end + end + + return true +end + +--- Start the MISSIONSCHEDULER. +function MISSIONSCHEDULER.Start() + if MISSIONSCHEDULER ~= nil then + --MISSIONSCHEDULER.SchedulerId = routines.scheduleFunction( MISSIONSCHEDULER.Scheduler, { }, 0, 2 ) + MISSIONSCHEDULER.SchedulerId = SCHEDULER:New( nil, MISSIONSCHEDULER.Scheduler, { }, 0, 2 ) + end +end + +--- Stop the MISSIONSCHEDULER. +function MISSIONSCHEDULER.Stop() + if MISSIONSCHEDULER.SchedulerId then + routines.removeFunction(MISSIONSCHEDULER.SchedulerId) + MISSIONSCHEDULER.SchedulerId = nil + end +end + +--- This is the main MISSION declaration method. Each Mission is like the master or a Mission orchestration between, Clients, Tasks, Stages etc. +-- @param Mission is the MISSION object instantiated by @{MISSION:New}. +-- @return MISSION +-- @usage +-- -- Declare a mission. +-- Mission = MISSION:New( 'Russia Transport Troops SA-6', +-- 'Operational', +-- 'Transport troops from the control center to one of the SA-6 SAM sites to activate their operation.', +-- 'Russia' ) +-- MISSIONSCHEDULER:AddMission( Mission ) +function MISSIONSCHEDULER.AddMission( Mission ) + MISSIONSCHEDULER.Missions[Mission.Name] = Mission + MISSIONSCHEDULER.MissionCount = MISSIONSCHEDULER.MissionCount + 1 + -- Add an overall AI Client for the AI tasks... This AI Client will facilitate the Events in the background for each Task. + --MissionAdd:AddClient( CLIENT:Register( 'AI' ) ) + + return Mission +end + +--- Remove a MISSION from the MISSIONSCHEDULER. +-- @param MissionName is the name of the MISSION given at declaration using @{AddMission}. +-- @usage +-- -- Declare a mission. +-- Mission = MISSION:New( 'Russia Transport Troops SA-6', +-- 'Operational', +-- 'Transport troops from the control center to one of the SA-6 SAM sites to activate their operation.', +-- 'Russia' ) +-- MISSIONSCHEDULER:AddMission( Mission ) +-- +-- -- Now remove the Mission. +-- MISSIONSCHEDULER:RemoveMission( 'Russia Transport Troops SA-6' ) +function MISSIONSCHEDULER.RemoveMission( MissionName ) + MISSIONSCHEDULER.Missions[MissionName] = nil + MISSIONSCHEDULER.MissionCount = MISSIONSCHEDULER.MissionCount - 1 +end + +--- Find a MISSION within the MISSIONSCHEDULER. +-- @param MissionName is the name of the MISSION given at declaration using @{AddMission}. +-- @return MISSION +-- @usage +-- -- Declare a mission. +-- Mission = MISSION:New( 'Russia Transport Troops SA-6', +-- 'Operational', +-- 'Transport troops from the control center to one of the SA-6 SAM sites to activate their operation.', +-- 'Russia' ) +-- MISSIONSCHEDULER:AddMission( Mission ) +-- +-- -- Now find the Mission. +-- MissionFind = MISSIONSCHEDULER:FindMission( 'Russia Transport Troops SA-6' ) +function MISSIONSCHEDULER.FindMission( MissionName ) + return MISSIONSCHEDULER.Missions[MissionName] +end + +-- Internal function used by the MISSIONSCHEDULER menu. +function MISSIONSCHEDULER.ReportMissionsShow( ) + for MissionName, Mission in pairs( MISSIONSCHEDULER.Missions ) do + Mission.MissionReportShow = true + Mission.MissionReportFlash = false + end +end + +-- Internal function used by the MISSIONSCHEDULER menu. +function MISSIONSCHEDULER.ReportMissionsFlash( TimeInterval ) + local Count = 0 + for MissionName, Mission in pairs( MISSIONSCHEDULER.Missions ) do + Mission.MissionReportShow = false + Mission.MissionReportFlash = true + Mission.MissionReportTrigger = timer.getTime() + Count * TimeInterval + Mission.MissionTimeInterval = MISSIONSCHEDULER.MissionCount * TimeInterval + env.info( "TimeInterval = " .. Mission.MissionTimeInterval ) + Count = Count + 1 + end +end + +-- Internal function used by the MISSIONSCHEDULER menu. +function MISSIONSCHEDULER.ReportMissionsHide( Prm ) + for MissionName, Mission in pairs( MISSIONSCHEDULER.Missions ) do + Mission.MissionReportShow = false + Mission.MissionReportFlash = false + end +end + +--- Enables a MENU option in the communications menu under F10 to control the status of the active missions. +-- This function should be called only once when starting the MISSIONSCHEDULER. +function MISSIONSCHEDULER.ReportMenu() + local ReportMenu = SUBMENU:New( 'Status' ) + local ReportMenuShow = COMMANDMENU:New( 'Show Report Missions', ReportMenu, MISSIONSCHEDULER.ReportMissionsShow, 0 ) + local ReportMenuFlash = COMMANDMENU:New('Flash Report Missions', ReportMenu, MISSIONSCHEDULER.ReportMissionsFlash, 120 ) + local ReportMenuHide = COMMANDMENU:New( 'Hide Report Missions', ReportMenu, MISSIONSCHEDULER.ReportMissionsHide, 0 ) +end + +--- Show the remaining mission time. +function MISSIONSCHEDULER:TimeShow() + self.TimeIntervalCount = self.TimeIntervalCount + 1 + if self.TimeIntervalCount >= self.TimeTriggerShow then + local TimeMsg = string.format("%00d", ( self.TimeSeconds / 60 ) - ( timer.getTime() / 60 )) .. ' minutes left until mission reload.' + MESSAGE:New( TimeMsg, self.TimeShow, "Mission time" ):ToAll() + self.TimeIntervalCount = 0 + end +end + +function MISSIONSCHEDULER:Time( TimeSeconds, TimeIntervalShow, TimeShow ) + + self.TimeIntervalCount = 0 + self.TimeSeconds = TimeSeconds + self.TimeIntervalShow = TimeIntervalShow + self.TimeShow = TimeShow +end + +--- Adds a mission scoring to the game. +function MISSIONSCHEDULER:Scoring( Scoring ) + + self.Scoring = Scoring +end + +--- This module contains the TASK class. +-- +-- 1) @{#TASK} class, extends @{Core.Base#BASE} +-- ============================================ +-- 1.1) The @{#TASK} class implements the methods for task orchestration within MOOSE. +-- ---------------------------------------------------------------------------------------- +-- The class provides a couple of methods to: +-- +-- * @{#TASK.AssignToGroup}():Assign a task to a group (of players). +-- * @{#TASK.AddProcess}():Add a @{Process} to a task. +-- * @{#TASK.RemoveProcesses}():Remove a running @{Process} from a running task. +-- * @{#TASK.SetStateMachine}():Set a @{Fsm} to a task. +-- * @{#TASK.RemoveStateMachine}():Remove @{Fsm} from a task. +-- * @{#TASK.HasStateMachine}():Enquire if the task has a @{Fsm} +-- * @{#TASK.AssignToUnit}(): Assign a task to a unit. (Needs to be implemented in the derived classes from @{#TASK}. +-- * @{#TASK.UnAssignFromUnit}(): Unassign the task from a unit. +-- +-- 1.2) Set and enquire task status (beyond the task state machine processing). +-- ---------------------------------------------------------------------------- +-- A task needs to implement as a minimum the following task states: +-- +-- * **Success**: Expresses the successful execution and finalization of the task. +-- * **Failed**: Expresses the failure of a task. +-- * **Planned**: Expresses that the task is created, but not yet in execution and is not assigned yet. +-- * **Assigned**: Expresses that the task is assigned to a Group of players, and that the task is in execution mode. +-- +-- A task may also implement the following task states: +-- +-- * **Rejected**: Expresses that the task is rejected by a player, who was requested to accept the task. +-- * **Cancelled**: Expresses that the task is cancelled by HQ or through a logical situation where a cancellation of the task is required. +-- +-- A task can implement more statusses than the ones outlined above. Please consult the documentation of the specific tasks to understand the different status modelled. +-- +-- The status of tasks can be set by the methods **State** followed by the task status. An example is `StateAssigned()`. +-- The status of tasks can be enquired by the methods **IsState** followed by the task status name. An example is `if IsStateAssigned() then`. +-- +-- 1.3) Add scoring when reaching a certain task status: +-- ----------------------------------------------------- +-- Upon reaching a certain task status in a task, additional scoring can be given. If the Mission has a scoring system attached, the scores will be added to the mission scoring. +-- Use the method @{#TASK.AddScore}() to add scores when a status is reached. +-- +-- 1.4) Task briefing: +-- ------------------- +-- A task briefing can be given that is shown to the player when he is assigned to the task. +-- +-- === +-- +-- ### Authors: FlightControl - Design and Programming +-- +-- @module Task + +--- The TASK class +-- @type TASK +-- @field Core.Scheduler#SCHEDULER TaskScheduler +-- @field Tasking.Mission#MISSION Mission +-- @field Core.Set#SET_GROUP SetGroup The Set of Groups assigned to the Task +-- @field Core.Fsm#FSM_PROCESS FsmTemplate +-- @field Tasking.Mission#MISSION Mission +-- @field Tasking.CommandCenter#COMMANDCENTER CommandCenter +-- @extends Core.Fsm#FSM_TASK +TASK = { + ClassName = "TASK", + TaskScheduler = nil, + ProcessClasses = {}, -- The container of the Process classes that will be used to create and assign new processes for the task to ProcessUnits. + Processes = {}, -- The container of actual process objects instantiated and assigned to ProcessUnits. + Players = nil, + Scores = {}, + Menu = {}, + SetGroup = nil, + FsmTemplate = nil, + Mission = nil, + CommandCenter = nil, +} + +--- FSM PlayerAborted event handler prototype for TASK. +-- @function [parent=#TASK] OnAfterPlayerAborted +-- @param #TASK self +-- @param Wrapper.Unit#UNIT PlayerUnit The Unit of the Player when he went back to spectators or left the mission. +-- @param #string PlayerName The name of the Player. + +--- FSM PlayerCrashed event handler prototype for TASK. +-- @function [parent=#TASK] OnAfterPlayerCrashed +-- @param #TASK self +-- @param Wrapper.Unit#UNIT PlayerUnit The Unit of the Player when he crashed in the mission. +-- @param #string PlayerName The name of the Player. + +--- FSM PlayerDead event handler prototype for TASK. +-- @function [parent=#TASK] OnAfterPlayerDead +-- @param #TASK self +-- @param Wrapper.Unit#UNIT PlayerUnit The Unit of the Player when he died in the mission. +-- @param #string PlayerName The name of the Player. + +--- FSM Fail synchronous event function for TASK. +-- Use this event to Fail the Task. +-- @function [parent=#TASK] Fail +-- @param #TASK self + +--- FSM Fail asynchronous event function for TASK. +-- Use this event to Fail the Task. +-- @function [parent=#TASK] __Fail +-- @param #TASK self + +--- FSM Abort synchronous event function for TASK. +-- Use this event to Abort the Task. +-- @function [parent=#TASK] Abort +-- @param #TASK self + +--- FSM Abort asynchronous event function for TASK. +-- Use this event to Abort the Task. +-- @function [parent=#TASK] __Abort +-- @param #TASK self + +--- FSM Success synchronous event function for TASK. +-- Use this event to make the Task a Success. +-- @function [parent=#TASK] Success +-- @param #TASK self + +--- FSM Success asynchronous event function for TASK. +-- Use this event to make the Task a Success. +-- @function [parent=#TASK] __Success +-- @param #TASK self + +--- FSM Cancel synchronous event function for TASK. +-- Use this event to Cancel the Task. +-- @function [parent=#TASK] Cancel +-- @param #TASK self + +--- FSM Cancel asynchronous event function for TASK. +-- Use this event to Cancel the Task. +-- @function [parent=#TASK] __Cancel +-- @param #TASK self + +--- FSM Replan synchronous event function for TASK. +-- Use this event to Replan the Task. +-- @function [parent=#TASK] Replan +-- @param #TASK self + +--- FSM Replan asynchronous event function for TASK. +-- Use this event to Replan the Task. +-- @function [parent=#TASK] __Replan +-- @param #TASK self + + +--- Instantiates a new TASK. Should never be used. Interface Class. +-- @param #TASK self +-- @param Tasking.Mission#MISSION Mission The mission wherein the Task is registered. +-- @param Core.Set#SET_GROUP SetGroupAssign The set of groups for which the Task can be assigned. +-- @param #string TaskName The name of the Task +-- @param #string TaskType The type of the Task +-- @return #TASK self +function TASK:New( Mission, SetGroupAssign, TaskName, TaskType ) + + local self = BASE:Inherit( self, FSM_TASK:New() ) -- Core.Fsm#FSM_TASK + + self:SetStartState( "Planned" ) + self:AddTransition( "Planned", "Assign", "Assigned" ) + self:AddTransition( "Assigned", "AssignUnit", "Assigned" ) + self:AddTransition( "Assigned", "Success", "Success" ) + self:AddTransition( "Assigned", "Fail", "Failed" ) + self:AddTransition( "Assigned", "Abort", "Aborted" ) + self:AddTransition( "Assigned", "Cancel", "Cancelled" ) + self:AddTransition( "*", "PlayerCrashed", "*" ) + self:AddTransition( "*", "PlayerAborted", "*" ) + self:AddTransition( "*", "PlayerDead", "*" ) + self:AddTransition( { "Failed", "Aborted", "Cancelled" }, "Replan", "Planned" ) + + self:E( "New TASK " .. TaskName ) + + self.Processes = {} + self.Fsm = {} + + self.Mission = Mission + self.CommandCenter = Mission:GetCommandCenter() + + self.SetGroup = SetGroupAssign + + self:SetType( TaskType ) + self:SetName( TaskName ) + self:SetID( Mission:GetNextTaskID( self ) ) -- The Mission orchestrates the task sequences .. + + self.TaskBriefing = "You are invited for the task: " .. self.TaskName .. "." + + self.FsmTemplate = self.FsmTemplate or FSM_PROCESS:New() + + -- Handle the birth of new planes within the assigned set. + + + -- Handle when a player crashes ... + -- The Task is UnAssigned from the Unit. + -- When there is no Unit left running the Task, and all of the Players crashed, the Task goes into Failed ... +-- self:EventOnCrash( +-- --- @param #TASK self +-- -- @param Core.Event#EVENTDATA EventData +-- function( self, EventData ) +-- self:E( "In LeaveUnit" ) +-- self:E( { "State", self:GetState() } ) +-- if self:IsStateAssigned() then +-- local TaskUnit = EventData.IniUnit +-- local TaskGroup = EventData.IniUnit:GetGroup() +-- self:E( self.SetGroup:IsIncludeObject( TaskGroup ) ) +-- if self.SetGroup:IsIncludeObject( TaskGroup ) then +-- self:UnAssignFromUnit( TaskUnit ) +-- end +-- self:MessageToGroups( TaskUnit:GetPlayerName() .. " crashed!, and has aborted Task " .. self:GetName() ) +-- end +-- end +-- ) +-- + + Mission:AddTask( self ) + + return self +end + +--- Get the Task FSM Process Template +-- @param #TASK self +-- @return Core.Fsm#FSM_PROCESS +function TASK:GetUnitProcess() + + return self.FsmTemplate +end + +--- Sets the Task FSM Process Template +-- @param #TASK self +-- @param Core.Fsm#FSM_PROCESS +function TASK:SetUnitProcess( FsmTemplate ) + + self.FsmTemplate = FsmTemplate +end + +--- Add a PlayerUnit to join the Task. +-- For each Group within the Task, the Unit is check if it can join the Task. +-- If the Unit was not part of the Task, false is returned. +-- If the Unit is part of the Task, true is returned. +-- @param #TASK self +-- @param Wrapper.Unit#UNIT PlayerUnit The CLIENT or UNIT of the Player joining the Mission. +-- @return #boolean true if Unit is part of the Task. +function TASK:JoinUnit( PlayerUnit ) + self:F( { PlayerUnit = PlayerUnit } ) + + local PlayerUnitAdded = false + + local PlayerGroups = self:GetGroups() + local PlayerGroup = PlayerUnit:GetGroup() + + -- Is the PlayerGroup part of the PlayerGroups? + if PlayerGroups:IsIncludeObject( PlayerGroup ) then + + -- Check if the PlayerGroup is already assigned to the Task. If yes, the PlayerGroup is added to the Task. + -- If the PlayerGroup is not assigned to the Task, the menu needs to be set. In that case, the PlayerUnit will become the GroupPlayer leader. + if self:IsStatePlanned() or self:IsStateReplanned() then + self:SetMenuForGroup( PlayerGroup ) + self:MessageToGroups( PlayerUnit:GetPlayerName() .. " is planning to join Task " .. self:GetName() ) + end + if self:IsStateAssigned() then + local IsAssignedToGroup = self:IsAssignedToGroup( PlayerGroup ) + self:E( { IsAssignedToGroup = IsAssignedToGroup } ) + if IsAssignedToGroup then + self:AssignToUnit( PlayerUnit ) + self:MessageToGroups( PlayerUnit:GetPlayerName() .. " joined Task " .. self:GetName() ) + end + end + end + + return PlayerUnitAdded +end + +--- Abort a PlayerUnit from a Task. +-- If the Unit was not part of the Task, false is returned. +-- If the Unit is part of the Task, true is returned. +-- @param #TASK self +-- @param Wrapper.Unit#UNIT PlayerUnit The CLIENT or UNIT of the Player aborting the Task. +-- @return #boolean true if Unit is part of the Task. +function TASK:AbortUnit( PlayerUnit ) + self:F( { PlayerUnit = PlayerUnit } ) + + local PlayerUnitAborted = false + + local PlayerGroups = self:GetGroups() + local PlayerGroup = PlayerUnit:GetGroup() + + -- Is the PlayerGroup part of the PlayerGroups? + if PlayerGroups:IsIncludeObject( PlayerGroup ) then + + -- Check if the PlayerGroup is already assigned to the Task. If yes, the PlayerGroup is aborted from the Task. + -- If the PlayerUnit was the last unit of the PlayerGroup, the menu needs to be removed from the Group. + if self:IsStateAssigned() then + local IsAssignedToGroup = self:IsAssignedToGroup( PlayerGroup ) + self:E( { IsAssignedToGroup = IsAssignedToGroup } ) + if IsAssignedToGroup then + self:UnAssignFromUnit( PlayerUnit ) + self:MessageToGroups( PlayerUnit:GetPlayerName() .. " aborted Task " .. self:GetName() ) + self:E( { TaskGroup = PlayerGroup:GetName(), GetUnits = PlayerGroup:GetUnits() } ) + if #PlayerGroup:GetUnits() == 1 then + PlayerGroup:SetState( PlayerGroup, "Assigned", nil ) + self:RemoveMenuForGroup( PlayerGroup ) + end + self:PlayerAborted( PlayerUnit ) + end + end + end + + return PlayerUnitAborted +end + +--- A PlayerUnit crashed in a Task. Abort the Player. +-- If the Unit was not part of the Task, false is returned. +-- If the Unit is part of the Task, true is returned. +-- @param #TASK self +-- @param Wrapper.Unit#UNIT PlayerUnit The CLIENT or UNIT of the Player aborting the Task. +-- @return #boolean true if Unit is part of the Task. +function TASK:CrashUnit( PlayerUnit ) + self:F( { PlayerUnit = PlayerUnit } ) + + local PlayerUnitCrashed = false + + local PlayerGroups = self:GetGroups() + local PlayerGroup = PlayerUnit:GetGroup() + + -- Is the PlayerGroup part of the PlayerGroups? + if PlayerGroups:IsIncludeObject( PlayerGroup ) then + + -- Check if the PlayerGroup is already assigned to the Task. If yes, the PlayerGroup is aborted from the Task. + -- If the PlayerUnit was the last unit of the PlayerGroup, the menu needs to be removed from the Group. + if self:IsStateAssigned() then + local IsAssignedToGroup = self:IsAssignedToGroup( PlayerGroup ) + self:E( { IsAssignedToGroup = IsAssignedToGroup } ) + if IsAssignedToGroup then + self:UnAssignFromUnit( PlayerUnit ) + self:MessageToGroups( PlayerUnit:GetPlayerName() .. " crashed in Task " .. self:GetName() ) + self:E( { TaskGroup = PlayerGroup:GetName(), GetUnits = PlayerGroup:GetUnits() } ) + if #PlayerGroup:GetUnits() == 1 then + PlayerGroup:SetState( PlayerGroup, "Assigned", nil ) + self:RemoveMenuForGroup( PlayerGroup ) + end + self:PlayerCrashed( PlayerUnit ) + end + end + end + + return PlayerUnitCrashed +end + + + +--- Gets the Mission to where the TASK belongs. +-- @param #TASK self +-- @return Tasking.Mission#MISSION +function TASK:GetMission() + + return self.Mission +end + + +--- Gets the SET_GROUP assigned to the TASK. +-- @param #TASK self +-- @return Core.Set#SET_GROUP +function TASK:GetGroups() + return self.SetGroup +end + + + +--- Assign the @{Task}to a @{Group}. +-- @param #TASK self +-- @param Wrapper.Group#GROUP TaskGroup +-- @return #TASK +function TASK:AssignToGroup( TaskGroup ) + self:F2( TaskGroup:GetName() ) + + local TaskGroupName = TaskGroup:GetName() + + TaskGroup:SetState( TaskGroup, "Assigned", self ) + + self:RemoveMenuForGroup( TaskGroup ) + self:SetAssignedMenuForGroup( TaskGroup ) + + local TaskUnits = TaskGroup:GetUnits() + for UnitID, UnitData in pairs( TaskUnits ) do + local TaskUnit = UnitData -- Wrapper.Unit#UNIT + local PlayerName = TaskUnit:GetPlayerName() + self:E(PlayerName) + if PlayerName ~= nil or PlayerName ~= "" then + self:AssignToUnit( TaskUnit ) + end + end + + return self +end + +--- +-- @param #TASK self +-- @param Wrapper.Group#GROUP FindGroup +-- @return #boolean +function TASK:HasGroup( FindGroup ) + + self:GetGroups():FilterOnce() -- Ensure that the filter is updated. + return self:GetGroups():IsIncludeObject( FindGroup ) + +end + +--- Assign the @{Task} to an alive @{Unit}. +-- @param #TASK self +-- @param Wrapper.Unit#UNIT TaskUnit +-- @return #TASK self +function TASK:AssignToUnit( TaskUnit ) + self:F( TaskUnit:GetName() ) + + local FsmTemplate = self:GetUnitProcess() + + -- Assign a new FsmUnit to TaskUnit. + local FsmUnit = self:SetStateMachine( TaskUnit, FsmTemplate:Copy( TaskUnit, self ) ) -- Core.Fsm#FSM_PROCESS + self:E({"Address FsmUnit", tostring( FsmUnit ) } ) + + FsmUnit:SetStartState( "Planned" ) + FsmUnit:Accept() -- Each Task needs to start with an Accept event to start the flow. + + return self +end + +--- UnAssign the @{Task} from an alive @{Unit}. +-- @param #TASK self +-- @param Wrapper.Unit#UNIT TaskUnit +-- @return #TASK self +function TASK:UnAssignFromUnit( TaskUnit ) + self:F( TaskUnit ) + + self:RemoveStateMachine( TaskUnit ) + + return self +end + +--- Send a message of the @{Task} to the assigned @{Group}s. +-- @param #TASK self +function TASK:MessageToGroups( Message ) + self:F( { Message = Message } ) + + local Mission = self:GetMission() + local CC = Mission:GetCommandCenter() + + for TaskGroupName, TaskGroup in pairs( self.SetGroup:GetSet() ) do + CC:MessageToGroup( Message, TaskGroup ) + end +end + + +--- Send the briefng message of the @{Task} to the assigned @{Group}s. +-- @param #TASK self +function TASK:SendBriefingToAssignedGroups() + self:F2() + + for TaskGroupName, TaskGroup in pairs( self.SetGroup:GetSet() ) do + + if self:IsAssignedToGroup( TaskGroup ) then + TaskGroup:Message( self.TaskBriefing, 60 ) + end + end +end + + +--- Assign the @{Task} from the @{Group}s. +-- @param #TASK self +function TASK:UnAssignFromGroups() + self:F2() + + for TaskGroupName, TaskGroup in pairs( self.SetGroup:GetSet() ) do + + TaskGroup:SetState( TaskGroup, "Assigned", nil ) + + self:RemoveMenuForGroup( TaskGroup ) + + local TaskUnits = TaskGroup:GetUnits() + for UnitID, UnitData in pairs( TaskUnits ) do + local TaskUnit = UnitData -- Wrapper.Unit#UNIT + local PlayerName = TaskUnit:GetPlayerName() + if PlayerName ~= nil or PlayerName ~= "" then + self:UnAssignFromUnit( TaskUnit ) + end + end + end +end + +--- Returns if the @{Task} is assigned to the Group. +-- @param #TASK self +-- @param Wrapper.Group#GROUP TaskGroup +-- @return #boolean +function TASK:IsAssignedToGroup( TaskGroup ) + + local TaskGroupName = TaskGroup:GetName() + + if self:IsStateAssigned() then + if TaskGroup:GetState( TaskGroup, "Assigned" ) == self then + return true + end + end + + return false +end + +--- Returns if the @{Task} has still alive and assigned Units. +-- @param #TASK self +-- @return #boolean +function TASK:HasAliveUnits() + self:F() + + for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do + if self:IsStateAssigned() then + if self:IsAssignedToGroup( TaskGroup ) then + for TaskUnitID, TaskUnit in pairs( TaskGroup:GetUnits() ) do + if TaskUnit:IsAlive() then + self:T( { HasAliveUnits = true } ) + return true + end + end + end + end + end + + self:T( { HasAliveUnits = false } ) + return false +end + +--- Set the menu options of the @{Task} to all the groups in the SetGroup. +-- @param #TASK self +function TASK:SetMenu() + self:F() + + self.SetGroup:Flush() + for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do + self:RemoveMenuForGroup( TaskGroup ) + if self:IsStatePlanned() or self:IsStateReplanned() then + self:SetMenuForGroup( TaskGroup ) + end + end +end + + +--- Remove the menu options of the @{Task} to all the groups in the SetGroup. +-- @param #TASK self +-- @return #TASK self +function TASK:RemoveMenu() + + for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do + self:RemoveMenuForGroup( TaskGroup ) + end +end + + +--- Set the Menu for a Group +-- @param #TASK self +function TASK:SetMenuForGroup( TaskGroup ) + + if not self:IsAssignedToGroup( TaskGroup ) then + self:SetPlannedMenuForGroup( TaskGroup, self:GetTaskName() ) + else + self:SetAssignedMenuForGroup( TaskGroup ) + end +end + + +--- Set the planned menu option of the @{Task}. +-- @param #TASK self +-- @param Wrapper.Group#GROUP TaskGroup +-- @param #string MenuText The menu text. +-- @return #TASK self +function TASK:SetPlannedMenuForGroup( TaskGroup, MenuText ) + self:E( TaskGroup:GetName() ) + + local Mission = self:GetMission() + local MissionMenu = Mission:GetMissionMenu( TaskGroup ) + + local TaskType = self:GetType() + local TaskTypeMenu = MENU_GROUP:New( TaskGroup, TaskType, MissionMenu ) + local TaskMenu = MENU_GROUP_COMMAND:New( TaskGroup, MenuText, TaskTypeMenu, self.MenuAssignToGroup, { self = self, TaskGroup = TaskGroup } ) + + return self +end + +--- Set the assigned menu options of the @{Task}. +-- @param #TASK self +-- @param Wrapper.Group#GROUP TaskGroup +-- @return #TASK self +function TASK:SetAssignedMenuForGroup( TaskGroup ) + self:E( TaskGroup:GetName() ) + + local Mission = self:GetMission() + local MissionMenu = Mission:GetMissionMenu( TaskGroup ) + + self:E( { MissionMenu = MissionMenu } ) + + local TaskTypeMenu = MENU_GROUP_COMMAND:New( TaskGroup, "Task Status", MissionMenu, self.MenuTaskStatus, { self = self, TaskGroup = TaskGroup } ) + local TaskMenu = MENU_GROUP_COMMAND:New( TaskGroup, "Abort Task", MissionMenu, self.MenuTaskAbort, { self = self, TaskGroup = TaskGroup } ) + + return self +end + +--- Remove the menu option of the @{Task} for a @{Group}. +-- @param #TASK self +-- @param Wrapper.Group#GROUP TaskGroup +-- @return #TASK self +function TASK:RemoveMenuForGroup( TaskGroup ) + + local Mission = self:GetMission() + local MissionName = Mission:GetName() + + local MissionMenu = Mission:GetMissionMenu( TaskGroup ) + MissionMenu:Remove() +end + +function TASK.MenuAssignToGroup( MenuParam ) + + local self = MenuParam.self + local TaskGroup = MenuParam.TaskGroup + + self:E( "Assigned menu selected") + + self:AssignToGroup( TaskGroup ) +end + +function TASK.MenuTaskStatus( MenuParam ) + + local self = MenuParam.self + local TaskGroup = MenuParam.TaskGroup + + --self:AssignToGroup( TaskGroup ) +end + +function TASK.MenuTaskAbort( MenuParam ) + + local self = MenuParam.self + local TaskGroup = MenuParam.TaskGroup + + --self:AssignToGroup( TaskGroup ) +end + + + +--- Returns the @{Task} name. +-- @param #TASK self +-- @return #string TaskName +function TASK:GetTaskName() + return self.TaskName +end + + + + +--- Get the default or currently assigned @{Process} template with key ProcessName. +-- @param #TASK self +-- @param #string ProcessName +-- @return Core.Fsm#FSM_PROCESS +function TASK:GetProcessTemplate( ProcessName ) + + local ProcessTemplate = self.ProcessClasses[ProcessName] + + return ProcessTemplate +end + + + +-- TODO: Obscolete? +--- Fail processes from @{Task} with key @{Unit} +-- @param #TASK self +-- @param #string TaskUnitName +-- @return #TASK self +function TASK:FailProcesses( TaskUnitName ) + + for ProcessID, ProcessData in pairs( self.Processes[TaskUnitName] ) do + local Process = ProcessData + Process.Fsm:Fail() + end +end + +--- Add a FiniteStateMachine to @{Task} with key Task@{Unit} +-- @param #TASK self +-- @param Wrapper.Unit#UNIT TaskUnit +-- @return #TASK self +function TASK:SetStateMachine( TaskUnit, Fsm ) + self:F( { TaskUnit, self.Fsm[TaskUnit] ~= nil } ) + + self.Fsm[TaskUnit] = Fsm + + return Fsm +end + +--- Remove FiniteStateMachines from @{Task} with key Task@{Unit} +-- @param #TASK self +-- @param Wrapper.Unit#UNIT TaskUnit +-- @return #TASK self +function TASK:RemoveStateMachine( TaskUnit ) + self:F( { TaskUnit, self.Fsm[TaskUnit] ~= nil } ) + + self.Fsm[TaskUnit] = nil + collectgarbage() + self:T( "Garbage Collected, Processes should be finalized now ...") +end + +--- Checks if there is a FiniteStateMachine assigned to Task@{Unit} for @{Task} +-- @param #TASK self +-- @param Wrapper.Unit#UNIT TaskUnit +-- @return #TASK self +function TASK:HasStateMachine( TaskUnit ) + self:F( { TaskUnit, self.Fsm[TaskUnit] ~= nil } ) + + return ( self.Fsm[TaskUnit] ~= nil ) +end + + +--- Gets the Scoring of the task +-- @param #TASK self +-- @return Functional.Scoring#SCORING Scoring +function TASK:GetScoring() + return self.Mission:GetScoring() +end + + +--- Gets the Task Index, which is a combination of the Task type, the Task name. +-- @param #TASK self +-- @return #string The Task ID +function TASK:GetTaskIndex() + + local TaskType = self:GetType() + local TaskName = self:GetName() + + return TaskType .. "." .. TaskName +end + +--- Sets the Name of the Task +-- @param #TASK self +-- @param #string TaskName +function TASK:SetName( TaskName ) + self.TaskName = TaskName +end + +--- Gets the Name of the Task +-- @param #TASK self +-- @return #string The Task Name +function TASK:GetName() + return self.TaskName +end + +--- Sets the Type of the Task +-- @param #TASK self +-- @param #string TaskType +function TASK:SetType( TaskType ) + self.TaskType = TaskType +end + +--- Gets the Type of the Task +-- @param #TASK self +-- @return #string TaskType +function TASK:GetType() + return self.TaskType +end + +--- Sets the ID of the Task +-- @param #TASK self +-- @param #string TaskID +function TASK:SetID( TaskID ) + self.TaskID = TaskID +end + +--- Gets the ID of the Task +-- @param #TASK self +-- @return #string TaskID +function TASK:GetID() + return self.TaskID +end + + +--- Sets a @{Task} to status **Success**. +-- @param #TASK self +function TASK:StateSuccess() + self:SetState( self, "State", "Success" ) + return self +end + +--- Is the @{Task} status **Success**. +-- @param #TASK self +function TASK:IsStateSuccess() + return self:Is( "Success" ) +end + +--- Sets a @{Task} to status **Failed**. +-- @param #TASK self +function TASK:StateFailed() + self:SetState( self, "State", "Failed" ) + return self +end + +--- Is the @{Task} status **Failed**. +-- @param #TASK self +function TASK:IsStateFailed() + return self:Is( "Failed" ) +end + +--- Sets a @{Task} to status **Planned**. +-- @param #TASK self +function TASK:StatePlanned() + self:SetState( self, "State", "Planned" ) + return self +end + +--- Is the @{Task} status **Planned**. +-- @param #TASK self +function TASK:IsStatePlanned() + return self:Is( "Planned" ) +end + +--- Sets a @{Task} to status **Assigned**. +-- @param #TASK self +function TASK:StateAssigned() + self:SetState( self, "State", "Assigned" ) + return self +end + +--- Is the @{Task} status **Assigned**. +-- @param #TASK self +function TASK:IsStateAssigned() + return self:Is( "Assigned" ) +end + +--- Sets a @{Task} to status **Hold**. +-- @param #TASK self +function TASK:StateHold() + self:SetState( self, "State", "Hold" ) + return self +end + +--- Is the @{Task} status **Hold**. +-- @param #TASK self +function TASK:IsStateHold() + return self:Is( "Hold" ) +end + +--- Sets a @{Task} to status **Replanned**. +-- @param #TASK self +function TASK:StateReplanned() + self:SetState( self, "State", "Replanned" ) + return self +end + +--- Is the @{Task} status **Replanned**. +-- @param #TASK self +function TASK:IsStateReplanned() + return self:Is( "Replanned" ) +end + +--- Gets the @{Task} status. +-- @param #TASK self +function TASK:GetStateString() + return self:GetState( self, "State" ) +end + +--- Sets a @{Task} briefing. +-- @param #TASK self +-- @param #string TaskBriefing +-- @return #TASK self +function TASK:SetBriefing( TaskBriefing ) + self.TaskBriefing = TaskBriefing + return self +end + + + + +--- FSM function for a TASK +-- @param #TASK self +-- @param #string Event +-- @param #string From +-- @param #string To +function TASK:onenterAssigned( Event, From, To ) + + self:E("Task Assigned") + + self:MessageToGroups( "Task " .. self:GetName() .. " has been assigned!" ) + self:GetMission():__Start() +end + + +--- FSM function for a TASK +-- @param #TASK self +-- @param #string Event +-- @param #string From +-- @param #string To +function TASK:onenterSuccess( Event, From, To ) + + self:E( "Task Success" ) + + self:MessageToGroups( "Task " .. self:GetName() .. " is successful! Good job!" ) + self:UnAssignFromGroups() + + self:GetMission():__Complete() + +end + + +--- FSM function for a TASK +-- @param #TASK self +-- @param #string Event +-- @param #string From +-- @param #string To +function TASK:onenterAborted( Event, From, To ) + + self:E( "Task Aborted" ) + + self:GetMission():GetCommandCenter():MessageToCoalition( "Task " .. self:GetName() .. " has been aborted! Task may be replanned." ) + + self:UnAssignFromGroups() +end + +--- FSM function for a TASK +-- @param #TASK self +-- @param #string Event +-- @param #string From +-- @param #string To +function TASK:onenterFailed( Event, From, To ) + + self:E( "Task Failed" ) + + self:GetMission():GetCommandCenter():MessageToCoalition( "Task " .. self:GetName() .. " has failed!" ) + + self:UnAssignFromGroups() +end + +--- FSM function for a TASK +-- @param #TASK self +-- @param #string Event +-- @param #string From +-- @param #string To +function TASK:onstatechange( Event, From, To ) + + if self:IsTrace() then + MESSAGE:New( "@ Task " .. self.TaskName .. " : " .. Event .. " changed to state " .. To, 2 ):ToAll() + end + + if self.Scores[To] then + local Scoring = self:GetScoring() + if Scoring then + self:E( { self.Scores[To].ScoreText, self.Scores[To].Score } ) + Scoring:_AddMissionScore( self.Mission, self.Scores[To].ScoreText, self.Scores[To].Score ) + end + end + +end + +do -- Reporting + +--- Create a summary report of the Task. +-- List the Task Name and Status +-- @param #TASK self +-- @return #string +function TASK:ReportSummary() + + local Report = REPORT:New() + + -- List the name of the Task. + local Name = self:GetName() + + -- Determine the status of the Task. + local State = self:GetState() + + Report:Add( "Task " .. Name .. " - State '" .. State ) + + return Report:Text() +end + + +--- Create a detailed report of the Task. +-- List the Task Status, and the Players assigned to the Task. +-- @param #TASK self +-- @return #string +function TASK:ReportDetails() + + local Report = REPORT:New() + + -- List the name of the Task. + local Name = self:GetName() + + -- Determine the status of the Task. + local State = self:GetState() + + + -- Loop each Unit active in the Task, and find Player Names. + local PlayerNames = {} + for PlayerGroupID, PlayerGroup in pairs( self:GetGroups():GetSet() ) do + local Player = PlayerGroup -- Wrapper.Group#GROUP + for PlayerUnitID, PlayerUnit in pairs( PlayerGroup:GetUnits() ) do + local PlayerUnit = PlayerUnit -- Wrapper.Unit#UNIT + if PlayerUnit and PlayerUnit:IsAlive() then + local PlayerName = PlayerUnit:GetPlayerName() + PlayerNames[#PlayerNames+1] = PlayerName + end + end + PlayerNameText = table.concat( PlayerNames, ", " ) + Report:Add( "Task " .. Name .. " - State '" .. State .. "' - Players " .. PlayerNameText ) + end + + return Report:Text() +end + + +end -- Reporting +-- This module contains the DETECTION_MANAGER class and derived classes. +-- +-- === +-- +-- 1) @{Tasking.DetectionManager#DETECTION_MANAGER} class, extends @{Core.Base#BASE} -- ==================================================================== --- The @{DetectionManager#DETECTION_MANAGER} class defines the core functions to report detected objects to groups. +-- The @{Tasking.DetectionManager#DETECTION_MANAGER} class defines the core functions to report detected objects to groups. -- Reportings can be done in several manners, and it is up to the derived classes if DETECTION_MANAGER to model the reporting behaviour. -- -- 1.1) DETECTION_MANAGER constructor: -- ----------------------------------- --- * @{DetectionManager#DETECTION_MANAGER.New}(): Create a new DETECTION_MANAGER instance. +-- * @{Tasking.DetectionManager#DETECTION_MANAGER.New}(): Create a new DETECTION_MANAGER instance. -- -- 1.2) DETECTION_MANAGER reporting: -- --------------------------------- --- Derived DETECTION_MANAGER classes will reports detected units using the method @{DetectionManager#DETECTION_MANAGER.ReportDetected}(). This method implements polymorphic behaviour. +-- Derived DETECTION_MANAGER classes will reports detected units using the method @{Tasking.DetectionManager#DETECTION_MANAGER.ReportDetected}(). This method implements polymorphic behaviour. -- --- The time interval in seconds of the reporting can be changed using the methods @{DetectionManager#DETECTION_MANAGER.SetReportInterval}(). --- To control how long a reporting message is displayed, use @{DetectionManager#DETECTION_MANAGER.SetReportDisplayTime}(). --- Derived classes need to implement the method @{DetectionManager#DETECTION_MANAGER.GetReportDisplayTime}() to use the correct display time for displayed messages during a report. +-- The time interval in seconds of the reporting can be changed using the methods @{Tasking.DetectionManager#DETECTION_MANAGER.SetReportInterval}(). +-- To control how long a reporting message is displayed, use @{Tasking.DetectionManager#DETECTION_MANAGER.SetReportDisplayTime}(). +-- Derived classes need to implement the method @{Tasking.DetectionManager#DETECTION_MANAGER.GetReportDisplayTime}() to use the correct display time for displayed messages during a report. -- --- Reporting can be started and stopped using the methods @{DetectionManager#DETECTION_MANAGER.StartReporting}() and @{DetectionManager#DETECTION_MANAGER.StopReporting}() respectively. --- If an ad-hoc report is requested, use the method @{DetectionManager#DETECTION_MANAGER#ReportNow}(). +-- Reporting can be started and stopped using the methods @{Tasking.DetectionManager#DETECTION_MANAGER.StartReporting}() and @{Tasking.DetectionManager#DETECTION_MANAGER.StopReporting}() respectively. +-- If an ad-hoc report is requested, use the method @{Tasking.DetectionManager#DETECTION_MANAGER#ReportNow}(). -- -- The default reporting interval is every 60 seconds. The reporting messages are displayed 15 seconds. -- -- === -- --- 2) @{DetectionManager#DETECTION_REPORTING} class, extends @{DetectionManager#DETECTION_MANAGER} +-- 2) @{Tasking.DetectionManager#DETECTION_REPORTING} class, extends @{Tasking.DetectionManager#DETECTION_MANAGER} -- ========================================================================================= --- The @{DetectionManager#DETECTION_REPORTING} class implements detected units reporting. Reporting can be controlled using the reporting methods available in the @{DetectionManager#DETECTION_MANAGER} class. +-- The @{Tasking.DetectionManager#DETECTION_REPORTING} class implements detected units reporting. Reporting can be controlled using the reporting methods available in the @{Tasking.DetectionManager#DETECTION_MANAGER} class. -- -- 2.1) DETECTION_REPORTING constructor: -- ------------------------------- --- The @{DetectionManager#DETECTION_REPORTING.New}() method creates a new DETECTION_REPORTING instance. +-- The @{Tasking.DetectionManager#DETECTION_REPORTING.New}() method creates a new DETECTION_REPORTING instance. -- -- === -- @@ -27192,7 +28676,7 @@ do -- DETECTION MANAGER --- DETECTION_MANAGER class. -- @type DETECTION_MANAGER -- @field Set#SET_GROUP SetGroup The groups to which the FAC will report to. - -- @field Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects. + -- @field Functional.Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects. -- @extends Base#BASE DETECTION_MANAGER = { ClassName = "DETECTION_MANAGER", @@ -27203,12 +28687,12 @@ do -- DETECTION MANAGER --- FAC constructor. -- @param #DETECTION_MANAGER self -- @param Set#SET_GROUP SetGroup - -- @param Detection#DETECTION_BASE Detection + -- @param Functional.Detection#DETECTION_BASE Detection -- @return #DETECTION_MANAGER self function DETECTION_MANAGER:New( SetGroup, Detection ) -- Inherits from BASE - local self = BASE:Inherit( self, BASE:New() ) -- Detection#DETECTION_MANAGER + local self = BASE:Inherit( self, BASE:New() ) -- Functional.Detection#DETECTION_MANAGER self.SetGroup = SetGroup self.Detection = Detection @@ -27253,7 +28737,7 @@ do -- DETECTION MANAGER --- Reports the detected items to the @{Set#SET_GROUP}. -- @param #DETECTION_MANAGER self - -- @param Detection#DETECTION_BASE Detection + -- @param Functional.Detection#DETECTION_BASE Detection -- @return #DETECTION_MANAGER self function DETECTION_MANAGER:ReportDetected( Detection ) self:F2() @@ -27276,7 +28760,7 @@ do -- DETECTION MANAGER return self end - --- Report the detected @{Unit#UNIT}s detected within the @{Detection#DETECTION_BASE} object to the @{Set#SET_GROUP}s. + --- Report the detected @{Wrapper.Unit#UNIT}s detected within the @{Functional.Detection#DETECTION_BASE} object to the @{Set#SET_GROUP}s. -- @param #DETECTION_MANAGER self function DETECTION_MANAGER:_FacScheduler( SchedulerName ) self:F2( { SchedulerName } ) @@ -27284,7 +28768,7 @@ do -- DETECTION MANAGER return self:ProcessDetected( self.Detection ) -- self.SetGroup:ForEachGroup( --- --- @param Group#GROUP Group +-- --- @param Wrapper.Group#GROUP Group -- function( Group ) -- if Group:IsAlive() then -- return self:ProcessDetected( self.Detection ) @@ -27303,7 +28787,7 @@ do -- DETECTION_REPORTING --- DETECTION_REPORTING class. -- @type DETECTION_REPORTING -- @field Set#SET_GROUP SetGroup The groups to which the FAC will report to. - -- @field Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects. + -- @field Functional.Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects. -- @extends #DETECTION_MANAGER DETECTION_REPORTING = { ClassName = "DETECTION_REPORTING", @@ -27313,7 +28797,7 @@ do -- DETECTION_REPORTING --- DETECTION_REPORTING constructor. -- @param #DETECTION_REPORTING self -- @param Set#SET_GROUP SetGroup - -- @param Detection#DETECTION_AREAS Detection + -- @param Functional.Detection#DETECTION_AREAS Detection -- @return #DETECTION_REPORTING self function DETECTION_REPORTING:New( SetGroup, Detection ) @@ -27326,7 +28810,7 @@ do -- DETECTION_REPORTING --- Creates a string of the detected items in a @{Detection}. -- @param #DETECTION_MANAGER self - -- @param Set#SET_UNIT DetectedSet The detected Set created by the @{Detection#DETECTION_BASE} object. + -- @param Set#SET_UNIT DetectedSet The detected Set created by the @{Functional.Detection#DETECTION_BASE} object. -- @return #DETECTION_MANAGER self function DETECTION_REPORTING:GetDetectedItemsText( DetectedSet ) self:F2() @@ -27335,7 +28819,7 @@ do -- DETECTION_REPORTING local UnitTypes = {} for DetectedUnitID, DetectedUnitData in pairs( DetectedSet:GetSet() ) do - local DetectedUnit = DetectedUnitData -- Unit#UNIT + local DetectedUnit = DetectedUnitData -- Wrapper.Unit#UNIT if DetectedUnit:IsAlive() then local UnitType = DetectedUnit:GetTypeName() @@ -27358,8 +28842,8 @@ do -- DETECTION_REPORTING --- Reports the detected items to the @{Set#SET_GROUP}. -- @param #DETECTION_REPORTING self - -- @param Group#GROUP Group The @{Group} object to where the report needs to go. - -- @param Detection#DETECTION_AREAS Detection The detection created by the @{Detection#DETECTION_BASE} object. + -- @param Wrapper.Group#GROUP Group The @{Group} object to where the report needs to go. + -- @param Functional.Detection#DETECTION_AREAS Detection The detection created by the @{Functional.Detection#DETECTION_BASE} object. -- @return #boolean Return true if you want the reporting to continue... false will cancel the reporting loop. function DETECTION_REPORTING:ProcessDetected( Group, Detection ) self:F2( Group ) @@ -27367,7 +28851,7 @@ do -- DETECTION_REPORTING self:E( Group ) local DetectedMsg = {} for DetectedAreaID, DetectedAreaData in pairs( Detection:GetDetectedAreas() ) do - local DetectedArea = DetectedAreaData -- Detection#DETECTION_AREAS.DetectedArea + local DetectedArea = DetectedAreaData -- Functional.Detection#DETECTION_AREAS.DetectedArea DetectedMsg[#DetectedMsg+1] = " - Group #" .. DetectedAreaID .. ": " .. self:GetDetectedItemsText( DetectedArea.Set ) end local FACGroup = Detection:GetDetectionGroups() @@ -27383,10 +28867,10 @@ do -- DETECTION_DISPATCHER --- DETECTION_DISPATCHER class. -- @type DETECTION_DISPATCHER -- @field Set#SET_GROUP SetGroup The groups to which the FAC will report to. - -- @field Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects. - -- @field Mission#MISSION Mission - -- @field Group#GROUP CommandCenter - -- @extends DetectionManager#DETECTION_MANAGER + -- @field Functional.Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects. + -- @field Tasking.Mission#MISSION Mission + -- @field Wrapper.Group#GROUP CommandCenter + -- @extends Tasking.DetectionManager#DETECTION_MANAGER DETECTION_DISPATCHER = { ClassName = "DETECTION_DISPATCHER", Mission = nil, @@ -27398,7 +28882,7 @@ do -- DETECTION_DISPATCHER --- DETECTION_DISPATCHER constructor. -- @param #DETECTION_DISPATCHER self -- @param Set#SET_GROUP SetGroup - -- @param Detection#DETECTION_BASE Detection + -- @param Functional.Detection#DETECTION_BASE Detection -- @return #DETECTION_DISPATCHER self function DETECTION_DISPATCHER:New( Mission, CommandCenter, SetGroup, Detection ) @@ -27416,7 +28900,7 @@ do -- DETECTION_DISPATCHER --- Creates a SEAD task when there are targets for it. -- @param #DETECTION_DISPATCHER self - -- @param Detection#DETECTION_AREAS.DetectedArea DetectedArea + -- @param Functional.Detection#DETECTION_AREAS.DetectedArea DetectedArea -- @return Set#SET_UNIT TargetSetUnit: The target set of units. -- @return #nil If there are no targets to be set. function DETECTION_DISPATCHER:EvaluateSEAD( DetectedArea ) @@ -27444,8 +28928,8 @@ do -- DETECTION_DISPATCHER --- Creates a CAS task when there are targets for it. -- @param #DETECTION_DISPATCHER self - -- @param Detection#DETECTION_AREAS.DetectedArea DetectedArea - -- @return Task#TASK_BASE + -- @param Functional.Detection#DETECTION_AREAS.DetectedArea DetectedArea + -- @return Tasking.Task#TASK function DETECTION_DISPATCHER:EvaluateCAS( DetectedArea ) self:F( { DetectedArea.AreaID } ) @@ -27472,8 +28956,8 @@ do -- DETECTION_DISPATCHER --- Creates a BAI task when there are targets for it. -- @param #DETECTION_DISPATCHER self - -- @param Detection#DETECTION_AREAS.DetectedArea DetectedArea - -- @return Task#TASK_BASE + -- @param Functional.Detection#DETECTION_AREAS.DetectedArea DetectedArea + -- @return Tasking.Task#TASK function DETECTION_DISPATCHER:EvaluateBAI( DetectedArea, FriendlyCoalition ) self:F( { DetectedArea.AreaID } ) @@ -27501,14 +28985,15 @@ do -- DETECTION_DISPATCHER --- Evaluates the removal of the Task from the Mission. -- Can only occur when the DetectedArea is Changed AND the state of the Task is "Planned". -- @param #DETECTION_DISPATCHER self - -- @param Mission#MISSION Mission - -- @param Task#TASK_BASE Task - -- @param Detection#DETECTION_AREAS.DetectedArea DetectedArea - -- @return Task#TASK_BASE + -- @param Tasking.Mission#MISSION Mission + -- @param Tasking.Task#TASK Task + -- @param Functional.Detection#DETECTION_AREAS.DetectedArea DetectedArea + -- @return Tasking.Task#TASK function DETECTION_DISPATCHER:EvaluateRemoveTask( Mission, Task, DetectedArea ) if Task then if Task:IsStatePlanned() and DetectedArea.Changed == true then + self:E( "Removing Tasking: " .. Task:GetTaskName() ) Mission:RemoveTaskMenu( Task ) Task = Mission:RemoveTask( Task ) end @@ -27520,7 +29005,7 @@ do -- DETECTION_DISPATCHER --- Assigns tasks in relation to the detected items to the @{Set#SET_GROUP}. -- @param #DETECTION_DISPATCHER self - -- @param Detection#DETECTION_AREAS Detection The detection created by the @{Detection#DETECTION_AREAS} object. + -- @param Functional.Detection#DETECTION_AREAS Detection The detection created by the @{Functional.Detection#DETECTION_AREAS} object. -- @return #boolean Return true if you want the task assigning to continue... false will cancel the loop. function DETECTION_DISPATCHER:ProcessDetected( Detection ) self:F2() @@ -27534,7 +29019,7 @@ do -- DETECTION_DISPATCHER --- First we need to the detected targets. for DetectedAreaID, DetectedAreaData in ipairs( Detection:GetDetectedAreas() ) do - local DetectedArea = DetectedAreaData -- Detection#DETECTION_AREAS.DetectedArea + local DetectedArea = DetectedAreaData -- Functional.Detection#DETECTION_AREAS.DetectedArea local DetectedSet = DetectedArea.Set local DetectedZone = DetectedArea.Zone self:E( { "Targets in DetectedArea", DetectedArea.AreaID, DetectedSet:Count(), tostring( DetectedArea ) } ) @@ -27548,11 +29033,12 @@ do -- DETECTION_DISPATCHER if not SEADTask then local TargetSetUnit = self:EvaluateSEAD( DetectedArea ) -- Returns a SetUnit if there are targets to be SEADed... if TargetSetUnit then - SEADTask = Mission:AddTask( TASK_SEAD:New( Mission, self.SetGroup, "SEAD." .. AreaID, TargetSetUnit , DetectedZone ) ):StatePlanned() + SEADTask = Mission:AddTask( TASK_SEAD:New( Mission, self.SetGroup, "SEAD." .. AreaID, TargetSetUnit , DetectedZone ) ) end end if SEADTask and SEADTask:IsStatePlanned() then - SEADTask:SetPlannedMenu() + self:E( "Planned" ) + --SEADTask:SetPlannedMenu() TaskMsg[#TaskMsg+1] = " - " .. SEADTask:GetStateString() .. " SEAD " .. AreaID .. " - " .. SEADTask.TargetSetUnit:GetUnitTypesText() end @@ -27562,11 +29048,11 @@ do -- DETECTION_DISPATCHER if not CASTask then local TargetSetUnit = self:EvaluateCAS( DetectedArea ) -- Returns a SetUnit if there are targets to be SEADed... if TargetSetUnit then - CASTask = Mission:AddTask( TASK_A2G:New( Mission, self.SetGroup, "CAS." .. AreaID, "CAS", TargetSetUnit , DetectedZone, DetectedArea.NearestFAC ) ):StatePlanned() + CASTask = Mission:AddTask( TASK_A2G:New( Mission, self.SetGroup, "CAS." .. AreaID, "CAS", TargetSetUnit , DetectedZone, DetectedArea.NearestFAC ) ) end end if CASTask and CASTask:IsStatePlanned() then - CASTask:SetPlannedMenu() + --CASTask:SetPlannedMenu() TaskMsg[#TaskMsg+1] = " - " .. CASTask:GetStateString() .. " CAS " .. AreaID .. " - " .. CASTask.TargetSetUnit:GetUnitTypesText() end @@ -27576,11 +29062,11 @@ do -- DETECTION_DISPATCHER if not BAITask then local TargetSetUnit = self:EvaluateBAI( DetectedArea, self.CommandCenter:GetCoalition() ) -- Returns a SetUnit if there are targets to be SEADed... if TargetSetUnit then - BAITask = Mission:AddTask( TASK_A2G:New( Mission, self.SetGroup, "BAI." .. AreaID, "BAI", TargetSetUnit , DetectedZone, DetectedArea.NearestFAC ) ):StatePlanned() + BAITask = Mission:AddTask( TASK_A2G:New( Mission, self.SetGroup, "BAI." .. AreaID, "BAI", TargetSetUnit , DetectedZone, DetectedArea.NearestFAC ) ) end end if BAITask and BAITask:IsStatePlanned() then - BAITask:SetPlannedMenu() + --BAITask:SetPlannedMenu() TaskMsg[#TaskMsg+1] = " - " .. BAITask:GetStateString() .. " BAI " .. AreaID .. " - " .. BAITask.TargetSetUnit:GetUnitTypesText() end @@ -27611,6 +29097,9 @@ do -- DETECTION_DISPATCHER end + -- TODO set menus using the HQ coordinator + Mission:SetMenu() + if #AreaMsg > 0 then for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do if not TaskGroup:GetState( TaskGroup, "Assigned" ) then @@ -27629,2018 +29118,17 @@ do -- DETECTION_DISPATCHER return true end -end--- This module contains the STATEMACHINE class. --- This development is based on a state machine implementation made by Conroy Kyle. --- The state machine can be found here: https://github.com/kyleconroy/lua-state-machine +end--- This module contains the TASK_SEAD classes. -- --- I've taken the development and enhanced it to make the state machine hierarchical... --- It is a fantastic development, this module. --- --- === --- --- 1) @{Workflow#STATEMACHINE} class, extends @{Base#BASE} --- ============================================== --- --- 1.1) Add or remove objects from the STATEMACHINE --- -------------------------------------------- --- @module StateMachine --- @author FlightControl - - ---- STATEMACHINE class --- @type STATEMACHINE -STATEMACHINE = { - ClassName = "STATEMACHINE", -} - ---- Creates a new STATEMACHINE object. --- @param #STATEMACHINE self --- @return #STATEMACHINE -function STATEMACHINE:New( options ) - - -- Inherits from BASE - local self = BASE:Inherit( self, BASE:New() ) - - - --local self = routines.utils.deepCopy( self ) -- Create a new self instance - - assert(options.events) - - --local MT = {} - --setmetatable( self, MT ) - --self.__index = self - - self.options = options - self.current = options.initial or 'none' - self.events = {} - self.subs = {} - self.endstates = {} - - for _, event in ipairs(options.events or {}) do - local name = event.name - self[name] = self[name] or self:_create_transition(name) - self.events[name] = self.events[name] or { map = {} } - self:_add_to_map(self.events[name].map, event) - end - - for name, callback in pairs(options.callbacks or {}) do - self[name] = callback - end - - for name, sub in pairs( options.subs or {} ) do - self:_submap( self.subs, sub, name ) - end - - for name, endstate in pairs( options.endstates or {} ) do - self.endstates[endstate] = endstate - end - - return self -end - -function STATEMACHINE:LoadCallBacks( CallBackTable ) - - for name, callback in pairs( CallBackTable or {} ) do - self[name] = callback - end - -end - -function STATEMACHINE:_submap( subs, sub, name ) - self:E( { sub = sub, name = name } ) - subs[sub.onstateparent] = subs[sub.onstateparent] or {} - subs[sub.onstateparent][sub.oneventparent] = subs[sub.onstateparent][sub.oneventparent] or {} - local Index = #subs[sub.onstateparent][sub.oneventparent] + 1 - subs[sub.onstateparent][sub.oneventparent][Index] = {} - subs[sub.onstateparent][sub.oneventparent][Index].fsm = sub.fsm - subs[sub.onstateparent][sub.oneventparent][Index].event = sub.event - subs[sub.onstateparent][sub.oneventparent][Index].returnevents = sub.returnevents -- these events need to be given to find the correct continue event ... if none given, the processing will stop. - subs[sub.onstateparent][sub.oneventparent][Index].name = name - subs[sub.onstateparent][sub.oneventparent][Index].fsmparent = self -end - - -function STATEMACHINE:_call_handler(handler, params) - if handler then - return handler(unpack(params)) - end -end - -function STATEMACHINE:_create_transition(name) - self:E( { name = name } ) - return function(self, ...) - local can, to = self:can(name) - self:T( { name, can, to } ) - - if can then - local from = self.current - local params = { self, name, from, to, ... } - - if self:_call_handler(self["onbefore" .. name], params) == false - or self:_call_handler(self["onleave" .. from], params) == false then - return false - end - - self.current = to - - local execute = true - - local subtable = self:_gosub( to, name ) - for _, sub in pairs( subtable ) do - self:F( "calling sub: " .. sub.event ) - sub.fsm.fsmparent = self - sub.fsm.returnevents = sub.returnevents - sub.fsm[sub.event]( sub.fsm ) - execute = true - end - - local fsmparent, event = self:_isendstate( to ) - if fsmparent and event then - self:F( { "end state: ", fsmparent, event } ) - self:_call_handler(self["onenter" .. to] or self["on" .. to], params) - self:_call_handler(self["onafter" .. name] or self["on" .. name], params) - self:_call_handler(self["onstatechange"], params) - fsmparent[event]( fsmparent ) - execute = false - end - - if execute then - self:F( { "execute: " .. to, name } ) - self:_call_handler(self["onenter" .. to] or self["on" .. to], params) - self:_call_handler(self["onafter" .. name] or self["on" .. name], params) - self:_call_handler(self["onstatechange"], params) - end - - return true - end - - return false - end -end - -function STATEMACHINE:_gosub( parentstate, parentevent ) - local fsmtable = {} - if self.subs[parentstate] and self.subs[parentstate][parentevent] then - return self.subs[parentstate][parentevent] - else - return {} - end -end - -function STATEMACHINE:_isendstate( state ) - local fsmparent = self.fsmparent - if fsmparent and self.endstates[state] then - self:E( { state = state, endstates = self.endstates, endstate = self.endstates[state] } ) - local returnevent = nil - local fromstate = fsmparent.current - self:E( fromstate ) - self:E( self.returnevents ) - for _, eventname in pairs( self.returnevents ) do - local event = fsmparent.events[eventname] - self:E( event ) - local to = event and event.map[fromstate] or event.map['*'] - if to and to == state then - return fsmparent, eventname - else - self:E( { "could not find parent event name for state", fromstate, to } ) - end - end - end - - return nil -end - -function STATEMACHINE:_add_to_map(map, event) - if type(event.from) == 'string' then - map[event.from] = event.to - else - for _, from in ipairs(event.from) do - map[from] = event.to - end - end -end - -function STATEMACHINE:is(state) - return self.current == state -end - -function STATEMACHINE:can(e) - local event = self.events[e] - local to = event and event.map[self.current] or event.map['*'] - return to ~= nil, to -end - -function STATEMACHINE:cannot(e) - return not self:can(e) -end - -function STATEMACHINE:todot(filename) - local dotfile = io.open(filename,'w') - dotfile:write('digraph {\n') - local transition = function(event,from,to) - dotfile:write(string.format('%s -> %s [label=%s];\n',from,to,event)) - end - for _, event in pairs(self.options.events) do - if type(event.from) == 'table' then - for _, from in ipairs(event.from) do - transition(event.name,from,event.to) - end - else - transition(event.name,event.from,event.to) - end - end - dotfile:write('}\n') - dotfile:close() -end - ---- STATEMACHINE_PROCESS class --- @type STATEMACHINE_PROCESS --- @field Process#PROCESS Process --- @extends StateMachine#STATEMACHINE -STATEMACHINE_PROCESS = { - ClassName = "STATEMACHINE_PROCESS", -} - ---- Creates a new STATEMACHINE_PROCESS object. --- @param #STATEMACHINE_PROCESS self --- @return #STATEMACHINE_PROCESS -function STATEMACHINE_PROCESS:New( Process, options ) - - local FsmProcess = routines.utils.deepCopy( self ) -- Create a new self instance - local Parent = STATEMACHINE:New(options) - - setmetatable( FsmProcess, Parent ) - FsmProcess.__index = FsmProcess - - FsmProcess["onstatechange"] = Process.OnStateChange - FsmProcess.Process = Process - - return FsmProcess -end - -function STATEMACHINE_PROCESS:_call_handler( handler, params ) - if handler then - return handler( self.Process, unpack( params ) ) - end -end - ---- STATEMACHINE_TASK class --- @type STATEMACHINE_TASK --- @field Task#TASK_BASE Task --- @extends StateMachine#STATEMACHINE -STATEMACHINE_TASK = { - ClassName = "STATEMACHINE_TASK", -} - ---- Creates a new STATEMACHINE_TASK object. --- @param #STATEMACHINE_TASK self --- @return #STATEMACHINE_TASK -function STATEMACHINE_TASK:New( Task, TaskUnit, options ) - - local FsmTask = routines.utils.deepCopy( self ) -- Create a new self instance - local Parent = STATEMACHINE:New(options) - - setmetatable( FsmTask, Parent ) - FsmTask.__index = FsmTask - - FsmTask["onstatechange"] = Task.OnStateChange - FsmTask["onAssigned"] = Task.OnAssigned - FsmTask["onSuccess"] = Task.OnSuccess - FsmTask["onFailed"] = Task.OnFailed - - FsmTask.Task = Task - FsmTask.TaskUnit = TaskUnit - - return FsmTask -end - -function STATEMACHINE_TASK:_call_handler( handler, params ) - if handler then - return handler( self.Task, self.TaskUnit, unpack( params ) ) - end -end ---- @module Process - ---- The PROCESS class --- @type PROCESS --- @field Scheduler#SCHEDULER ProcessScheduler --- @field Unit#UNIT ProcessUnit --- @field Group#GROUP ProcessGroup --- @field Menu#MENU_GROUP MissionMenu --- @field Task#TASK_BASE Task --- @field StateMachine#STATEMACHINE_TASK Fsm --- @field #string ProcessName --- @extends Base#BASE -PROCESS = { - ClassName = "TASK", - ProcessScheduler = nil, - NextEvent = nil, - Scores = {}, -} - ---- Instantiates a new TASK Base. Should never be used. Interface Class. --- @param #PROCESS self --- @param #string ProcessName --- @param Task#TASK_BASE Task --- @param Unit#UNIT ProcessUnit --- @return #PROCESS self -function PROCESS:New( ProcessName, Task, ProcessUnit ) - local self = BASE:Inherit( self, BASE:New() ) - self:F() - - self.ProcessUnit = ProcessUnit - self.ProcessGroup = ProcessUnit:GetGroup() - self.MissionMenu = Task.Mission:GetMissionMenu( self.ProcessGroup ) - self.Task = Task - self.ProcessName = ProcessName - - self.ProcessScheduler = SCHEDULER:New() - - return self -end - ---- @param #PROCESS self -function PROCESS:NextEvent( NextEvent, ... ) - self:F(self.ProcessName) - self.ProcessScheduler:Schedule( self.Fsm, NextEvent, arg, 1 ) -- This schedules the next event, but only if scheduling is activated. -end - ---- @param #PROCESS self -function PROCESS:StopEvents() - self:F( { "Stop Process ", self.ProcessName } ) - self.ProcessScheduler:Stop() -end - ---- Adds a score for the PROCESS to be achieved. --- @param #PROCESS self --- @param #string ProcessStatus is the status of the PROCESS when the score needs to be given. --- @param #string ScoreText is a text describing the score that is given according the status. --- @param #number Score is a number providing the score of the status. --- @return #PROCESS self -function PROCESS:AddScore( ProcessStatus, ScoreText, Score ) - self:F2( { ProcessStatus, ScoreText, Score } ) - - self.Scores[ProcessStatus] = self.Scores[ProcessStatus] or {} - self.Scores[ProcessStatus].ScoreText = ScoreText - self.Scores[ProcessStatus].Score = Score - return self -end - ---- StateMachine callback function for a PROCESS --- @param #PROCESS self --- @param StateMachine#STATEMACHINE_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To -function PROCESS:OnStateChange( Fsm, Event, From, To ) - self:E( { self.ProcessName, Event, From, To, self.ProcessUnit.UnitName } ) - - if self:IsTrace() then - MESSAGE:New( "Process " .. self.ProcessName .. " : " .. Event .. " changed to state " .. To, 15 ):ToAll() - end - - if self.Scores[To] then - - local Scoring = self.Task:GetScoring() - if Scoring then - Scoring:_AddMissionTaskScore( self.Task.Mission, self.ProcessUnit, self.Scores[To].ScoreText, self.Scores[To].Score ) - end - end -end - - ---- This module contains the PROCESS_ASSIGN classes. --- --- === --- --- 1) @{Task_Assign#TASK_ASSIGN_ACCEPT} class, extends @{Task#TASK_BASE} --- ===================================================================== --- The @{Task_Assign#TASK_ASSIGN_ACCEPT} class accepts by default a task for a player. No player intervention is allowed to reject the task. --- --- 2) @{Task_Assign#TASK_ASSIGN_MENU_ACCEPT} class, extends @{Task#TASK_BASE} --- ========================================================================== --- The @{Task_Assign#TASK_ASSIGN_MENU_ACCEPT} class accepts a task when the player accepts the task through an added menu option. --- This assignment type is useful to conditionally allow the player to choose whether or not he would accept the task. --- The assignment type also allows to reject the task. --- --- --- --- --- --- --- @module Task_Assign --- - - -do -- PROCESS_ASSIGN_ACCEPT - - --- PROCESS_ASSIGN_ACCEPT class - -- @type PROCESS_ASSIGN_ACCEPT - -- @field Task#TASK_BASE Task - -- @field Unit#UNIT ProcessUnit - -- @field Zone#ZONE_BASE TargetZone - -- @extends Task2#TASK2 - PROCESS_ASSIGN_ACCEPT = { - ClassName = "PROCESS_ASSIGN_ACCEPT", - } - - - --- Creates a new task assignment state machine. The process will accept the task by default, no player intervention accepted. - -- @param #PROCESS_ASSIGN_ACCEPT self - -- @param Task#TASK Task - -- @param Unit#UNIT Unit - -- @return #PROCESS_ASSIGN_ACCEPT self - function PROCESS_ASSIGN_ACCEPT:New( Task, ProcessUnit, TaskBriefing ) - - -- Inherits from BASE - local self = BASE:Inherit( self, PROCESS:New( "ASSIGN_ACCEPT", Task, ProcessUnit ) ) -- #PROCESS_ASSIGN_ACCEPT - - self.TaskBriefing = TaskBriefing - - self.Fsm = STATEMACHINE_PROCESS:New( self, { - initial = 'UnAssigned', - events = { - { name = 'Start', from = 'UnAssigned', to = 'Assigned' }, - { name = 'Fail', from = 'UnAssigned', to = 'Failed' }, - }, - callbacks = { - onAssign = self.OnAssign, - }, - endstates = { - 'Assigned', 'Failed' - }, - } ) - - return self - end - - --- StateMachine callback function for a TASK2 - -- @param #PROCESS_ASSIGN_ACCEPT self - -- @param StateMachine#STATEMACHINE_PROCESS Fsm - -- @param #string Event - -- @param #string From - -- @param #string To - function PROCESS_ASSIGN_ACCEPT:OnAssigned( Fsm, Event, From, To ) - self:E( { Event, From, To, self.ProcessUnit.UnitName} ) - - end - -end - - -do -- PROCESS_ASSIGN_MENU_ACCEPT - - --- PROCESS_ASSIGN_MENU_ACCEPT class - -- @type PROCESS_ASSIGN_MENU_ACCEPT - -- @field Task#TASK_BASE Task - -- @field Unit#UNIT ProcessUnit - -- @field Zone#ZONE_BASE TargetZone - -- @extends Task2#TASK2 - PROCESS_ASSIGN_MENU_ACCEPT = { - ClassName = "PROCESS_ASSIGN_MENU_ACCEPT", - } - - - --- Creates a new task assignment state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator. - -- @param #PROCESS_ASSIGN_MENU_ACCEPT self - -- @param Task#TASK Task - -- @param Unit#UNIT Unit - -- @return #PROCESS_ASSIGN_MENU_ACCEPT self - function PROCESS_ASSIGN_MENU_ACCEPT:New( Task, ProcessUnit, TaskBriefing ) - - -- Inherits from BASE - local self = BASE:Inherit( self, PROCESS:New( "ASSIGN_MENU_ACCEPT", Task, ProcessUnit ) ) -- #PROCESS_ASSIGN_MENU_ACCEPT - - self.TaskBriefing = TaskBriefing - - self.Fsm = STATEMACHINE_PROCESS:New( self, { - initial = 'UnAssigned', - events = { - { name = 'Start', from = 'UnAssigned', to = 'AwaitAccept' }, - { name = 'Assign', from = 'AwaitAccept', to = 'Assigned' }, - { name = 'Reject', from = 'AwaitAccept', to = 'Rejected' }, - { name = 'Fail', from = 'AwaitAccept', to = 'Rejected' }, - }, - callbacks = { - onStart = self.OnStart, - onAssign = self.OnAssign, - onReject = self.OnReject, - }, - endstates = { - 'Assigned', 'Rejected' - }, - } ) - - return self - end - - --- StateMachine callback function for a TASK2 - -- @param #PROCESS_ASSIGN_MENU_ACCEPT self - -- @param StateMachine#STATEMACHINE_TASK Fsm - -- @param #string Event - -- @param #string From - -- @param #string To - function PROCESS_ASSIGN_MENU_ACCEPT:OnStart( Fsm, Event, From, To ) - self:E( { Event, From, To, self.ProcessUnit.UnitName} ) - - MESSAGE:New( self.TaskBriefing .. "\nAccess the radio menu to accept the task. You have 30 seconds or the assignment will be cancelled.", 30, "Assignment" ):ToGroup( self.ProcessUnit:GetGroup() ) - self.MenuText = self.Task.TaskName - - local ProcessGroup = self.ProcessUnit:GetGroup() - self.Menu = MENU_GROUP:New( ProcessGroup, "Task " .. self.MenuText .. " acceptance" ) - self.MenuAcceptTask = MENU_GROUP_COMMAND:New( ProcessGroup, "Accept task " .. self.MenuText, self.Menu, self.MenuAssign, self ) - self.MenuRejectTask = MENU_GROUP_COMMAND:New( ProcessGroup, "Reject task " .. self.MenuText, self.Menu, self.MenuReject, self ) - end - - --- Menu function. - -- @param #PROCESS_ASSIGN_MENU_ACCEPT self - function PROCESS_ASSIGN_MENU_ACCEPT:MenuAssign() - self:E( ) - - self:NextEvent( self.Fsm.Assign ) - end - - --- Menu function. - -- @param #PROCESS_ASSIGN_MENU_ACCEPT self - function PROCESS_ASSIGN_MENU_ACCEPT:MenuReject() - self:E( ) - - self:NextEvent( self.Fsm.Reject ) - end - - --- StateMachine callback function for a TASK2 - -- @param #PROCESS_ASSIGN_MENU_ACCEPT self - -- @param StateMachine#STATEMACHINE_PROCESS Fsm - -- @param #string Event - -- @param #string From - -- @param #string To - function PROCESS_ASSIGN_MENU_ACCEPT:OnAssign( Fsm, Event, From, To ) - self:E( { Event, From, To, self.ProcessUnit.UnitName} ) - - self.Menu:Remove() - end - - --- StateMachine callback function for a TASK2 - -- @param #PROCESS_ASSIGN_MENU_ACCEPT self - -- @param StateMachine#STATEMACHINE_PROCESS Fsm - -- @param #string Event - -- @param #string From - -- @param #string To - function PROCESS_ASSIGN_MENU_ACCEPT:OnReject( Fsm, Event, From, To ) - self:E( { Event, From, To, self.ProcessUnit.UnitName} ) - - self.Menu:Remove() - self.Task:UnAssignFromUnit( self.ProcessUnit ) - self.ProcessUnit:Destroy() - end -end ---- @module Task_Route - ---- PROCESS_ROUTE class --- @type PROCESS_ROUTE --- @field Task#TASK TASK --- @field Unit#UNIT ProcessUnit --- @field Zone#ZONE_BASE TargetZone --- @extends Task2#TASK2 -PROCESS_ROUTE = { - ClassName = "PROCESS_ROUTE", -} - - ---- Creates a new routing state machine. The task will route a CLIENT to a ZONE until the CLIENT is within that ZONE. --- @param #PROCESS_ROUTE self --- @param Task#TASK Task --- @param Unit#UNIT Unit --- @return #PROCESS_ROUTE self -function PROCESS_ROUTE:New( Task, ProcessUnit, TargetZone ) - - -- Inherits from BASE - local self = BASE:Inherit( self, PROCESS:New( "ROUTE", Task, ProcessUnit ) ) -- #PROCESS_ROUTE - - self.TargetZone = TargetZone - self.DisplayInterval = 30 - self.DisplayCount = 30 - self.DisplayMessage = true - self.DisplayTime = 10 -- 10 seconds is the default - self.DisplayCategory = "HQ" -- Route is the default display category - - self.Fsm = STATEMACHINE_PROCESS:New( self, { - initial = 'UnArrived', - events = { - { name = 'Start', from = 'UnArrived', to = 'UnArrived' }, - { name = 'Fail', from = 'UnArrived', to = 'Failed' }, - }, - callbacks = { - onleaveUnArrived = self.OnLeaveUnArrived, - onFail = self.OnFail, - }, - endstates = { - 'Arrived', 'Failed' - }, - } ) - - return self -end - ---- Task Events - ---- StateMachine callback function for a TASK2 --- @param #PROCESS_ROUTE self --- @param StateMachine#STATEMACHINE_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To -function PROCESS_ROUTE:OnLeaveUnArrived( Fsm, Event, From, To ) - - if self.ProcessUnit:IsAlive() then - local IsInZone = self.ProcessUnit:IsInZone( self.TargetZone ) - - if self.DisplayCount >= self.DisplayInterval then - if not IsInZone then - local ZoneVec2 = self.TargetZone:GetVec2() - local ZonePointVec2 = POINT_VEC2:New( ZoneVec2.x, ZoneVec2.y ) - local TaskUnitVec2 = self.ProcessUnit:GetVec2() - local TaskUnitPointVec2 = POINT_VEC2:New( TaskUnitVec2.x, TaskUnitVec2.y ) - local RouteText = self.ProcessUnit:GetCallsign() .. ": Route to " .. TaskUnitPointVec2:GetBRText( ZonePointVec2 ) .. " km to target." - MESSAGE:New( RouteText, self.DisplayTime, self.DisplayCategory ):ToGroup( self.ProcessUnit:GetGroup() ) - end - self.DisplayCount = 1 - else - self.DisplayCount = self.DisplayCount + 1 - end - - --if not IsInZone then - self:NextEvent( Fsm.Start ) - --end - - return IsInZone -- if false, then the event will not be executed... - end - - return false - -end - ---- @module Process_Smoke - -do -- PROCESS_SMOKE_TARGETS - - --- PROCESS_SMOKE_TARGETS class - -- @type PROCESS_SMOKE_TARGETS - -- @field Task#TASK_BASE Task - -- @field Unit#UNIT ProcessUnit - -- @field Set#SET_UNIT TargetSetUnit - -- @field Zone#ZONE_BASE TargetZone - -- @extends Task2#TASK2 - PROCESS_SMOKE_TARGETS = { - ClassName = "PROCESS_SMOKE_TARGETS", - } - - - --- Creates a new task assignment state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator. - -- @param #PROCESS_SMOKE_TARGETS self - -- @param Task#TASK Task - -- @param Unit#UNIT Unit - -- @return #PROCESS_SMOKE_TARGETS self - function PROCESS_SMOKE_TARGETS:New( Task, ProcessUnit, TargetSetUnit, TargetZone ) - - -- Inherits from BASE - local self = BASE:Inherit( self, PROCESS:New( "ASSIGN_MENU_ACCEPT", Task, ProcessUnit ) ) -- #PROCESS_SMOKE_TARGETS - - self.TargetSetUnit = TargetSetUnit - self.TargetZone = TargetZone - - self.Fsm = STATEMACHINE_PROCESS:New( self, { - initial = 'None', - events = { - { name = 'Start', from = 'None', to = 'AwaitSmoke' }, - { name = 'Next', from = 'AwaitSmoke', to = 'Smoking' }, - { name = 'Next', from = 'Smoking', to = 'AwaitSmoke' }, - { name = 'Fail', from = 'Smoking', to = 'Failed' }, - { name = 'Fail', from = 'AwaitSmoke', to = 'Failed' }, - { name = 'Fail', from = 'None', to = 'Failed' }, - }, - callbacks = { - onStart = self.OnStart, - onNext = self.OnNext, - onSmoking = self.OnSmoking, - }, - endstates = { - }, - } ) - - return self - end - - --- StateMachine callback function for a TASK2 - -- @param #PROCESS_SMOKE_TARGETS self - -- @param StateMachine#STATEMACHINE_TASK Fsm - -- @param #string Event - -- @param #string From - -- @param #string To - function PROCESS_SMOKE_TARGETS:OnStart( Fsm, Event, From, To ) - self:E( { Event, From, To, self.ProcessUnit.UnitName} ) - - self:E("Set smoke menu") - - local ProcessGroup = self.ProcessUnit:GetGroup() - local MissionMenu = self.Task.Mission:GetMissionMenu( ProcessGroup ) - - local function MenuSmoke( MenuParam ) - self:E( MenuParam ) - local self = MenuParam.self - local SmokeColor = MenuParam.SmokeColor - self.SmokeColor = SmokeColor - self:NextEvent( self.Fsm.Next ) - end - - self.Menu = MENU_GROUP:New( ProcessGroup, "Target acquisition", MissionMenu ) - self.MenuSmokeBlue = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop blue smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Blue } ) - self.MenuSmokeGreen = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop green smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Green } ) - self.MenuSmokeOrange = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop Orange smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Orange } ) - self.MenuSmokeRed = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop Red smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Red } ) - self.MenuSmokeWhite = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop White smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.White } ) - end - - --- StateMachine callback function for a TASK2 - -- @param #PROCESS_SMOKE_TARGETS self - -- @param StateMachine#STATEMACHINE_PROCESS Fsm - -- @param #string Event - -- @param #string From - -- @param #string To - function PROCESS_SMOKE_TARGETS:OnSmoking( Fsm, Event, From, To ) - self:E( { Event, From, To, self.ProcessUnit.UnitName} ) - - self.TargetSetUnit:ForEachUnit( - --- @param Unit#UNIT SmokeUnit - function( SmokeUnit ) - if math.random( 1, ( 100 * self.TargetSetUnit:Count() ) / 4 ) <= 100 then - SCHEDULER:New( self, - function() - if SmokeUnit:IsAlive() then - SmokeUnit:Smoke( self.SmokeColor, 150 ) - end - end, {}, math.random( 10, 60 ) - ) - end - end - ) - - end - -end--- @module Process_Destroy - ---- PROCESS_DESTROY class --- @type PROCESS_DESTROY --- @field Unit#UNIT ProcessUnit --- @field Set#SET_UNIT TargetSetUnit --- @extends Process#PROCESS -PROCESS_DESTROY = { - ClassName = "PROCESS_DESTROY", - Fsm = {}, - TargetSetUnit = nil, -} - - ---- Creates a new DESTROY process. --- @param #PROCESS_DESTROY self --- @param Task#TASK Task --- @param Unit#UNIT ProcessUnit --- @param Set#SET_UNIT TargetSetUnit --- @return #PROCESS_DESTROY self -function PROCESS_DESTROY:New( Task, ProcessName, ProcessUnit, TargetSetUnit ) - - -- Inherits from BASE - local self = BASE:Inherit( self, PROCESS:New( ProcessName, Task, ProcessUnit ) ) -- #PROCESS_DESTROY - - self.TargetSetUnit = TargetSetUnit - - self.DisplayInterval = 30 - self.DisplayCount = 30 - self.DisplayMessage = true - self.DisplayTime = 10 -- 10 seconds is the default - self.DisplayCategory = "HQ" -- Targets is the default display category - - self.Fsm = STATEMACHINE_PROCESS:New( self, { - initial = 'Assigned', - events = { - { name = 'Start', from = 'Assigned', to = 'Waiting' }, - { name = 'Start', from = 'Waiting', to = 'Waiting' }, - { name = 'HitTarget', from = 'Waiting', to = 'Destroy' }, - { name = 'MoreTargets', from = 'Destroy', to = 'Waiting' }, - { name = 'Destroyed', from = 'Destroy', to = 'Success' }, - { name = 'Fail', from = 'Assigned', to = 'Failed' }, - { name = 'Fail', from = 'Waiting', to = 'Failed' }, - { name = 'Fail', from = 'Destroy', to = 'Failed' }, - }, - callbacks = { - onStart = self.OnStart, - onWaiting = self.OnWaiting, - onHitTarget = self.OnHitTarget, - onMoreTargets = self.OnMoreTargets, - onDestroyed = self.OnDestroyed, - onKilled = self.OnKilled, - }, - endstates = { 'Success', 'Failed' } - } ) - - - _EVENTDISPATCHER:OnDead( self.EventDead, self ) - - return self -end - ---- Process Events - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_DESTROY self --- @param StateMachine#STATEMACHINE_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To -function PROCESS_DESTROY:OnStart( Fsm, Event, From, To ) - - self:NextEvent( Fsm.Start ) -end - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_DESTROY self --- @param StateMachine#STATEMACHINE_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To -function PROCESS_DESTROY:OnWaiting( Fsm, Event, From, To ) - - local TaskGroup = self.ProcessUnit:GetGroup() - if self.DisplayCount >= self.DisplayInterval then - MESSAGE:New( "Your group with assigned " .. self.Task:GetName() .. " task has " .. self.TargetSetUnit:GetUnitTypesText() .. " targets left to be destroyed.", 5, "HQ" ):ToGroup( TaskGroup ) - self.DisplayCount = 1 - else - self.DisplayCount = self.DisplayCount + 1 - end - - return true -- Process always the event. - -end - - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_DESTROY self --- @param StateMachine#STATEMACHINE_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To --- @param Event#EVENTDATA Event -function PROCESS_DESTROY:OnHitTarget( Fsm, Event, From, To, Event ) - - - self.TargetSetUnit:Flush() - - if self.TargetSetUnit:FindUnit( Event.IniUnitName ) then - self.TargetSetUnit:RemoveUnitsByName( Event.IniUnitName ) - local TaskGroup = self.ProcessUnit:GetGroup() - MESSAGE:New( "You hit a target. Your group with assigned " .. self.Task:GetName() .. " task has " .. self.TargetSetUnit:Count() .. " targets ( " .. self.TargetSetUnit:GetUnitTypesText() .. " ) left to be destroyed.", 15, "HQ" ):ToGroup( TaskGroup ) - end - - - if self.TargetSetUnit:Count() > 0 then - self:NextEvent( Fsm.MoreTargets ) - else - self:NextEvent( Fsm.Destroyed ) - end -end - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_DESTROY self --- @param StateMachine#STATEMACHINE_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To -function PROCESS_DESTROY:OnMoreTargets( Fsm, Event, From, To ) - - -end - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_DESTROY self --- @param StateMachine#STATEMACHINE_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To --- @param Event#EVENTDATA DCSEvent -function PROCESS_DESTROY:OnKilled( Fsm, Event, From, To ) - - self:NextEvent( Fsm.Restart ) - -end - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_DESTROY self --- @param StateMachine#STATEMACHINE_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To -function PROCESS_DESTROY:OnRestart( Fsm, Event, From, To ) - - self:NextEvent( Fsm.Menu ) - -end - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_DESTROY self --- @param StateMachine#STATEMACHINE_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To -function PROCESS_DESTROY:OnDestroyed( Fsm, Event, From, To ) - -end - ---- DCS Events - ---- @param #PROCESS_DESTROY self --- @param Event#EVENTDATA Event -function PROCESS_DESTROY:EventDead( Event ) - - if Event.IniDCSUnit then - self:NextEvent( self.Fsm.HitTarget, Event ) - end -end - - ---- @module Process_JTAC - ---- PROCESS_JTAC class --- @type PROCESS_JTAC --- @field Unit#UNIT ProcessUnit --- @field Set#SET_UNIT TargetSetUnit --- @extends Process#PROCESS -PROCESS_JTAC = { - ClassName = "PROCESS_JTAC", - Fsm = {}, - TargetSetUnit = nil, -} - - ---- Creates a new DESTROY process. --- @param #PROCESS_JTAC self --- @param Task#TASK Task --- @param Unit#UNIT ProcessUnit --- @param Set#SET_UNIT TargetSetUnit --- @param Unit#UNIT FACUnit --- @return #PROCESS_JTAC self -function PROCESS_JTAC:New( Task, ProcessUnit, TargetSetUnit, FACUnit ) - - -- Inherits from BASE - local self = BASE:Inherit( self, PROCESS:New( "JTAC", Task, ProcessUnit ) ) -- #PROCESS_JTAC - - self.TargetSetUnit = TargetSetUnit - self.FACUnit = FACUnit - - self.DisplayInterval = 60 - self.DisplayCount = 30 - self.DisplayMessage = true - self.DisplayTime = 10 -- 10 seconds is the default - self.DisplayCategory = "HQ" -- Targets is the default display category - - - self.Fsm = STATEMACHINE_PROCESS:New( self, { - initial = 'Assigned', - events = { - { name = 'Start', from = 'Assigned', to = 'CreatedMenu' }, - { name = 'JTACMenuUpdate', from = 'CreatedMenu', to = 'AwaitingMenu' }, - { name = 'JTACMenuAwait', from = 'AwaitingMenu', to = 'AwaitingMenu' }, - { name = 'JTACMenuSpot', from = 'AwaitingMenu', to = 'AwaitingMenu' }, - { name = 'JTACMenuCancel', from = 'AwaitingMenu', to = 'AwaitingMenu' }, - { name = 'JTACStatus', from = 'AwaitingMenu', to = 'AwaitingMenu' }, - { name = 'Fail', from = 'AwaitingMenu', to = 'Failed' }, - { name = 'Fail', from = 'CreatedMenu', to = 'Failed' }, - }, - callbacks = { - onStart = self.OnStart, - onJTACMenuUpdate = self.OnJTACMenuUpdate, - onJTACMenuAwait = self.OnJTACMenuAwait, - onJTACMenuSpot = self.OnJTACMenuSpot, - onJTACMenuCancel = self.OnJTACMenuCancel, - }, - endstates = { 'Failed' } - } ) - - - _EVENTDISPATCHER:OnDead( self.EventDead, self ) - - return self -end - ---- Process Events - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_JTAC self --- @param StateMachine#STATEMACHINE_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To -function PROCESS_JTAC:OnStart( Fsm, Event, From, To ) - - self:NextEvent( Fsm.JTACMenuUpdate ) -end - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_JTAC self --- @param StateMachine#STATEMACHINE_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To -function PROCESS_JTAC:OnJTACMenuUpdate( Fsm, Event, From, To ) - - local function JTACMenuSpot( MenuParam ) - self:E( MenuParam.TargetUnit.UnitName ) - local self = MenuParam.self - local TargetUnit = MenuParam.TargetUnit - - self:NextEvent( self.Fsm.JTACMenuSpot, TargetUnit ) - end - - local function JTACMenuCancel( MenuParam ) - self:E( MenuParam ) - local self = MenuParam.self - local TargetUnit = MenuParam.TargetUnit - - self:NextEvent( self.Fsm.JTACMenuCancel, TargetUnit ) - end - - - -- Loop each unit in the target set, and determine the threat levels map table. - local UnitThreatLevels = self.TargetSetUnit:GetUnitThreatLevels() - - self:E( {"UnitThreadLevels", UnitThreatLevels } ) - - local JTACMenu = self.ProcessGroup:GetState( self.ProcessGroup, "JTACMenu" ) - - if not JTACMenu then - JTACMenu = MENU_GROUP:New( self.ProcessGroup, "JTAC", self.MissionMenu ) - for ThreatLevel, ThreatLevelTable in pairs( UnitThreatLevels ) do - local JTACMenuThreatLevel = MENU_GROUP:New( self.ProcessGroup, ThreatLevelTable.UnitThreatLevelText, JTACMenu ) - for ThreatUnitName, ThreatUnit in pairs( ThreatLevelTable.Units ) do - local JTACMenuUnit = MENU_GROUP:New( self.ProcessGroup, ThreatUnit:GetTypeName(), JTACMenuThreatLevel ) - MENU_GROUP_COMMAND:New( self.ProcessGroup, "Lase Target", JTACMenuUnit, JTACMenuSpot, { self = self, TargetUnit = ThreatUnit } ) - MENU_GROUP_COMMAND:New( self.ProcessGroup, "Cancel Target", JTACMenuUnit, JTACMenuCancel, { self = self, TargetUnit = ThreatUnit } ) - end - end - end - -end - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_JTAC self --- @param StateMachine#STATEMACHINE_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To -function PROCESS_JTAC:OnJTACMenuAwait( Fsm, Event, From, To ) - - if self.DisplayCount >= self.DisplayInterval then - - local TaskJTAC = self.Task -- Task#TASK_JTAC - TaskJTAC.Spots = TaskJTAC.Spots or {} - for TargetUnitName, SpotData in pairs( TaskJTAC.Spots) do - local TargetUnit = UNIT:FindByName( TargetUnitName ) - self.FACUnit:MessageToGroup( "Lasing " .. TargetUnit:GetTypeName() .. " with laser code " .. SpotData:getCode(), 15, self.ProcessGroup ) - end - self.DisplayCount = 1 - else - self.DisplayCount = self.DisplayCount + 1 - end - - self:NextEvent( Fsm.JTACMenuAwait ) -end - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_JTAC self --- @param StateMachine#STATEMACHINE_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To --- @param Unit#UNIT TargetUnit -function PROCESS_JTAC:OnJTACMenuSpot( Fsm, Event, From, To, TargetUnit ) - - local TargetUnitName = TargetUnit:GetName() - - local TaskJTAC = self.Task -- Task#TASK_JTAC - - TaskJTAC.Spots = TaskJTAC.Spots or {} - TaskJTAC.Spots[TargetUnitName] = TaskJTAC.Spots[TargetUnitName] or {} - - local DCSFACObject = self.FACUnit:GetDCSObject() - local TargetVec3 = TargetUnit:GetVec3() - - TaskJTAC.Spots[TargetUnitName] = Spot.createInfraRed( self.FACUnit:GetDCSObject(), { x = 0, y = 1, z = 0 }, TargetUnit:GetVec3(), math.random( 1000, 9999 ) ) - - local SpotData = TaskJTAC.Spots[TargetUnitName] - self.FACUnit:MessageToGroup( "Lasing " .. TargetUnit:GetTypeName() .. " with laser code " .. SpotData:getCode(), 15, self.ProcessGroup ) - - self:NextEvent( Fsm.JTACMenuAwait ) -end - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_JTAC self --- @param StateMachine#STATEMACHINE_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To --- @param Unit#UNIT TargetUnit -function PROCESS_JTAC:OnJTACMenuCancel( Fsm, Event, From, To, TargetUnit ) - - local TargetUnitName = TargetUnit:GetName() - - local TaskJTAC = self.Task -- Task#TASK_JTAC - - TaskJTAC.Spots = TaskJTAC.Spots or {} - if TaskJTAC.Spots[TargetUnitName] then - TaskJTAC.Spots[TargetUnitName]:destroy() -- destroys the spot - TaskJTAC.Spots[TargetUnitName] = nil - end - - self.FACUnit:MessageToGroup( "Stopped lasing " .. TargetUnit:GetTypeName(), 15, self.ProcessGroup ) - - self:NextEvent( Fsm.JTACMenuAwait ) -end - - ---- This module contains the TASK_BASE class. --- --- 1) @{#TASK_BASE} class, extends @{Base#BASE} --- ============================================ --- 1.1) The @{#TASK_BASE} class implements the methods for task orchestration within MOOSE. --- ---------------------------------------------------------------------------------------- --- The class provides a couple of methods to: --- --- * @{#TASK_BASE.AssignToGroup}():Assign a task to a group (of players). --- * @{#TASK_BASE.AddProcess}():Add a @{Process} to a task. --- * @{#TASK_BASE.RemoveProcesses}():Remove a running @{Process} from a running task. --- * @{#TASK_BASE.AddStateMachine}():Add a @{StateMachine} to a task. --- * @{#TASK_BASE.RemoveStateMachines}():Remove @{StateMachine}s from a task. --- * @{#TASK_BASE.HasStateMachine}():Enquire if the task has a @{StateMachine} --- * @{#TASK_BASE.AssignToUnit}(): Assign a task to a unit. (Needs to be implemented in the derived classes from @{#TASK_BASE}. --- * @{#TASK_BASE.UnAssignFromUnit}(): Unassign the task from a unit. --- --- 1.2) Set and enquire task status (beyond the task state machine processing). --- ---------------------------------------------------------------------------- --- A task needs to implement as a minimum the following task states: --- --- * **Success**: Expresses the successful execution and finalization of the task. --- * **Failed**: Expresses the failure of a task. --- * **Planned**: Expresses that the task is created, but not yet in execution and is not assigned yet. --- * **Assigned**: Expresses that the task is assigned to a Group of players, and that the task is in execution mode. --- --- A task may also implement the following task states: --- --- * **Rejected**: Expresses that the task is rejected by a player, who was requested to accept the task. --- * **Cancelled**: Expresses that the task is cancelled by HQ or through a logical situation where a cancellation of the task is required. --- --- A task can implement more statusses than the ones outlined above. Please consult the documentation of the specific tasks to understand the different status modelled. --- --- The status of tasks can be set by the methods **State** followed by the task status. An example is `StateAssigned()`. --- The status of tasks can be enquired by the methods **IsState** followed by the task status name. An example is `if IsStateAssigned() then`. --- --- 1.3) Add scoring when reaching a certain task status: --- ----------------------------------------------------- --- Upon reaching a certain task status in a task, additional scoring can be given. If the Mission has a scoring system attached, the scores will be added to the mission scoring. --- Use the method @{#TASK_BASE.AddScore}() to add scores when a status is reached. --- --- 1.4) Task briefing: --- ------------------- --- A task briefing can be given that is shown to the player when he is assigned to the task. --- --- === --- --- ### Authors: FlightControl - Design and Programming --- --- @module Task - ---- The TASK_BASE class --- @type TASK_BASE --- @field Scheduler#SCHEDULER TaskScheduler --- @field Mission#MISSION Mission --- @field StateMachine#STATEMACHINE Fsm --- @field Set#SET_GROUP SetGroup The Set of Groups assigned to the Task --- @extends Base#BASE -TASK_BASE = { - ClassName = "TASK_BASE", - TaskScheduler = nil, - Processes = {}, - Players = nil, - Scores = {}, - Menu = {}, - SetGroup = nil, -} - - ---- Instantiates a new TASK_BASE. Should never be used. Interface Class. --- @param #TASK_BASE self --- @param Mission#MISSION The mission wherein the Task is registered. --- @param Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. --- @param #string TaskName The name of the Task --- @param #string TaskType The type of the Task --- @param #string TaskCategory The category of the Task (A2G, A2A, Transport, ... ) --- @return #TASK_BASE self -function TASK_BASE:New( Mission, SetGroup, TaskName, TaskType, TaskCategory ) - - local self = BASE:Inherit( self, BASE:New() ) - self:E( "New TASK " .. TaskName ) - - self.Processes = {} - self.Fsm = {} - - self.Mission = Mission - self.SetGroup = SetGroup - - self:SetCategory( TaskCategory ) - self:SetType( TaskType ) - self:SetName( TaskName ) - self:SetID( Mission:GetNextTaskID( self ) ) -- The Mission orchestrates the task sequences .. - - self.TaskBriefing = "You are assigned to the task: " .. self.TaskName .. "." - - return self -end - ---- Cleans all references of a TASK_BASE. --- @param #TASK_BASE self --- @return #nil -function TASK_BASE:CleanUp() - - _EVENTDISPATCHER:OnPlayerLeaveRemove( self ) - _EVENTDISPATCHER:OnDeadRemove( self ) - _EVENTDISPATCHER:OnCrashRemove( self ) - _EVENTDISPATCHER:OnPilotDeadRemove( self ) - - return nil -end - - ---- Assign the @{Task}to a @{Group}. --- @param #TASK_BASE self --- @param Group#GROUP TaskGroup -function TASK_BASE:AssignToGroup( TaskGroup ) - self:F2( TaskGroup:GetName() ) - - local TaskGroupName = TaskGroup:GetName() - - TaskGroup:SetState( TaskGroup, "Assigned", self ) - - self:RemoveMenuForGroup( TaskGroup ) - self:SetAssignedMenuForGroup( TaskGroup ) - - local TaskUnits = TaskGroup:GetUnits() - for UnitID, UnitData in pairs( TaskUnits ) do - local TaskUnit = UnitData -- Unit#UNIT - local PlayerName = TaskUnit:GetPlayerName() - if PlayerName ~= nil or PlayerName ~= "" then - self:AssignToUnit( TaskUnit ) - end - end -end - ---- Send the briefng message of the @{Task} to the assigned @{Group}s. --- @param #TASK_BASE self -function TASK_BASE:SendBriefingToAssignedGroups() - self:F2() - - for TaskGroupName, TaskGroup in pairs( self.SetGroup:GetSet() ) do - - if self:IsAssignedToGroup( TaskGroup ) then - TaskGroup:Message( self.TaskBriefing, 60 ) - end - end -end - - ---- Assign the @{Task} from the @{Group}s. --- @param #TASK_BASE self -function TASK_BASE:UnAssignFromGroups() - self:F2() - - for TaskGroupName, TaskGroup in pairs( self.SetGroup:GetSet() ) do - - TaskGroup:SetState( TaskGroup, "Assigned", nil ) - local TaskUnits = TaskGroup:GetUnits() - for UnitID, UnitData in pairs( TaskUnits ) do - local TaskUnit = UnitData -- Unit#UNIT - local PlayerName = TaskUnit:GetPlayerName() - if PlayerName ~= nil or PlayerName ~= "" then - self:UnAssignFromUnit( TaskUnit ) - end - end - end -end - ---- Returns if the @{Task} is assigned to the Group. --- @param #TASK_BASE self --- @param Group#GROUP TaskGroup --- @return #boolean -function TASK_BASE:IsAssignedToGroup( TaskGroup ) - - local TaskGroupName = TaskGroup:GetName() - - if self:IsStateAssigned() then - if TaskGroup:GetState( TaskGroup, "Assigned" ) == self then - return true - end - end - - return false -end - ---- Assign the @{Task}to an alive @{Unit}. --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @return #TASK_BASE self -function TASK_BASE:AssignToUnit( TaskUnit ) - self:F( TaskUnit:GetName() ) - - return nil -end - ---- UnAssign the @{Task} from an alive @{Unit}. --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @return #TASK_BASE self -function TASK_BASE:UnAssignFromUnit( TaskUnitName ) - self:F( TaskUnitName ) - - if self:HasStateMachine( TaskUnitName ) == true then - self:RemoveStateMachines( TaskUnitName ) - self:RemoveProcesses( TaskUnitName ) - end - - return self -end - ---- Set the menu options of the @{Task} to all the groups in the SetGroup. --- @param #TASK_BASE self --- @return #TASK_BASE self -function TASK_BASE:SetPlannedMenu() - - local MenuText = self:GetPlannedMenuText() - for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - if not self:IsAssignedToGroup( TaskGroup ) then - self:SetPlannedMenuForGroup( TaskGroup, MenuText ) - end - end -end - ---- Set the menu options of the @{Task} to all the groups in the SetGroup. --- @param #TASK_BASE self --- @return #TASK_BASE self -function TASK_BASE:SetAssignedMenu() - - for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - if self:IsAssignedToGroup( TaskGroup ) then - self:SetAssignedMenuForGroup( TaskGroup ) - end - end -end - ---- Remove the menu options of the @{Task} to all the groups in the SetGroup. --- @param #TASK_BASE self --- @return #TASK_BASE self -function TASK_BASE:RemoveMenu() - - for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - self:RemoveMenuForGroup( TaskGroup ) - end -end - ---- Set the planned menu option of the @{Task}. --- @param #TASK_BASE self --- @param Group#GROUP TaskGroup --- @param #string MenuText The menu text. --- @return #TASK_BASE self -function TASK_BASE:SetPlannedMenuForGroup( TaskGroup, MenuText ) - self:E( TaskGroup:GetName() ) - - local TaskMission = self.Mission:GetName() - local TaskCategory = self:GetCategory() - local TaskType = self:GetType() - - local Mission = self.Mission - - Mission.MenuMission = Mission.MenuMission or {} - local MenuMission = Mission.MenuMission - - Mission.MenuCategory = Mission.MenuCategory or {} - local MenuCategory = Mission.MenuCategory - - Mission.MenuType = Mission.MenuType or {} - local MenuType = Mission.MenuType - - self.Menu = self.Menu or {} - local Menu = self.Menu - - local TaskGroupName = TaskGroup:GetName() - MenuMission[TaskGroupName] = MenuMission[TaskGroupName] or MENU_GROUP:New( TaskGroup, TaskMission, nil ) - - MenuCategory[TaskGroupName] = MenuCategory[TaskGroupName] or {} - MenuCategory[TaskGroupName][TaskCategory] = MenuCategory[TaskGroupName][TaskCategory] or MENU_GROUP:New( TaskGroup, TaskCategory, MenuMission[TaskGroupName] ) - - MenuType[TaskGroupName] = MenuType[TaskGroupName] or {} - MenuType[TaskGroupName][TaskType] = MenuType[TaskGroupName][TaskType] or MENU_GROUP:New( TaskGroup, TaskType, MenuCategory[TaskGroupName][TaskCategory] ) - - if Menu[TaskGroupName] then - Menu[TaskGroupName]:Remove() - end - Menu[TaskGroupName] = MENU_GROUP_COMMAND:New( TaskGroup, MenuText, MenuType[TaskGroupName][TaskType], self.MenuAssignToGroup, { self = self, TaskGroup = TaskGroup } ) - - return self -end - ---- Set the assigned menu options of the @{Task}. --- @param #TASK_BASE self --- @param Group#GROUP TaskGroup --- @return #TASK_BASE self -function TASK_BASE:SetAssignedMenuForGroup( TaskGroup ) - self:E( TaskGroup:GetName() ) - - local TaskMission = self.Mission:GetName() - - local Mission = self.Mission - - Mission.MenuMission = Mission.MenuMission or {} - local MenuMission = Mission.MenuMission - - self.MenuStatus = self.MenuStatus or {} - local MenuStatus = self.MenuStatus - - - self.MenuAbort = self.MenuAbort or {} - local MenuAbort = self.MenuAbort - - local TaskGroupName = TaskGroup:GetName() - MenuMission[TaskGroupName] = MenuMission[TaskGroupName] or MENU_GROUP:New( TaskGroup, TaskMission, nil ) - MenuStatus[TaskGroupName] = MENU_GROUP_COMMAND:New( TaskGroup, "Task Status", MenuMission[TaskGroupName], self.MenuTaskStatus, { self = self, TaskGroup = TaskGroup } ) - MenuAbort[TaskGroupName] = MENU_GROUP_COMMAND:New( TaskGroup, "Abort Task", MenuMission[TaskGroupName], self.MenuTaskAbort, { self = self, TaskGroup = TaskGroup } ) - - return self -end - ---- Remove the menu option of the @{Task} for a @{Group}. --- @param #TASK_BASE self --- @param Group#GROUP TaskGroup --- @return #TASK_BASE self -function TASK_BASE:RemoveMenuForGroup( TaskGroup ) - - local TaskGroupName = TaskGroup:GetName() - - local Mission = self.Mission - local MenuMission = Mission.MenuMission - local MenuCategory = Mission.MenuCategory - local MenuType = Mission.MenuType - local MenuStatus = self.MenuStatus - local MenuAbort = self.MenuAbort - local Menu = self.Menu - - Menu = Menu or {} - if Menu[TaskGroupName] then - Menu[TaskGroupName]:Remove() - Menu[TaskGroupName] = nil - end - - MenuType = MenuType or {} - if MenuType[TaskGroupName] then - for _, Menu in pairs( MenuType[TaskGroupName] ) do - Menu:Remove() - end - MenuType[TaskGroupName] = nil - end - - MenuCategory = MenuCategory or {} - if MenuCategory[TaskGroupName] then - for _, Menu in pairs( MenuCategory[TaskGroupName] ) do - Menu:Remove() - end - MenuCategory[TaskGroupName] = nil - end - - MenuStatus = MenuStatus or {} - if MenuStatus[TaskGroupName] then - MenuStatus[TaskGroupName]:Remove() - MenuStatus[TaskGroupName] = nil - end - - MenuAbort = MenuAbort or {} - if MenuAbort[TaskGroupName] then - MenuAbort[TaskGroupName]:Remove() - MenuAbort[TaskGroupName] = nil - end - -end - -function TASK_BASE.MenuAssignToGroup( MenuParam ) - - local self = MenuParam.self - local TaskGroup = MenuParam.TaskGroup - - self:AssignToGroup( TaskGroup ) -end - -function TASK_BASE.MenuTaskStatus( MenuParam ) - - local self = MenuParam.self - local TaskGroup = MenuParam.TaskGroup - - --self:AssignToGroup( TaskGroup ) -end - -function TASK_BASE.MenuTaskAbort( MenuParam ) - - local self = MenuParam.self - local TaskGroup = MenuParam.TaskGroup - - --self:AssignToGroup( TaskGroup ) -end - - - ---- Returns the @{Task} name. --- @param #TASK_BASE self --- @return #string TaskName -function TASK_BASE:GetTaskName() - return self.TaskName -end - - ---- Add Process to @{Task} with key @{Unit}. --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @return #TASK_BASE self -function TASK_BASE:AddProcess( TaskUnit, Process ) - local TaskUnitName = TaskUnit:GetName() - self.Processes = self.Processes or {} - self.Processes[TaskUnitName] = self.Processes[TaskUnitName] or {} - self.Processes[TaskUnitName][#self.Processes[TaskUnitName]+1] = Process - return Process -end - - ---- Remove Processes from @{Task} with key @{Unit} --- @param #TASK_BASE self --- @param #string TaskUnitName --- @return #TASK_BASE self -function TASK_BASE:RemoveProcesses( TaskUnitName ) - - for ProcessID, ProcessData in pairs( self.Processes[TaskUnitName] ) do - local Process = ProcessData -- Process#PROCESS - Process:StopEvents() - Process = nil - self.Processes[TaskUnitName][ProcessID] = nil - self:E( self.Processes[TaskUnitName][ProcessID] ) - end - self.Processes[TaskUnitName] = nil -end - ---- Fail processes from @{Task} with key @{Unit} --- @param #TASK_BASE self --- @param #string TaskUnitName --- @return #TASK_BASE self -function TASK_BASE:FailProcesses( TaskUnitName ) - - for ProcessID, ProcessData in pairs( self.Processes[TaskUnitName] ) do - local Process = ProcessData -- Process#PROCESS - Process.Fsm:Fail() - end -end - ---- Add a FiniteStateMachine to @{Task} with key @{Unit} --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @return #TASK_BASE self -function TASK_BASE:AddStateMachine( TaskUnit, Fsm ) - local TaskUnitName = TaskUnit:GetName() - self.Fsm[TaskUnitName] = self.Fsm[TaskUnitName] or {} - self.Fsm[TaskUnitName][#self.Fsm[TaskUnitName]+1] = Fsm - return Fsm -end - ---- Remove FiniteStateMachines from @{Task} with key @{Unit} --- @param #TASK_BASE self --- @param #string TaskUnitName --- @return #TASK_BASE self -function TASK_BASE:RemoveStateMachines( TaskUnitName ) - - for _, Fsm in pairs( self.Fsm[TaskUnitName] ) do - Fsm = nil - self.Fsm[TaskUnitName][_] = nil - self:E( self.Fsm[TaskUnitName][_] ) - end - self.Fsm[TaskUnitName] = nil -end - ---- Checks if there is a FiniteStateMachine assigned to @{Unit} for @{Task} --- @param #TASK_BASE self --- @param #string TaskUnitName --- @return #TASK_BASE self -function TASK_BASE:HasStateMachine( TaskUnitName ) - - self:F( { TaskUnitName, self.Fsm[TaskUnitName] ~= nil } ) - return ( self.Fsm[TaskUnitName] ~= nil ) -end - - - - - ---- Register a potential new assignment for a new spawned @{Unit}. --- Tasks only get assigned if there are players in it. --- @param #TASK_BASE self --- @param Event#EVENTDATA Event --- @return #TASK_BASE self -function TASK_BASE:_EventAssignUnit( Event ) - if Event.IniUnit then - self:F( Event ) - local TaskUnit = Event.IniUnit - if TaskUnit:IsAlive() then - local TaskPlayerName = TaskUnit:GetPlayerName() - if TaskPlayerName ~= nil then - if not self:HasStateMachine( TaskUnit ) then - -- Check if the task was assigned to the group, if it was assigned to the group, assign to the unit just spawned and initiate the processes. - local TaskGroup = TaskUnit:GetGroup() - if self:IsAssignedToGroup( TaskGroup ) then - self:AssignToUnit( TaskUnit ) - end - end - end - end - end - return nil -end - ---- Catches the "player leave unit" event for a @{Unit} .... --- When a player is an air unit, and leaves the unit: --- --- * and he is not at an airbase runway on the ground, he will fail its task. --- * and he is on an airbase and on the ground, the process for him will just continue to work, he can switch airplanes, and take-off again. --- This is important to model the change from plane types for a player during mission assignment. --- @param #TASK_BASE self --- @param Event#EVENTDATA Event --- @return #TASK_BASE self -function TASK_BASE:_EventPlayerLeaveUnit( Event ) - self:F( Event ) - if Event.IniUnit then - local TaskUnit = Event.IniUnit - local TaskUnitName = Event.IniUnitName - - -- Check if for this unit in the task there is a process ongoing. - if self:HasStateMachine( TaskUnitName ) then - if TaskUnit:IsAir() then - if TaskUnit:IsAboveRunway() then - -- do nothing - else - self:E( "IsNotAboveRunway" ) - -- Player left airplane during an assigned task and was not at an airbase. - self:FailProcesses( TaskUnitName ) - self:UnAssignFromUnit( TaskUnitName ) - end - end - end - - end - return nil -end - ---- UnAssigns a @{Unit} that is left by a player, crashed, dead, .... --- There are only assignments if there are players in it. --- @param #TASK_BASE self --- @param Event#EVENTDATA Event --- @return #TASK_BASE self -function TASK_BASE:_EventDead( Event ) - self:F( Event ) - if Event.IniUnit then - local TaskUnit = Event.IniUnit - local TaskUnitName = Event.IniUnitName - - -- Check if for this unit in the task there is a process ongoing. - if self:HasStateMachine( TaskUnitName ) then - self:FailProcesses( TaskUnitName ) - self:UnAssignFromUnit( TaskUnitName ) - end - - local TaskGroup = Event.IniUnit:GetGroup() - TaskGroup:SetState( TaskGroup, "Assigned", nil ) - end - return nil -end - ---- Gets the Scoring of the task --- @param #TASK_BASE self --- @return Scoring#SCORING Scoring -function TASK_BASE:GetScoring() - return self.Mission:GetScoring() -end - - ---- Gets the Task Index, which is a combination of the Task category, the Task type, the Task name. --- @param #TASK_BASE self --- @return #string The Task ID -function TASK_BASE:GetTaskIndex() - - local TaskCategory = self:GetCategory() - local TaskType = self:GetType() - local TaskName = self:GetName() - - return TaskCategory .. "." ..TaskType .. "." .. TaskName -end - ---- Sets the Name of the Task --- @param #TASK_BASE self --- @param #string TaskName -function TASK_BASE:SetName( TaskName ) - self.TaskName = TaskName -end - ---- Gets the Name of the Task --- @param #TASK_BASE self --- @return #string The Task Name -function TASK_BASE:GetName() - return self.TaskName -end - ---- Sets the Type of the Task --- @param #TASK_BASE self --- @param #string TaskType -function TASK_BASE:SetType( TaskType ) - self.TaskType = TaskType -end - ---- Gets the Type of the Task --- @param #TASK_BASE self --- @return #string TaskType -function TASK_BASE:GetType() - return self.TaskType -end - ---- Sets the Category of the Task --- @param #TASK_BASE self --- @param #string TaskCategory -function TASK_BASE:SetCategory( TaskCategory ) - self.TaskCategory = TaskCategory -end - ---- Gets the Category of the Task --- @param #TASK_BASE self --- @return #string TaskCategory -function TASK_BASE:GetCategory() - return self.TaskCategory -end - ---- Sets the ID of the Task --- @param #TASK_BASE self --- @param #string TaskID -function TASK_BASE:SetID( TaskID ) - self.TaskID = TaskID -end - ---- Gets the ID of the Task --- @param #TASK_BASE self --- @return #string TaskID -function TASK_BASE:GetID() - return self.TaskID -end - - ---- Sets a @{Task} to status **Success**. --- @param #TASK_BASE self -function TASK_BASE:StateSuccess() - self:SetState( self, "State", "Success" ) - return self -end - ---- Is the @{Task} status **Success**. --- @param #TASK_BASE self -function TASK_BASE:IsStateSuccess() - return self:GetStateString() == "Success" -end - ---- Sets a @{Task} to status **Failed**. --- @param #TASK_BASE self -function TASK_BASE:StateFailed() - self:SetState( self, "State", "Failed" ) - return self -end - ---- Is the @{Task} status **Failed**. --- @param #TASK_BASE self -function TASK_BASE:IsStateFailed() - return self:GetStateString() == "Failed" -end - ---- Sets a @{Task} to status **Planned**. --- @param #TASK_BASE self -function TASK_BASE:StatePlanned() - self:SetState( self, "State", "Planned" ) - return self -end - ---- Is the @{Task} status **Planned**. --- @param #TASK_BASE self -function TASK_BASE:IsStatePlanned() - return self:GetStateString() == "Planned" -end - ---- Sets a @{Task} to status **Assigned**. --- @param #TASK_BASE self -function TASK_BASE:StateAssigned() - self:SetState( self, "State", "Assigned" ) - return self -end - ---- Is the @{Task} status **Assigned**. --- @param #TASK_BASE self -function TASK_BASE:IsStateAssigned() - return self:GetStateString() == "Assigned" -end - ---- Sets a @{Task} to status **Hold**. --- @param #TASK_BASE self -function TASK_BASE:StateHold() - self:SetState( self, "State", "Hold" ) - return self -end - ---- Is the @{Task} status **Hold**. --- @param #TASK_BASE self -function TASK_BASE:IsStateHold() - return self:GetStateString() == "Hold" -end - ---- Sets a @{Task} to status **Replanned**. --- @param #TASK_BASE self -function TASK_BASE:StateReplanned() - self:SetState( self, "State", "Replanned" ) - return self -end - ---- Is the @{Task} status **Replanned**. --- @param #TASK_BASE self -function TASK_BASE:IsStateReplanned() - return self:GetStateString() == "Replanned" -end - ---- Gets the @{Task} status. --- @param #TASK_BASE self -function TASK_BASE:GetStateString() - return self:GetState( self, "State" ) -end - ---- Sets a @{Task} briefing. --- @param #TASK_BASE self --- @param #string TaskBriefing --- @return #TASK_BASE self -function TASK_BASE:SetBriefing( TaskBriefing ) - self.TaskBriefing = TaskBriefing - return self -end - - - ---- Adds a score for the TASK to be achieved. --- @param #TASK_BASE self --- @param #string TaskStatus is the status of the TASK when the score needs to be given. --- @param #string ScoreText is a text describing the score that is given according the status. --- @param #number Score is a number providing the score of the status. --- @return #TASK_BASE self -function TASK_BASE:AddScore( TaskStatus, ScoreText, Score ) - self:F2( { TaskStatus, ScoreText, Score } ) - - self.Scores[TaskStatus] = self.Scores[TaskStatus] or {} - self.Scores[TaskStatus].ScoreText = ScoreText - self.Scores[TaskStatus].Score = Score - return self -end - ---- StateMachine callback function for a TASK --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @param StateMachine#STATEMACHINE_TASK Fsm --- @param #string Event --- @param #string From --- @param #string To --- @param Event#EVENTDATA Event -function TASK_BASE:OnAssigned( TaskUnit, Fsm, Event, From, To ) - - self:E("Assigned") - - local TaskGroup = TaskUnit:GetGroup() - - TaskGroup:Message( self.TaskBriefing, 20 ) - - self:RemoveMenuForGroup( TaskGroup ) - self:SetAssignedMenuForGroup( TaskGroup ) - -end - - ---- StateMachine callback function for a TASK --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @param StateMachine#STATEMACHINE_TASK Fsm --- @param #string Event --- @param #string From --- @param #string To --- @param Event#EVENTDATA Event -function TASK_BASE:OnSuccess( TaskUnit, Fsm, Event, From, To ) - - self:E("Success") - - self:UnAssignFromGroups() - - local TaskGroup = TaskUnit:GetGroup() - self.Mission:SetPlannedMenu() - - self:StateSuccess() - - -- The task has become successful, the event catchers can be cleaned. - self:CleanUp() - -end - ---- StateMachine callback function for a TASK --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @param StateMachine#STATEMACHINE_TASK Fsm --- @param #string Event --- @param #string From --- @param #string To --- @param Event#EVENTDATA Event -function TASK_BASE:OnFailed( TaskUnit, Fsm, Event, From, To ) - - self:E( { "Failed for unit ", TaskUnit:GetName(), TaskUnit:GetPlayerName() } ) - - -- A task cannot be "failed", so a task will always be there waiting for players to join. - -- When the player leaves its unit, we will need to check whether he was on the ground or not at an airbase. - -- When the player crashes, we will need to check whether in the group there are other players still active. It not, we reset the task from Assigned to Planned, otherwise, we just leave as Assigned. - - self:UnAssignFromGroups() - self:StatePlanned() - -end - ---- StateMachine callback function for a TASK --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @param StateMachine#STATEMACHINE_TASK Fsm --- @param #string Event --- @param #string From --- @param #string To --- @param Event#EVENTDATA Event -function TASK_BASE:OnStateChange( TaskUnit, Fsm, Event, From, To ) - - if self:IsTrace() then - MESSAGE:New( "Task " .. self.TaskName .. " : " .. Event .. " changed to state " .. To, 15 ):ToAll() - end - - self:E( { Event, From, To } ) - self:SetState( self, "State", To ) - - if self.Scores[To] then - local Scoring = self:GetScoring() - if Scoring then - Scoring:_AddMissionScore( self.Mission, self.Scores[To].ScoreText, self.Scores[To].Score ) - end - end - -end - - ---- @param #TASK_BASE self -function TASK_BASE:_Schedule() - self:F2() - - self.TaskScheduler = SCHEDULER:New( self, _Scheduler, {}, 15, 15 ) - return self -end - - ---- @param #TASK_BASE self -function TASK_BASE._Scheduler() - self:F2() - - return true -end - - - - ---- This module contains the TASK_SEAD classes. --- --- 1) @{#TASK_SEAD} class, extends @{Task#TASK_BASE} +-- 1) @{#TASK_SEAD} class, extends @{Tasking.Task#TASK} -- ================================================= -- The @{#TASK_SEAD} class defines a SEAD task for a @{Set} of Target Units, located at a Target Zone, --- based on the tasking capabilities defined in @{Task#TASK_BASE}. --- The TASK_SEAD is implemented using a @{Statemachine#STATEMACHINE_TASK}, and has the following statuses: +-- based on the tasking capabilities defined in @{Tasking.Task#TASK}. +-- The TASK_SEAD is implemented using a @{Statemachine#FSM_TASK}, and has the following statuses: -- -- * **None**: Start of the process --- * **Planned**: The SEAD task is planned. Upon Planned, the sub-process @{Process_Assign#PROCESS_ASSIGN_ACCEPT} is started to accept the task. --- * **Assigned**: The SEAD task is assigned to a @{Group#GROUP}. Upon Assigned, the sub-process @{Process_Route#PROCESS_ROUTE} is started to route the active Units in the Group to the attack zone. +-- * **Planned**: The SEAD task is planned. Upon Planned, the sub-process @{Process_Fsm.Assign#ACT_ASSIGN_ACCEPT} is started to accept the task. +-- * **Assigned**: The SEAD task is assigned to a @{Wrapper.Group#GROUP}. Upon Assigned, the sub-process @{Process_Fsm.Route#ACT_ROUTE} is started to route the active Units in the Group to the attack zone. -- * **Success**: The SEAD task is successfully completed. Upon Success, the sub-process @{Process_SEAD#PROCESS_SEAD} is started to follow-up successful SEADing of the targets assigned in the task. -- * **Failed**: The SEAD task has failed. This will happen if the player exists the task early, without communicating a possible cancellation to HQ. -- @@ -29651,142 +29139,74 @@ end -- @module Task_SEAD + do -- TASK_SEAD --- The TASK_SEAD class -- @type TASK_SEAD -- @field Set#SET_UNIT TargetSetUnit - -- @extends Task#TASK_BASE + -- @extends Tasking.Task#TASK TASK_SEAD = { ClassName = "TASK_SEAD", } --- Instantiates a new TASK_SEAD. -- @param #TASK_SEAD self - -- @param Mission#MISSION Mission + -- @param Tasking.Mission#MISSION Mission -- @param Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. -- @param #string TaskName The name of the Task. -- @param Set#SET_UNIT UnitSetTargets - -- @param Zone#ZONE_BASE TargetZone + -- @param Core.Zone#ZONE_BASE TargetZone -- @return #TASK_SEAD self function TASK_SEAD:New( Mission, SetGroup, TaskName, TargetSetUnit, TargetZone ) - local self = BASE:Inherit( self, TASK_BASE:New( Mission, SetGroup, TaskName, "SEAD", "A2G" ) ) + local self = BASE:Inherit( self, TASK:New( Mission, SetGroup, TaskName, "SEAD" ) ) -- Tasking.Task_SEAD#TASK_SEAD self:F() self.TargetSetUnit = TargetSetUnit self.TargetZone = TargetZone + + local Fsm = self:GetUnitProcess() - _EVENTDISPATCHER:OnPlayerLeaveUnit( self._EventPlayerLeaveUnit, self ) - _EVENTDISPATCHER:OnDead( self._EventDead, self ) - _EVENTDISPATCHER:OnCrash( self._EventDead, self ) - _EVENTDISPATCHER:OnPilotDead( self._EventDead, self ) + Fsm:AddProcess( "Planned", "Accept", ACT_ASSIGN_ACCEPT:New( self.TaskBriefing ), { Assigned = "Route", Rejected = "Eject" } ) + Fsm:AddProcess( "Assigned", "Route", ACT_ROUTE_ZONE:New( self.TargetZone ), { Arrived = "Update" } ) + Fsm:AddAction ( "Rejected", "Eject", "Planned" ) + Fsm:AddAction ( "Arrived", "Update", "Updated" ) + Fsm:AddProcess( "Updated", "Account", ACT_ACCOUNT_DEADS:New( self.TargetSetUnit, "SEAD" ), { Accounted = "Success" } ) + Fsm:AddProcess( "Updated", "Smoke", ACT_ASSIST_SMOKE_TARGETS_ZONE:New( self.TargetSetUnit, self.TargetZone ) ) + Fsm:AddAction ( "Accounted", "Success", "Success" ) + Fsm:AddAction ( "Failed", "Fail", "Failed" ) + + function Fsm:onenterUpdated( TaskUnit ) + self:E( { self } ) + self:Account() + self:Smoke() + end + +-- _EVENTDISPATCHER:OnPlayerLeaveUnit( self._EventPlayerLeaveUnit, self ) +-- _EVENTDISPATCHER:OnDead( self._EventDead, self ) +-- _EVENTDISPATCHER:OnCrash( self._EventDead, self ) +-- _EVENTDISPATCHER:OnPilotDead( self._EventDead, self ) return self end - - --- Removes a TASK_SEAD. - -- @param #TASK_SEAD self - -- @return #nil - function TASK_SEAD:CleanUp() - - self:GetParent(self):CleanUp() - - return nil - end - - - - --- Assign the @{Task} to a @{Unit}. - -- @param #TASK_SEAD self - -- @param Unit#UNIT TaskUnit - -- @return #TASK_SEAD self - function TASK_SEAD:AssignToUnit( TaskUnit ) - self:F( TaskUnit:GetName() ) - - local ProcessAssign = self:AddProcess( TaskUnit, PROCESS_ASSIGN_ACCEPT:New( self, TaskUnit, self.TaskBriefing ) ) - local ProcessRoute = self:AddProcess( TaskUnit, PROCESS_ROUTE:New( self, TaskUnit, self.TargetZone ) ) - local ProcessSEAD = self:AddProcess( TaskUnit, PROCESS_DESTROY:New( self, "SEAD", TaskUnit, self.TargetSetUnit ) ) - local ProcessSmoke = self:AddProcess( TaskUnit, PROCESS_SMOKE_TARGETS:New( self, TaskUnit, self.TargetSetUnit, self.TargetZone ) ) - - local Process = self:AddStateMachine( TaskUnit, STATEMACHINE_TASK:New( self, TaskUnit, { - initial = 'None', - events = { - { name = 'Next', from = 'None', to = 'Planned' }, - { name = 'Next', from = 'Planned', to = 'Assigned' }, - { name = 'Reject', from = 'Planned', to = 'Rejected' }, - { name = 'Next', from = 'Assigned', to = 'Success' }, - { name = 'Fail', from = 'Assigned', to = 'Failed' }, - { name = 'Fail', from = 'Arrived', to = 'Failed' } - }, - callbacks = { - onNext = self.OnNext, - onRemove = self.OnRemove, - }, - subs = { - Assign = { onstateparent = 'Planned', oneventparent = 'Next', fsm = ProcessAssign.Fsm, event = 'Start', returnevents = { 'Next', 'Reject' } }, - Route = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessRoute.Fsm, event = 'Start' }, - Sead = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessSEAD.Fsm, event = 'Start', returnevents = { 'Next' } }, - Smoke = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessSmoke.Fsm, event = 'Start', } - } - } ) ) - - ProcessRoute:AddScore( "Failed", "failed to destroy a radar", -100 ) - ProcessSEAD:AddScore( "Destroy", "destroyed a radar", 25 ) - ProcessSEAD:AddScore( "Failed", "failed to destroy a radar", -100 ) - self:AddScore( "Success", "Destroyed all target radars", 250 ) - - Process:Next() - - return self - end - - --- StateMachine callback function for a TASK - -- @param #TASK_SEAD self - -- @param StateMachine#STATEMACHINE_TASK Fsm - -- @param #string Event - -- @param #string From - -- @param #string To - -- @param Event#EVENTDATA Event - function TASK_SEAD:OnNext( Fsm, Event, From, To ) - - self:SetState( self, "State", To ) - - end - - + --- @param #TASK_SEAD self function TASK_SEAD:GetPlannedMenuText() return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.TargetSetUnit:GetUnitTypesText() .. " )" end - --- @param #TASK_SEAD self - function TASK_SEAD:_Schedule() - self:F2() - - self.TaskScheduler = SCHEDULER:New( self, _Scheduler, {}, 15, 15 ) - return self - end - - - --- @param #TASK_SEAD self - function TASK_SEAD._Scheduler() - self:F2() - - return true - end - end ---- This module contains the TASK_A2G classes. +--- (AI) (SP) (MP) Tasking for Air to Ground Processes. -- --- 1) @{#TASK_A2G} class, extends @{Task#TASK_BASE} +-- 1) @{#TASK_A2G} class, extends @{Tasking.Task#TASK} -- ================================================= -- The @{#TASK_A2G} class defines a CAS or BAI task of a @{Set} of Target Units, --- located at a Target Zone, based on the tasking capabilities defined in @{Task#TASK_BASE}. --- The TASK_A2G is implemented using a @{Statemachine#STATEMACHINE_TASK}, and has the following statuses: +-- located at a Target Zone, based on the tasking capabilities defined in @{Tasking.Task#TASK}. +-- The TASK_A2G is implemented using a @{Statemachine#FSM_TASK}, and has the following statuses: -- -- * **None**: Start of the process --- * **Planned**: The SEAD task is planned. Upon Planned, the sub-process @{Process_Assign#PROCESS_ASSIGN_ACCEPT} is started to accept the task. --- * **Assigned**: The SEAD task is assigned to a @{Group#GROUP}. Upon Assigned, the sub-process @{Process_Route#PROCESS_ROUTE} is started to route the active Units in the Group to the attack zone. +-- * **Planned**: The SEAD task is planned. Upon Planned, the sub-process @{Process_Fsm.Assign#ACT_ASSIGN_ACCEPT} is started to accept the task. +-- * **Assigned**: The SEAD task is assigned to a @{Wrapper.Group#GROUP}. Upon Assigned, the sub-process @{Process_Fsm.Route#ACT_ROUTE} is started to route the active Units in the Group to the attack zone. -- * **Success**: The SEAD task is successfully completed. Upon Success, the sub-process @{Process_SEAD#PROCESS_SEAD} is started to follow-up successful SEADing of the targets assigned in the task. -- * **Failed**: The SEAD task has failed. This will happen if the player exists the task early, without communicating a possible cancellation to HQ. -- @@ -29801,128 +29221,136 @@ do -- TASK_A2G --- The TASK_A2G class -- @type TASK_A2G - -- @extends Task#TASK_BASE + -- @extends Tasking.Task#TASK TASK_A2G = { ClassName = "TASK_A2G", } --- Instantiates a new TASK_A2G. -- @param #TASK_A2G self - -- @param Mission#MISSION Mission + -- @param Tasking.Mission#MISSION Mission -- @param Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. -- @param #string TaskName The name of the Task. -- @param #string TaskType BAI or CAS -- @param Set#SET_UNIT UnitSetTargets - -- @param Zone#ZONE_BASE TargetZone + -- @param Core.Zone#ZONE_BASE TargetZone -- @return #TASK_A2G self function TASK_A2G:New( Mission, SetGroup, TaskName, TaskType, TargetSetUnit, TargetZone, FACUnit ) - local self = BASE:Inherit( self, TASK_BASE:New( Mission, SetGroup, TaskName, TaskType, "A2G" ) ) + local self = BASE:Inherit( self, TASK:New( Mission, SetGroup, TaskName, TaskType ) ) self:F() self.TargetSetUnit = TargetSetUnit self.TargetZone = TargetZone self.FACUnit = FACUnit + + local Fsm = self:GetUnitProcess() - _EVENTDISPATCHER:OnPlayerLeaveUnit( self._EventPlayerLeaveUnit, self ) - _EVENTDISPATCHER:OnDead( self._EventDead, self ) - _EVENTDISPATCHER:OnCrash( self._EventDead, self ) - _EVENTDISPATCHER:OnPilotDead( self._EventDead, self ) + Fsm:AddProcess( "Planned", "Accept", ACT_ASSIGN_ACCEPT:New( "Attack the Area" ), { Assigned = "Route", Rejected = "Eject" } ) + Fsm:AddProcess( "Assigned", "Route", ACT_ROUTE_ZONE:New( self.TargetZone ), { Arrived = "Update" } ) + Fsm:AddAction ( "Rejected", "Eject", "Planned" ) + Fsm:AddAction ( "Arrived", "Update", "Updated" ) + Fsm:AddProcess( "Updated", "Account", ACT_ACCOUNT_DEADS:New( self.TargetSetUnit, "Attack" ), { Accounted = "Success" } ) + Fsm:AddProcess( "Updated", "Smoke", ACT_ASSIST_SMOKE_TARGETS_ZONE:New( self.TargetSetUnit, self.TargetZone ) ) + --Fsm:AddProcess( "Updated", "JTAC", PROCESS_JTAC:New( self, TaskUnit, self.TargetSetUnit, self.FACUnit ) ) + Fsm:AddAction ( "Accounted", "Success", "Success" ) + Fsm:AddAction ( "Failed", "Fail", "Failed" ) + + function Fsm:onenterUpdated( TaskUnit ) + self:E( { self } ) + self:Account() + self:Smoke() + end + + + + --_EVENTDISPATCHER:OnPlayerLeaveUnit( self._EventPlayerLeaveUnit, self ) + --_EVENTDISPATCHER:OnDead( self._EventDead, self ) + --_EVENTDISPATCHER:OnCrash( self._EventDead, self ) + --_EVENTDISPATCHER:OnPilotDead( self._EventDead, self ) return self end - --- Removes a TASK_A2G. - -- @param #TASK_A2G self - -- @return #nil - function TASK_A2G:CleanUp() - - self:GetParent( self ):CleanUp() - - return nil - end - - - --- Assign the @{Task} to a @{Unit}. - -- @param #TASK_A2G self - -- @param Unit#UNIT TaskUnit - -- @return #TASK_A2G self - function TASK_A2G:AssignToUnit( TaskUnit ) - self:F( TaskUnit:GetName() ) - - local ProcessAssign = self:AddProcess( TaskUnit, PROCESS_ASSIGN_ACCEPT:New( self, TaskUnit, self.TaskBriefing ) ) - local ProcessRoute = self:AddProcess( TaskUnit, PROCESS_ROUTE:New( self, TaskUnit, self.TargetZone ) ) - local ProcessDestroy = self:AddProcess( TaskUnit, PROCESS_DESTROY:New( self, self.TaskType, TaskUnit, self.TargetSetUnit ) ) - local ProcessSmoke = self:AddProcess( TaskUnit, PROCESS_SMOKE_TARGETS:New( self, TaskUnit, self.TargetSetUnit, self.TargetZone ) ) - local ProcessJTAC = self:AddProcess( TaskUnit, PROCESS_JTAC:New( self, TaskUnit, self.TargetSetUnit, self.FACUnit ) ) - - local Process = self:AddStateMachine( TaskUnit, STATEMACHINE_TASK:New( self, TaskUnit, { - initial = 'None', - events = { - { name = 'Next', from = 'None', to = 'Planned' }, - { name = 'Next', from = 'Planned', to = 'Assigned' }, - { name = 'Reject', from = 'Planned', to = 'Rejected' }, - { name = 'Next', from = 'Assigned', to = 'Success' }, - { name = 'Fail', from = 'Assigned', to = 'Failed' }, - { name = 'Fail', from = 'Arrived', to = 'Failed' } - }, - callbacks = { - onNext = self.OnNext, - onRemove = self.OnRemove, - }, - subs = { - Assign = { onstateparent = 'Planned', oneventparent = 'Next', fsm = ProcessAssign.Fsm, event = 'Start', returnevents = { 'Next', 'Reject' } }, - Route = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessRoute.Fsm, event = 'Start' }, - Destroy = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessDestroy.Fsm, event = 'Start', returnevents = { 'Next' } }, - Smoke = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessSmoke.Fsm, event = 'Start', }, - JTAC = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessJTAC.Fsm, event = 'Start', }, - } - } ) ) - - ProcessRoute:AddScore( "Failed", "failed to destroy a ground unit", -100 ) - ProcessDestroy:AddScore( "Destroy", "destroyed a ground unit", 25 ) - ProcessDestroy:AddScore( "Failed", "failed to destroy a ground unit", -100 ) - - Process:Next() - - return self - end - - --- StateMachine callback function for a TASK - -- @param #TASK_A2G self - -- @param StateMachine#STATEMACHINE_TASK Fsm - -- @param #string Event - -- @param #string From - -- @param #string To - -- @param Event#EVENTDATA Event - function TASK_A2G:OnNext( Fsm, Event, From, To, Event ) - - self:SetState( self, "State", To ) - - end - --- @param #TASK_A2G self function TASK_A2G:GetPlannedMenuText() return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.TargetSetUnit:GetUnitTypesText() .. " )" end - - --- @param #TASK_A2G self - function TASK_A2G:_Schedule() - self:F2() - - self.TaskScheduler = SCHEDULER:New( self, _Scheduler, {}, 15, 15 ) - return self end - - - --- @param #TASK_A2G self - function TASK_A2G._Scheduler() - self:F2() - - return true - end - -end + + + +--- The main include file for the MOOSE system. + +--- Core Routines +Include.File( "Utilities/Routines" ) +Include.File( "Utilities/Utils" ) + +--- Core Classes +Include.File( "Core/Base" ) +Include.File( "Core/Scheduler" ) +Include.File( "Core/ScheduleDispatcher") +Include.File( "Core/Event" ) +Include.File( "Core/Menu" ) +Include.File( "Core/Zone" ) +Include.File( "Core/Database" ) +Include.File( "Core/Set" ) +Include.File( "Core/Point" ) +Include.File( "Core/Message" ) +Include.File( "Core/Fsm" ) + +--- Wrapper Classes +Include.File( "Wrapper/Object" ) +Include.File( "Wrapper/Identifiable" ) +Include.File( "Wrapper/Positionable" ) +Include.File( "Wrapper/Controllable" ) +Include.File( "Wrapper/Group" ) +Include.File( "Wrapper/Unit" ) +Include.File( "Wrapper/Client" ) +Include.File( "Wrapper/Static" ) +Include.File( "Wrapper/Airbase" ) + +--- Functional Classes +Include.File( "Functional/Scoring" ) +Include.File( "Functional/CleanUp" ) +Include.File( "Functional/Spawn" ) +Include.File( "Functional/Movement" ) +Include.File( "Functional/Sead" ) +Include.File( "Functional/Escort" ) +Include.File( "Functional/MissileTrainer" ) +Include.File( "Functional/AirbasePolice" ) +Include.File( "Functional/Detection" ) + +--- AI Classes +Include.File( "AI/AI_Balancer" ) +Include.File( "AI/AI_Patrol" ) +Include.File( "AI/AI_Cargo" ) + +--- Actions +Include.File( "Actions/Act_Assign" ) +Include.File( "Actions/Act_Route" ) +Include.File( "Actions/Act_Account" ) +Include.File( "Actions/Act_Assist" ) + +--- Task Handling Classes +Include.File( "Tasking/CommandCenter" ) +Include.File( "Tasking/Mission" ) +Include.File( "Tasking/Task" ) +Include.File( "Tasking/DetectionManager" ) +Include.File( "Tasking/Task_SEAD" ) +Include.File( "Tasking/Task_A2G" ) + + +-- The order of the declarations is important here. Don't touch it. + +--- Declare the event dispatcher based on the EVENT class +_EVENTDISPATCHER = EVENT:New() -- Core.Event#EVENT + +--- Declare the timer dispatcher based on the SCHEDULEDISPATCHER class +_SCHEDULEDISPATCHER = SCHEDULEDISPATCHER:New() -- Core.Timer#SCHEDULEDISPATCHER + +--- Declare the main database object, which is used internally by the MOOSE classes. +_DATABASE = DATABASE:New() -- Database#DATABASE diff --git a/Moose Mission Setup/Moose Mission Update/readme.txt b/Moose Mission Setup/Moose Mission Update/readme.txt new file mode 100644 index 000000000..c6a5e53c1 --- /dev/null +++ b/Moose Mission Setup/Moose Mission Update/readme.txt @@ -0,0 +1,51 @@ +7-Zip 16.02 +----------- + +7-Zip is a file archiver for Windows NT / 2000 / 2003 / 2008 / 2012 / XP / Vista / 7 / 8 / 10. + +7-Zip Copyright (C) 1999-2016 Igor Pavlov. + +The main features of 7-Zip: + + - High compression ratio in the new 7z format + - Supported formats: + - Packing / unpacking: 7z, XZ, BZIP2, GZIP, TAR, ZIP and WIM. + - Unpacking only: AR, ARJ, CAB, CHM, CPIO, CramFS, DMG, EXT, FAT, GPT, HFS, + IHEX, ISO, LZH, LZMA, MBR, MSI, NSIS, NTFS, QCOW2, RAR, + RPM, SquashFS, UDF, UEFI, VDI, VHD, VMDK, WIM, XAR and Z. + - Fast compression and decompression + - Self-extracting capability for 7z format + - Strong AES-256 encryption in 7z and ZIP formats + - Integration with Windows Shell + - Powerful File Manager + - Powerful command line version + - Localizations for 85 languages + + +7-Zip is free software distributed under the GNU LGPL (except for unRar code). +Read License.txt for more information about license. + + + This distribution package contains the following files: + + 7zFM.exe - 7-Zip File Manager + 7-zip.dll - Plugin for Windows Shell + 7-zip32.dll - Plugin for Windows Shell (32-bit plugin for 64-bit system) + 7zg.exe - GUI module + 7z.exe - Command line version + 7z.dll - 7-Zip engine module + 7z.sfx - SFX module (Windows version) + 7zCon.sfx - SFX module (Console version) + + License.txt - License information + readme.txt - This file + History.txt - History of 7-Zip + 7-zip.chm - User's Manual in HTML Help format + descript.ion - Description for files + + Lang\en.ttt - English (base) localization file + Lang\*.txt - Localization files + + +--- +End of document diff --git a/Moose Mission Setup/Moose.lua b/Moose Mission Setup/Moose.lua index 8db5aea17..ac46b458e 100644 --- a/Moose Mission Setup/Moose.lua +++ b/Moose Mission Setup/Moose.lua @@ -1,5 +1,5 @@ env.info( '*** MOOSE STATIC INCLUDE START *** ' ) -env.info( 'Moose Generation Timestamp: 20161205_1227' ) +env.info( 'Moose Generation Timestamp: 20161216_1640' ) local base = _G Include = {} @@ -2495,6 +2495,19 @@ end env.info(( 'Init: Scripts Loaded v1.1' )) +--- This module contains derived utilities taken from the MIST framework, +-- which are excellent tools to be reused in an OO environment!. +-- +-- ### Authors: +-- +-- * Grimes : Design & Programming of the MIST framework. +-- +-- ### Contributions: +-- +-- * FlightControl : Rework to OO framework +-- +-- @module Utils + --- @type SMOKECOLOR -- @field Green @@ -2597,11 +2610,11 @@ UTILS.OneLineSerialize = function( tbl ) -- serialization of a table all on a s tbl_str[#tbl_str + 1] = table.concat(val_str) end elseif type(val) == 'function' then - -- tbl_str[#tbl_str + 1] = "function " .. tostring(ind) - -- tbl_str[#tbl_str + 1] = ',' --I think this is right, I just added it + tbl_str[#tbl_str + 1] = "f() " .. tostring(ind) + tbl_str[#tbl_str + 1] = ',' --I think this is right, I just added it else --- env.info('unable to serialize value type ' .. routines.utils.basicSerialize(type(val)) .. ' at index ' .. tostring(ind)) --- env.info( debug.traceback() ) + env.info('unable to serialize value type ' .. routines.utils.basicSerialize(type(val)) .. ' at index ' .. tostring(ind)) + env.info( debug.traceback() ) end end @@ -2792,8 +2805,8 @@ end -- -- 1.1) BASE constructor -- --------------------- --- Any class derived from BASE, must use the @{Base#BASE.New) constructor within the @{Base#BASE.Inherit) method. --- See an example at the @{Base#BASE.New} method how this is done. +-- Any class derived from BASE, must use the @{Core.Base#BASE.New) constructor within the @{Core.Base#BASE.Inherit) method. +-- See an example at the @{Core.Base#BASE.New} method how this is done. -- -- 1.2) BASE Trace functionality -- ----------------------------- @@ -2869,24 +2882,6 @@ FORMATION = { ---- The base constructor. This is the top top class of all classed defined within the MOOSE. --- Any new class needs to be derived from this class for proper inheritance. --- @param #BASE self --- @return #BASE The new instance of the BASE class. --- @usage --- -- This declares the constructor of the class TASK, inheriting from BASE. --- --- TASK constructor --- -- @param #TASK self --- -- @param Parameter The parameter of the New constructor. --- -- @return #TASK self --- function TASK:New( Parameter ) --- --- local self = BASE:Inherit( self, BASE:New() ) --- --- self.Variable = Parameter --- --- return self --- end -- @todo need to investigate if the deepCopy is really needed... Don't think so. function BASE:New() local self = routines.utils.deepCopy( self ) -- Create a new self instance @@ -2895,9 +2890,40 @@ function BASE:New() self.__index = self _ClassID = _ClassID + 1 self.ClassID = _ClassID + + return self end +function BASE:_Destructor() + --self:E("_Destructor") + + --self:EventRemoveAll() +end + +function BASE:_SetDestructor() + + -- TODO: Okay, this is really technical... + -- When you set a proxy to a table to catch __gc, weak tables don't behave like weak... + -- Therefore, I am parking this logic until I've properly discussed all this with the community. + --[[ + local proxy = newproxy(true) + local proxyMeta = getmetatable(proxy) + + proxyMeta.__gc = function () + env.info("In __gc for " .. self:GetClassNameAndID() ) + if self._Destructor then + self:_Destructor() + end + end + + -- keep the userdata from newproxy reachable until the object + -- table is about to be garbage-collected - then the __gc hook + -- will be invoked and the destructor called + rawset( self, '__proxy', proxy ) + --]] +end + --- This is the worker method to inherit from a parent class. -- @param #BASE self -- @param Child is the Child class that inherits. @@ -2910,6 +2936,8 @@ function BASE:Inherit( Child, Parent ) if Child ~= nil then setmetatable( Child, Parent ) Child.__index = Child + + Child:_SetDestructor() end --self:T( 'Inherited from ' .. Parent.ClassName ) return Child @@ -2949,7 +2977,7 @@ end --- Set a new listener for the class. -- @param self --- @param DCSTypes#Event Event +-- @param Dcs.DCSTypes#Event Event -- @param #function EventFunction -- @return #BASE function BASE:AddEvent( Event, EventFunction ) @@ -2965,12 +2993,278 @@ end --- Returns the event dispatcher -- @param #BASE self --- @return Event#EVENT +-- @return Core.Event#EVENT function BASE:Event() return _EVENTDISPATCHER end +--- Remove all subscribed events +-- @param #BASE self +-- @return #BASE +function BASE:EventRemoveAll() + + _EVENTDISPATCHER:RemoveAll( self ) + + return self +end + +--- Subscribe to a S_EVENT_SHOT event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnShot( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_SHOT ) + + return self +end + +--- Subscribe to a S_EVENT_HIT event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnHit( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_HIT ) + + return self +end + +--- Subscribe to a S_EVENT_TAKEOFF event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnTakeOff( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_TAKEOFF ) + + return self +end + +--- Subscribe to a S_EVENT_LAND event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnLand( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_LAND ) + + return self +end + +--- Subscribe to a S_EVENT_CRASH event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnCrash( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_CRASH ) + + return self +end + +--- Subscribe to a S_EVENT_EJECTION event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnEjection( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_EJECTION ) + + return self +end + + +--- Subscribe to a S_EVENT_REFUELING event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnRefueling( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_REFUELING ) + + return self +end + +--- Subscribe to a S_EVENT_DEAD event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnDead( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_DEAD ) + + return self +end + +--- Subscribe to a S_EVENT_PILOT_DEAD event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnPilotDead( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_PILOT_DEAD ) + + return self +end + +--- Subscribe to a S_EVENT_BASE_CAPTURED event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnBaseCaptured( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_BASE_CAPTURED ) + + return self +end + +--- Subscribe to a S_EVENT_MISSION_START event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnMissionStart( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_MISSION_START ) + + return self +end + +--- Subscribe to a S_EVENT_MISSION_END event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnPlayerMissionEnd( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_MISSION_END ) + + return self +end + +--- Subscribe to a S_EVENT_TOOK_CONTROL event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnTookControl( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_TOOK_CONTROL ) + + return self +end + +--- Subscribe to a S_EVENT_REFUELING_STOP event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnRefuelingStop( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_REFUELING_STOP ) + + return self +end + +--- Subscribe to a S_EVENT_BIRTH event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnBirth( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_BIRTH ) + + return self +end + +--- Subscribe to a S_EVENT_HUMAN_FAILURE event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnHumanFailure( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_HUMAN_FAILURE ) + + return self +end + +--- Subscribe to a S_EVENT_ENGINE_STARTUP event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnEngineStartup( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_ENGINE_STARTUP ) + + return self +end + +--- Subscribe to a S_EVENT_ENGINE_SHUTDOWN event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnEngineShutdown( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_ENGINE_SHUTDOWN ) + + return self +end + +--- Subscribe to a S_EVENT_PLAYER_ENTER_UNIT event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnPlayerEnterUnit( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_PLAYER_ENTER_UNIT ) + + return self +end + +--- Subscribe to a S_EVENT_PLAYER_LEAVE_UNIT event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnPlayerLeaveUnit( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_PLAYER_LEAVE_UNIT ) + + return self +end + +--- Subscribe to a S_EVENT_PLAYER_COMMENT event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnPlayerComment( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_PLAYER_COMMENT ) + + return self +end + +--- Subscribe to a S_EVENT_SHOOTING_START event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnShootingStart( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_SHOOTING_START ) + + return self +end + +--- Subscribe to a S_EVENT_SHOOTING_END event. +-- @param #BASE self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @return #BASE +function BASE:EventOnShootingEnd( EventFunction ) + + _EVENTDISPATCHER:OnEventGeneric( EventFunction, self, world.event.S_EVENT_SHOOTING_END ) + + return self +end + + + @@ -3047,8 +3341,8 @@ local BaseEventCodes = { --- Creation of a Birth Event. -- @param #BASE self --- @param DCSTypes#Time EventTime The time stamp of the event. --- @param DCSObject#Object Initiator The initiating object of the event. +-- @param Dcs.DCSTypes#Time EventTime The time stamp of the event. +-- @param Dcs.DCSWrapper.Object#Object Initiator The initiating object of the event. -- @param #string IniUnitName The initiating unit name. -- @param place -- @param subplace @@ -3069,8 +3363,8 @@ end --- Creation of a Crash Event. -- @param #BASE self --- @param DCSTypes#Time EventTime The time stamp of the event. --- @param DCSObject#Object Initiator The initiating object of the event. +-- @param Dcs.DCSTypes#Time EventTime The time stamp of the event. +-- @param Dcs.DCSWrapper.Object#Object Initiator The initiating object of the event. function BASE:CreateEventCrash( EventTime, Initiator ) self:F( { EventTime, Initiator } ) @@ -3083,10 +3377,10 @@ function BASE:CreateEventCrash( EventTime, Initiator ) world.onEvent( Event ) end --- TODO: Complete DCSTypes#Event structure. +-- TODO: Complete Dcs.DCSTypes#Event structure. --- The main event handling function... This function captures all events generated for the class. -- @param #BASE self --- @param DCSTypes#Event event +-- @param Dcs.DCSTypes#Event event function BASE:onEvent(event) --self:F( { BaseEventCodes[event.id], event } ) @@ -3129,7 +3423,7 @@ function BASE:GetState( Object, StateName ) local ClassNameAndID = Object:GetClassNameAndID() if self.States[ClassNameAndID] then - local State = self.States[ClassNameAndID][StateName] + local State = self.States[ClassNameAndID][StateName] or false self:T2( { ClassNameAndID, StateName, State } ) return State end @@ -3401,2947 +3695,46 @@ end ---- This module contains the OBJECT class. --- --- 1) @{Object#OBJECT} class, extends @{Base#BASE} --- =========================================================== --- The @{Object#OBJECT} class is a wrapper class to handle the DCS Object objects: --- --- * Support all DCS Object APIs. --- * Enhance with Object specific APIs not in the DCS Object API set. --- * Manage the "state" of the DCS Object. --- --- 1.1) OBJECT constructor: --- ------------------------------ --- The OBJECT class provides the following functions to construct a OBJECT instance: --- --- * @{Object#OBJECT.New}(): Create a OBJECT instance. --- --- 1.2) OBJECT methods: --- -------------------------- --- The following methods can be used to identify an Object object: --- --- * @{Object#OBJECT.GetID}(): Returns the ID of the Object object. --- --- === --- --- @module Object --- @author FlightControl - ---- The OBJECT class --- @type OBJECT --- @extends Base#BASE --- @field #string ObjectName The name of the Object. -OBJECT = { - ClassName = "OBJECT", - ObjectName = "", -} - - ---- A DCSObject --- @type DCSObject --- @field id_ The ID of the controllable in DCS - ---- Create a new OBJECT from a DCSObject --- @param #OBJECT self --- @param DCSObject#Object ObjectName The Object name --- @return #OBJECT self -function OBJECT:New( ObjectName ) - local self = BASE:Inherit( self, BASE:New() ) - self:F2( ObjectName ) - self.ObjectName = ObjectName - return self -end - - ---- Returns the unit's unique identifier. --- @param Object#OBJECT self --- @return DCSObject#Object.ID ObjectID --- @return #nil The DCS Object is not existing or alive. -function OBJECT:GetID() - self:F2( self.ObjectName ) - - local DCSObject = self:GetDCSObject() - - if DCSObject then - local ObjectID = DCSObject:getID() - return ObjectID - end - - return nil -end - ---- Destroys the OBJECT. --- @param #OBJECT self --- @return #nil The DCS Unit is not existing or alive. -function OBJECT:Destroy() - self:F2( self.ObjectName ) - - local DCSObject = self:GetDCSObject() - - if DCSObject then - - DCSObject:destroy() - end - - return nil -end - - - - ---- This module contains the IDENTIFIABLE class. --- --- 1) @{Identifiable#IDENTIFIABLE} class, extends @{Object#OBJECT} --- =============================================================== --- The @{Identifiable#IDENTIFIABLE} class is a wrapper class to handle the DCS Identifiable objects: --- --- * Support all DCS Identifiable APIs. --- * Enhance with Identifiable specific APIs not in the DCS Identifiable API set. --- * Manage the "state" of the DCS Identifiable. --- --- 1.1) IDENTIFIABLE constructor: --- ------------------------------ --- The IDENTIFIABLE class provides the following functions to construct a IDENTIFIABLE instance: --- --- * @{Identifiable#IDENTIFIABLE.New}(): Create a IDENTIFIABLE instance. --- --- 1.2) IDENTIFIABLE methods: --- -------------------------- --- The following methods can be used to identify an identifiable object: --- --- * @{Identifiable#IDENTIFIABLE.GetName}(): Returns the name of the Identifiable. --- * @{Identifiable#IDENTIFIABLE.IsAlive}(): Returns if the Identifiable is alive. --- * @{Identifiable#IDENTIFIABLE.GetTypeName}(): Returns the type name of the Identifiable. --- * @{Identifiable#IDENTIFIABLE.GetCoalition}(): Returns the coalition of the Identifiable. --- * @{Identifiable#IDENTIFIABLE.GetCountry}(): Returns the country of the Identifiable. --- * @{Identifiable#IDENTIFIABLE.GetDesc}(): Returns the descriptor structure of the Identifiable. --- --- --- === --- --- @module Identifiable --- @author FlightControl - ---- The IDENTIFIABLE class --- @type IDENTIFIABLE --- @extends Object#OBJECT --- @field #string IdentifiableName The name of the identifiable. -IDENTIFIABLE = { - ClassName = "IDENTIFIABLE", - IdentifiableName = "", -} - -local _CategoryName = { - [Unit.Category.AIRPLANE] = "Airplane", - [Unit.Category.HELICOPTER] = "Helicoper", - [Unit.Category.GROUND_UNIT] = "Ground Identifiable", - [Unit.Category.SHIP] = "Ship", - [Unit.Category.STRUCTURE] = "Structure", - } - ---- Create a new IDENTIFIABLE from a DCSIdentifiable --- @param #IDENTIFIABLE self --- @param DCSIdentifiable#Identifiable IdentifiableName The DCS Identifiable name --- @return #IDENTIFIABLE self -function IDENTIFIABLE:New( IdentifiableName ) - local self = BASE:Inherit( self, OBJECT:New( IdentifiableName ) ) - self:F2( IdentifiableName ) - self.IdentifiableName = IdentifiableName - return self -end - ---- Returns if the Identifiable is alive. --- @param Identifiable#IDENTIFIABLE self --- @return #boolean true if Identifiable is alive. --- @return #nil The DCS Identifiable is not existing or alive. -function IDENTIFIABLE:IsAlive() - self:F2( self.IdentifiableName ) - - local DCSIdentifiable = self:GetDCSObject() - - if DCSIdentifiable then - local IdentifiableIsAlive = DCSIdentifiable:isExist() - return IdentifiableIsAlive - end - - return false -end - - - - ---- Returns DCS Identifiable object name. --- The function provides access to non-activated objects too. --- @param Identifiable#IDENTIFIABLE self --- @return #string The name of the DCS Identifiable. --- @return #nil The DCS Identifiable is not existing or alive. -function IDENTIFIABLE:GetName() - self:F2( self.IdentifiableName ) - - local DCSIdentifiable = self:GetDCSObject() - - if DCSIdentifiable then - local IdentifiableName = self.IdentifiableName - return IdentifiableName - end - - self:E( self.ClassName .. " " .. self.IdentifiableName .. " not found!" ) - return nil -end - - ---- Returns the type name of the DCS Identifiable. --- @param Identifiable#IDENTIFIABLE self --- @return #string The type name of the DCS Identifiable. --- @return #nil The DCS Identifiable is not existing or alive. -function IDENTIFIABLE:GetTypeName() - self:F2( self.IdentifiableName ) - - local DCSIdentifiable = self:GetDCSObject() - - if DCSIdentifiable then - local IdentifiableTypeName = DCSIdentifiable:getTypeName() - self:T3( IdentifiableTypeName ) - return IdentifiableTypeName - end - - self:E( self.ClassName .. " " .. self.IdentifiableName .. " not found!" ) - return nil -end - - ---- Returns category of the DCS Identifiable. --- @param #IDENTIFIABLE self --- @return DCSObject#Object.Category The category ID -function IDENTIFIABLE:GetCategory() - self:F2( self.ObjectName ) - - local DCSObject = self:GetDCSObject() - if DCSObject then - local ObjectCategory = DCSObject:getCategory() - self:T3( ObjectCategory ) - return ObjectCategory - end - - return nil -end - - ---- Returns the DCS Identifiable category name as defined within the DCS Identifiable Descriptor. --- @param Identifiable#IDENTIFIABLE self --- @return #string The DCS Identifiable Category Name -function IDENTIFIABLE:GetCategoryName() - local DCSIdentifiable = self:GetDCSObject() - - if DCSIdentifiable then - local IdentifiableCategoryName = _CategoryName[ self:GetDesc().category ] - return IdentifiableCategoryName - end - - self:E( self.ClassName .. " " .. self.IdentifiableName .. " not found!" ) - return nil -end - ---- Returns coalition of the Identifiable. --- @param Identifiable#IDENTIFIABLE self --- @return DCSCoalitionObject#coalition.side The side of the coalition. --- @return #nil The DCS Identifiable is not existing or alive. -function IDENTIFIABLE:GetCoalition() - self:F2( self.IdentifiableName ) - - local DCSIdentifiable = self:GetDCSObject() - - if DCSIdentifiable then - local IdentifiableCoalition = DCSIdentifiable:getCoalition() - self:T3( IdentifiableCoalition ) - return IdentifiableCoalition - end - - self:E( self.ClassName .. " " .. self.IdentifiableName .. " not found!" ) - return nil -end - ---- Returns country of the Identifiable. --- @param Identifiable#IDENTIFIABLE self --- @return DCScountry#country.id The country identifier. --- @return #nil The DCS Identifiable is not existing or alive. -function IDENTIFIABLE:GetCountry() - self:F2( self.IdentifiableName ) - - local DCSIdentifiable = self:GetDCSObject() - - if DCSIdentifiable then - local IdentifiableCountry = DCSIdentifiable:getCountry() - self:T3( IdentifiableCountry ) - return IdentifiableCountry - end - - self:E( self.ClassName .. " " .. self.IdentifiableName .. " not found!" ) - return nil -end - - - ---- Returns Identifiable descriptor. Descriptor type depends on Identifiable category. --- @param Identifiable#IDENTIFIABLE self --- @return DCSIdentifiable#Identifiable.Desc The Identifiable descriptor. --- @return #nil The DCS Identifiable is not existing or alive. -function IDENTIFIABLE:GetDesc() - self:F2( self.IdentifiableName ) - - local DCSIdentifiable = self:GetDCSObject() - - if DCSIdentifiable then - local IdentifiableDesc = DCSIdentifiable:getDesc() - self:T2( IdentifiableDesc ) - return IdentifiableDesc - end - - self:E( self.ClassName .. " " .. self.IdentifiableName .. " not found!" ) - return nil -end - - - - - - - - - ---- This module contains the POSITIONABLE class. --- --- 1) @{Positionable#POSITIONABLE} class, extends @{Identifiable#IDENTIFIABLE} --- =========================================================== --- The @{Positionable#POSITIONABLE} class is a wrapper class to handle the POSITIONABLE objects: --- --- * Support all DCS APIs. --- * Enhance with POSITIONABLE specific APIs not in the DCS API set. --- * Manage the "state" of the POSITIONABLE. --- --- 1.1) POSITIONABLE constructor: --- ------------------------------ --- The POSITIONABLE class provides the following functions to construct a POSITIONABLE instance: --- --- * @{Positionable#POSITIONABLE.New}(): Create a POSITIONABLE instance. --- --- 1.2) POSITIONABLE methods: --- -------------------------- --- The following methods can be used to identify an measurable object: --- --- * @{Positionable#POSITIONABLE.GetID}(): Returns the ID of the measurable object. --- * @{Positionable#POSITIONABLE.GetName}(): Returns the name of the measurable object. --- --- === --- --- @module Positionable --- @author FlightControl - ---- The POSITIONABLE class --- @type POSITIONABLE --- @extends Identifiable#IDENTIFIABLE --- @field #string PositionableName The name of the measurable. -POSITIONABLE = { - ClassName = "POSITIONABLE", - PositionableName = "", -} - ---- A DCSPositionable --- @type DCSPositionable --- @field id_ The ID of the controllable in DCS - ---- Create a new POSITIONABLE from a DCSPositionable --- @param #POSITIONABLE self --- @param DCSPositionable#Positionable PositionableName The POSITIONABLE name --- @return #POSITIONABLE self -function POSITIONABLE:New( PositionableName ) - local self = BASE:Inherit( self, IDENTIFIABLE:New( PositionableName ) ) - - return self -end - ---- Returns the @{DCSTypes#Position3} position vectors indicating the point and direction vectors in 3D of the POSITIONABLE within the mission. --- @param Positionable#POSITIONABLE self --- @return DCSTypes#Position The 3D position vectors of the POSITIONABLE. --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:GetPositionVec3() - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local PositionablePosition = DCSPositionable:getPosition() - self:T3( PositionablePosition ) - return PositionablePosition - end - - return nil -end - ---- Returns the @{DCSTypes#Vec2} vector indicating the point in 2D of the POSITIONABLE within the mission. --- @param Positionable#POSITIONABLE self --- @return DCSTypes#Vec2 The 2D point vector of the POSITIONABLE. --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:GetVec2() - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local PositionableVec3 = DCSPositionable:getPosition().p - - local PositionableVec2 = {} - PositionableVec2.x = PositionableVec3.x - PositionableVec2.y = PositionableVec3.z - - self:T2( PositionableVec2 ) - return PositionableVec2 - end - - return nil -end - ---- Returns a POINT_VEC2 object indicating the point in 2D of the POSITIONABLE within the mission. --- @param Positionable#POSITIONABLE self --- @return Point#POINT_VEC2 The 2D point vector of the POSITIONABLE. --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:GetPointVec2() - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local PositionableVec3 = DCSPositionable:getPosition().p - - local PositionablePointVec2 = POINT_VEC2:NewFromVec3( PositionableVec3 ) - - self:T2( PositionablePointVec2 ) - return PositionablePointVec2 - end - - return nil -end - - ---- Returns a random @{DCSTypes#Vec3} vector within a range, indicating the point in 3D of the POSITIONABLE within the mission. --- @param Positionable#POSITIONABLE self --- @return DCSTypes#Vec3 The 3D point vector of the POSITIONABLE. --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:GetRandomVec3( Radius ) - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local PositionablePointVec3 = DCSPositionable:getPosition().p - local PositionableRandomVec3 = {} - local angle = math.random() * math.pi*2; - PositionableRandomVec3.x = PositionablePointVec3.x + math.cos( angle ) * math.random() * Radius; - PositionableRandomVec3.y = PositionablePointVec3.y - PositionableRandomVec3.z = PositionablePointVec3.z + math.sin( angle ) * math.random() * Radius; - - self:T3( PositionableRandomVec3 ) - return PositionableRandomVec3 - end - - return nil -end - ---- Returns the @{DCSTypes#Vec3} vector indicating the 3D vector of the POSITIONABLE within the mission. --- @param Positionable#POSITIONABLE self --- @return DCSTypes#Vec3 The 3D point vector of the POSITIONABLE. --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:GetVec3() - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local PositionableVec3 = DCSPositionable:getPosition().p - self:T3( PositionableVec3 ) - return PositionableVec3 - end - - return nil -end - ---- Returns the altitude of the POSITIONABLE. --- @param Positionable#POSITIONABLE self --- @return DCSTypes#Distance The altitude of the POSITIONABLE. --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:GetAltitude() - self:F2() - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local PositionablePointVec3 = DCSPositionable:getPoint() --DCSTypes#Vec3 - return PositionablePointVec3.y - end - - return nil -end - ---- Returns if the Positionable is located above a runway. --- @param Positionable#POSITIONABLE self --- @return #boolean true if Positionable is above a runway. --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:IsAboveRunway() - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - - local Vec2 = self:GetVec2() - local SurfaceType = land.getSurfaceType( Vec2 ) - local IsAboveRunway = SurfaceType == land.SurfaceType.RUNWAY - - self:T2( IsAboveRunway ) - return IsAboveRunway - end - - return nil -end - - - ---- Returns the POSITIONABLE heading in degrees. --- @param Positionable#POSITIONABLE self --- @return #number The POSTIONABLE heading -function POSITIONABLE:GetHeading() - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - - local PositionablePosition = DCSPositionable:getPosition() - if PositionablePosition then - local PositionableHeading = math.atan2( PositionablePosition.x.z, PositionablePosition.x.x ) - if PositionableHeading < 0 then - PositionableHeading = PositionableHeading + 2 * math.pi - end - PositionableHeading = PositionableHeading * 180 / math.pi - self:T2( PositionableHeading ) - return PositionableHeading - end - end - - return nil -end - - ---- Returns true if the POSITIONABLE is in the air. --- @param Positionable#POSITIONABLE self --- @return #boolean true if in the air. --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:InAir() - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local PositionableInAir = DCSPositionable:inAir() - self:T3( PositionableInAir ) - return PositionableInAir - end - - return nil -end - ---- Returns the POSITIONABLE velocity vector. --- @param Positionable#POSITIONABLE self --- @return DCSTypes#Vec3 The velocity vector --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:GetVelocity() - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local PositionableVelocityVec3 = DCSPositionable:getVelocity() - self:T3( PositionableVelocityVec3 ) - return PositionableVelocityVec3 - end - - return nil -end - ---- Returns the POSITIONABLE velocity in km/h. --- @param Positionable#POSITIONABLE self --- @return #number The velocity in km/h --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:GetVelocityKMH() - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local VelocityVec3 = self:GetVelocity() - local Velocity = ( VelocityVec3.x ^ 2 + VelocityVec3.y ^ 2 + VelocityVec3.z ^ 2 ) ^ 0.5 -- in meters / sec - local Velocity = Velocity * 3.6 -- now it is in km/h. - self:T3( Velocity ) - return Velocity - end - - return nil -end - - - - ---- This module contains the CONTROLLABLE class. --- --- 1) @{Controllable#CONTROLLABLE} class, extends @{Positionable#POSITIONABLE} --- =========================================================== --- The @{Controllable#CONTROLLABLE} class is a wrapper class to handle the DCS Controllable objects: --- --- * Support all DCS Controllable APIs. --- * Enhance with Controllable specific APIs not in the DCS Controllable API set. --- * Handle local Controllable Controller. --- * Manage the "state" of the DCS Controllable. --- --- 1.1) CONTROLLABLE constructor --- ----------------------------- --- The CONTROLLABLE class provides the following functions to construct a CONTROLLABLE instance: --- --- * @{#CONTROLLABLE.New}(): Create a CONTROLLABLE instance. --- --- 1.2) CONTROLLABLE task methods --- ------------------------------ --- Several controllable task methods are available that help you to prepare tasks. --- These methods return a string consisting of the task description, which can then be given to either a @{Controllable#CONTROLLABLE.PushTask} or @{Controllable#SetTask} method to assign the task to the CONTROLLABLE. --- Tasks are specific for the category of the CONTROLLABLE, more specific, for AIR, GROUND or AIR and GROUND. --- Each task description where applicable indicates for which controllable category the task is valid. --- There are 2 main subdivisions of tasks: Assigned tasks and EnRoute tasks. --- --- ### 1.2.1) Assigned task methods --- --- Assigned task methods make the controllable execute the task where the location of the (possible) targets of the task are known before being detected. --- This is different from the EnRoute tasks, where the targets of the task need to be detected before the task can be executed. --- --- Find below a list of the **assigned task** methods: --- --- * @{#CONTROLLABLE.TaskAttackControllable}: (AIR) Attack a Controllable. --- * @{#CONTROLLABLE.TaskAttackMapObject}: (AIR) Attacking the map object (building, structure, e.t.c). --- * @{#CONTROLLABLE.TaskAttackUnit}: (AIR) Attack the Unit. --- * @{#CONTROLLABLE.TaskBombing}: (AIR) Delivering weapon at the point on the ground. --- * @{#CONTROLLABLE.TaskBombingRunway}: (AIR) Delivering weapon on the runway. --- * @{#CONTROLLABLE.TaskEmbarking}: (AIR) Move the controllable to a Vec2 Point, wait for a defined duration and embark a controllable. --- * @{#CONTROLLABLE.TaskEmbarkToTransport}: (GROUND) Embark to a Transport landed at a location. --- * @{#CONTROLLABLE.TaskEscort}: (AIR) Escort another airborne controllable. --- * @{#CONTROLLABLE.TaskFAC_AttackControllable}: (AIR + GROUND) The task makes the controllable/unit a FAC and orders the FAC to control the target (enemy ground controllable) destruction. --- * @{#CONTROLLABLE.TaskFireAtPoint}: (GROUND) Fire at a VEC2 point until ammunition is finished. --- * @{#CONTROLLABLE.TaskFollow}: (AIR) Following another airborne controllable. --- * @{#CONTROLLABLE.TaskHold}: (GROUND) Hold ground controllable from moving. --- * @{#CONTROLLABLE.TaskHoldPosition}: (AIR) Hold position at the current position of the first unit of the controllable. --- * @{#CONTROLLABLE.TaskLand}: (AIR HELICOPTER) Landing at the ground. For helicopters only. --- * @{#CONTROLLABLE.TaskLandAtZone}: (AIR) Land the controllable at a @{Zone#ZONE_RADIUS). --- * @{#CONTROLLABLE.TaskOrbitCircle}: (AIR) Orbit at the current position of the first unit of the controllable at a specified alititude. --- * @{#CONTROLLABLE.TaskOrbitCircleAtVec2}: (AIR) Orbit at a specified position at a specified alititude during a specified duration with a specified speed. --- * @{#CONTROLLABLE.TaskRefueling}: (AIR) Refueling from the nearest tanker. No parameters. --- * @{#CONTROLLABLE.TaskRoute}: (AIR + GROUND) Return a Misson task to follow a given route defined by Points. --- * @{#CONTROLLABLE.TaskRouteToVec2}: (AIR + GROUND) Make the Controllable move to a given point. --- * @{#CONTROLLABLE.TaskRouteToVec3}: (AIR + GROUND) Make the Controllable move to a given point. --- * @{#CONTROLLABLE.TaskRouteToZone}: (AIR + GROUND) Route the controllable to a given zone. --- * @{#CONTROLLABLE.TaskReturnToBase}: (AIR) Route the controllable to an airbase. --- --- ### 1.2.2) EnRoute task methods --- --- EnRoute tasks require the targets of the task need to be detected by the controllable (using its sensors) before the task can be executed: --- --- * @{#CONTROLLABLE.EnRouteTaskAWACS}: (AIR) Aircraft will act as an AWACS for friendly units (will provide them with information about contacts). No parameters. --- * @{#CONTROLLABLE.EnRouteTaskEngageControllable}: (AIR) Engaging a controllable. The task does not assign the target controllable to the unit/controllable to attack now; it just allows the unit/controllable to engage the target controllable as well as other assigned targets. --- * @{#CONTROLLABLE.EnRouteTaskEngageTargets}: (AIR) Engaging targets of defined types. --- * @{#CONTROLLABLE.EnRouteTaskEWR}: (AIR) Attack the Unit. --- * @{#CONTROLLABLE.EnRouteTaskFAC}: (AIR + GROUND) The task makes the controllable/unit a FAC and lets the FAC to choose a targets (enemy ground controllable) around as well as other assigned targets. --- * @{#CONTROLLABLE.EnRouteTaskFAC_EngageControllable}: (AIR + GROUND) The task makes the controllable/unit a FAC and lets the FAC to choose the target (enemy ground controllable) as well as other assigned targets. --- * @{#CONTROLLABLE.EnRouteTaskTanker}: (AIR) Aircraft will act as a tanker for friendly units. No parameters. --- --- ### 1.2.3) Preparation task methods --- --- There are certain task methods that allow to tailor the task behaviour: --- --- * @{#CONTROLLABLE.TaskWrappedAction}: Return a WrappedAction Task taking a Command. --- * @{#CONTROLLABLE.TaskCombo}: Return a Combo Task taking an array of Tasks. --- * @{#CONTROLLABLE.TaskCondition}: Return a condition section for a controlled task. --- * @{#CONTROLLABLE.TaskControlled}: Return a Controlled Task taking a Task and a TaskCondition. --- --- ### 1.2.4) Obtain the mission from controllable templates --- --- Controllable templates contain complete mission descriptions. Sometimes you want to copy a complete mission from a controllable and assign it to another: --- --- * @{#CONTROLLABLE.TaskMission}: (AIR + GROUND) Return a mission task from a mission template. --- --- 1.3) CONTROLLABLE Command methods --- -------------------------- --- Controllable **command methods** prepare the execution of commands using the @{#CONTROLLABLE.SetCommand} method: --- --- * @{#CONTROLLABLE.CommandDoScript}: Do Script command. --- * @{#CONTROLLABLE.CommandSwitchWayPoint}: Perform a switch waypoint command. --- --- 1.4) CONTROLLABLE Option methods --- ------------------------- --- Controllable **Option methods** change the behaviour of the Controllable while being alive. --- --- ### 1.4.1) Rule of Engagement: --- --- * @{#CONTROLLABLE.OptionROEWeaponFree} --- * @{#CONTROLLABLE.OptionROEOpenFire} --- * @{#CONTROLLABLE.OptionROEReturnFire} --- * @{#CONTROLLABLE.OptionROEEvadeFire} --- --- To check whether an ROE option is valid for a specific controllable, use: --- --- * @{#CONTROLLABLE.OptionROEWeaponFreePossible} --- * @{#CONTROLLABLE.OptionROEOpenFirePossible} --- * @{#CONTROLLABLE.OptionROEReturnFirePossible} --- * @{#CONTROLLABLE.OptionROEEvadeFirePossible} --- --- ### 1.4.2) Rule on thread: --- --- * @{#CONTROLLABLE.OptionROTNoReaction} --- * @{#CONTROLLABLE.OptionROTPassiveDefense} --- * @{#CONTROLLABLE.OptionROTEvadeFire} --- * @{#CONTROLLABLE.OptionROTVertical} --- --- To test whether an ROT option is valid for a specific controllable, use: --- --- * @{#CONTROLLABLE.OptionROTNoReactionPossible} --- * @{#CONTROLLABLE.OptionROTPassiveDefensePossible} --- * @{#CONTROLLABLE.OptionROTEvadeFirePossible} --- * @{#CONTROLLABLE.OptionROTVerticalPossible} --- --- === --- --- @module Controllable --- @author FlightControl - ---- The CONTROLLABLE class --- @type CONTROLLABLE --- @extends Positionable#POSITIONABLE --- @field DCSControllable#Controllable DCSControllable The DCS controllable class. --- @field #string ControllableName The name of the controllable. -CONTROLLABLE = { - ClassName = "CONTROLLABLE", - ControllableName = "", - WayPointFunctions = {}, -} - ---- Create a new CONTROLLABLE from a DCSControllable --- @param #CONTROLLABLE self --- @param DCSControllable#Controllable ControllableName The DCS Controllable name --- @return #CONTROLLABLE self -function CONTROLLABLE:New( ControllableName ) - local self = BASE:Inherit( self, POSITIONABLE:New( ControllableName ) ) - self:F2( ControllableName ) - self.ControllableName = ControllableName - return self -end - --- DCS Controllable methods support. - ---- Get the controller for the CONTROLLABLE. --- @param #CONTROLLABLE self --- @return DCSController#Controller -function CONTROLLABLE:_GetController() - self:F2( { self.ControllableName } ) - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - local ControllableController = DCSControllable:getController() - self:T3( ControllableController ) - return ControllableController - end - - return nil -end - - - --- Tasks - ---- Popping current Task from the controllable. --- @param #CONTROLLABLE self --- @return Controllable#CONTROLLABLE self -function CONTROLLABLE:PopCurrentTask() - self:F2() - - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - local Controller = self:_GetController() - Controller:popTask() - return self - end - - return nil -end - ---- Pushing Task on the queue from the controllable. --- @param #CONTROLLABLE self --- @return Controllable#CONTROLLABLE self -function CONTROLLABLE:PushTask( DCSTask, WaitTime ) - self:F2() - - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - local Controller = self:_GetController() - - -- When a controllable SPAWNs, it takes about a second to get the controllable in the simulator. Setting tasks to unspawned controllables provides unexpected results. - -- Therefore we schedule the functions to set the mission and options for the Controllable. - -- Controller:pushTask( DCSTask ) - - if WaitTime then - SCHEDULER:New( Controller, Controller.pushTask, { DCSTask }, WaitTime ) - else - Controller:pushTask( DCSTask ) - end - - return self - end - - return nil -end - ---- Clearing the Task Queue and Setting the Task on the queue from the controllable. --- @param #CONTROLLABLE self --- @return Controllable#CONTROLLABLE self -function CONTROLLABLE:SetTask( DCSTask, WaitTime ) - self:F2( { DCSTask } ) - - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - - local Controller = self:_GetController() - self:E(Controller) - - -- When a controllable SPAWNs, it takes about a second to get the controllable in the simulator. Setting tasks to unspawned controllables provides unexpected results. - -- Therefore we schedule the functions to set the mission and options for the Controllable. - -- Controller.setTask( Controller, DCSTask ) - - if not WaitTime then - WaitTime = 1 - end - SCHEDULER:New( Controller, Controller.setTask, { DCSTask }, WaitTime ) - - return self - end - - return nil -end - - ---- Return a condition section for a controlled task. --- @param #CONTROLLABLE self --- @param DCSTime#Time time --- @param #string userFlag --- @param #boolean userFlagValue --- @param #string condition --- @param DCSTime#Time duration --- @param #number lastWayPoint --- return DCSTask#Task -function CONTROLLABLE:TaskCondition( time, userFlag, userFlagValue, condition, duration, lastWayPoint ) - self:F2( { time, userFlag, userFlagValue, condition, duration, lastWayPoint } ) - - local DCSStopCondition = {} - DCSStopCondition.time = time - DCSStopCondition.userFlag = userFlag - DCSStopCondition.userFlagValue = userFlagValue - DCSStopCondition.condition = condition - DCSStopCondition.duration = duration - DCSStopCondition.lastWayPoint = lastWayPoint - - self:T3( { DCSStopCondition } ) - return DCSStopCondition -end - ---- Return a Controlled Task taking a Task and a TaskCondition. --- @param #CONTROLLABLE self --- @param DCSTask#Task DCSTask --- @param #DCSStopCondition DCSStopCondition --- @return DCSTask#Task -function CONTROLLABLE:TaskControlled( DCSTask, DCSStopCondition ) - self:F2( { DCSTask, DCSStopCondition } ) - - local DCSTaskControlled - - DCSTaskControlled = { - id = 'ControlledTask', - params = { - task = DCSTask, - stopCondition = DCSStopCondition - } - } - - self:T3( { DCSTaskControlled } ) - return DCSTaskControlled -end - ---- Return a Combo Task taking an array of Tasks. --- @param #CONTROLLABLE self --- @param DCSTask#TaskArray DCSTasks Array of @{DCSTask#Task} --- @return DCSTask#Task -function CONTROLLABLE:TaskCombo( DCSTasks ) - self:F2( { DCSTasks } ) - - local DCSTaskCombo - - DCSTaskCombo = { - id = 'ComboTask', - params = { - tasks = DCSTasks - } - } - - self:T3( { DCSTaskCombo } ) - return DCSTaskCombo -end - ---- Return a WrappedAction Task taking a Command. --- @param #CONTROLLABLE self --- @param DCSCommand#Command DCSCommand --- @return DCSTask#Task -function CONTROLLABLE:TaskWrappedAction( DCSCommand, Index ) - self:F2( { DCSCommand } ) - - local DCSTaskWrappedAction - - DCSTaskWrappedAction = { - id = "WrappedAction", - enabled = true, - number = Index, - auto = false, - params = { - action = DCSCommand, - }, - } - - self:T3( { DCSTaskWrappedAction } ) - return DCSTaskWrappedAction -end - ---- Executes a command action --- @param #CONTROLLABLE self --- @param DCSCommand#Command DCSCommand --- @return #CONTROLLABLE self -function CONTROLLABLE:SetCommand( DCSCommand ) - self:F2( DCSCommand ) - - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - local Controller = self:_GetController() - Controller:setCommand( DCSCommand ) - return self - end - - return nil -end - ---- Perform a switch waypoint command --- @param #CONTROLLABLE self --- @param #number FromWayPoint --- @param #number ToWayPoint --- @return DCSTask#Task --- @usage --- --- This test demonstrates the use(s) of the SwitchWayPoint method of the GROUP class. --- HeliGroup = GROUP:FindByName( "Helicopter" ) --- --- --- Route the helicopter back to the FARP after 60 seconds. --- -- We use the SCHEDULER class to do this. --- SCHEDULER:New( nil, --- function( HeliGroup ) --- local CommandRTB = HeliGroup:CommandSwitchWayPoint( 2, 8 ) --- HeliGroup:SetCommand( CommandRTB ) --- end, { HeliGroup }, 90 --- ) -function CONTROLLABLE:CommandSwitchWayPoint( FromWayPoint, ToWayPoint ) - self:F2( { FromWayPoint, ToWayPoint } ) - - local CommandSwitchWayPoint = { - id = 'SwitchWaypoint', - params = { - fromWaypointIndex = FromWayPoint, - goToWaypointIndex = ToWayPoint, - }, - } - - self:T3( { CommandSwitchWayPoint } ) - return CommandSwitchWayPoint -end - ---- Perform stop route command --- @param #CONTROLLABLE self --- @param #boolean StopRoute --- @return DCSTask#Task -function CONTROLLABLE:CommandStopRoute( StopRoute, Index ) - self:F2( { StopRoute, Index } ) - - local CommandStopRoute = { - id = 'StopRoute', - params = { - value = StopRoute, - }, - } - - self:T3( { CommandStopRoute } ) - return CommandStopRoute -end - - --- TASKS FOR AIR CONTROLLABLES - - ---- (AIR) Attack a Controllable. --- @param #CONTROLLABLE self --- @param Controllable#CONTROLLABLE AttackGroup The Controllable to be attacked. --- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. --- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. --- @param DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. --- @param DCSTypes#Distance Altitude (optional) Desired attack start altitude. Controllable/aircraft will make its attacks from the altitude. If the altitude is too low or too high to use weapon aircraft/controllable will choose closest altitude to the desired attack start altitude. If the desired altitude is defined controllable/aircraft will not attack from safe altitude. --- @param #boolean AttackQtyLimit (optional) The flag determines how to interpret attackQty parameter. If the flag is true then attackQty is a limit on maximal attack quantity for "AttackControllable" and "AttackUnit" tasks. If the flag is false then attackQty is a desired attack quantity for "Bombing" and "BombingRunway" tasks. --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:TaskAttackGroup( AttackGroup, WeaponType, WeaponExpend, AttackQty, Direction, Altitude, AttackQtyLimit ) - self:F2( { self.ControllableName, AttackGroup, WeaponType, WeaponExpend, AttackQty, Direction, Altitude, AttackQtyLimit } ) - - -- AttackControllable = { - -- id = 'AttackControllable', - -- params = { - -- groupId = Group.ID, - -- weaponType = number, - -- expend = enum AI.Task.WeaponExpend, - -- attackQty = number, - -- directionEnabled = boolean, - -- direction = Azimuth, - -- altitudeEnabled = boolean, - -- altitude = Distance, - -- attackQtyLimit = boolean, - -- } - -- } - - local DirectionEnabled = nil - if Direction then - DirectionEnabled = true - end - - local AltitudeEnabled = nil - if Altitude then - AltitudeEnabled = true - end - - local DCSTask - DCSTask = { id = 'AttackControllable', - params = { - groupId = AttackGroup:GetID(), - weaponType = WeaponType, - expend = WeaponExpend, - attackQty = AttackQty, - directionEnabled = DirectionEnabled, - direction = Direction, - altitudeEnabled = AltitudeEnabled, - altitude = Altitude, - attackQtyLimit = AttackQtyLimit, - }, - }, - - self:T3( { DCSTask } ) - return DCSTask -end - - ---- (AIR) Attack the Unit. --- @param #CONTROLLABLE self --- @param Unit#UNIT AttackUnit The unit. --- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. --- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. --- @param DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. --- @param #boolean AttackQtyLimit (optional) The flag determines how to interpret attackQty parameter. If the flag is true then attackQty is a limit on maximal attack quantity for "AttackControllable" and "AttackUnit" tasks. If the flag is false then attackQty is a desired attack quantity for "Bombing" and "BombingRunway" tasks. --- @param #boolean ControllableAttack (optional) Flag indicates that the target must be engaged by all aircrafts of the controllable. Has effect only if the task is assigned to a controllable, not to a single aircraft. --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:TaskAttackUnit( AttackUnit, WeaponType, WeaponExpend, AttackQty, Direction, AttackQtyLimit, ControllableAttack ) - self:F2( { self.ControllableName, AttackUnit, WeaponType, WeaponExpend, AttackQty, Direction, AttackQtyLimit, ControllableAttack } ) - - -- AttackUnit = { - -- id = 'AttackUnit', - -- params = { - -- unitId = Unit.ID, - -- weaponType = number, - -- expend = enum AI.Task.WeaponExpend - -- attackQty = number, - -- direction = Azimuth, - -- attackQtyLimit = boolean, - -- controllableAttack = boolean, - -- } - -- } - - local DCSTask - DCSTask = { id = 'AttackUnit', - params = { - unitId = AttackUnit:GetID(), - weaponType = WeaponType, - expend = WeaponExpend, - attackQty = AttackQty, - direction = Direction, - attackQtyLimit = AttackQtyLimit, - controllableAttack = ControllableAttack, - }, - }, - - self:T3( { DCSTask } ) - return DCSTask -end - - ---- (AIR) Delivering weapon at the point on the ground. --- @param #CONTROLLABLE self --- @param DCSTypes#Vec2 Vec2 2D-coordinates of the point to deliver weapon at. --- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. --- @param #number AttackQty (optional) Desired quantity of passes. The parameter is not the same in AttackControllable and AttackUnit tasks. --- @param DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. --- @param #boolean ControllableAttack (optional) Flag indicates that the target must be engaged by all aircrafts of the controllable. Has effect only if the task is assigned to a controllable, not to a single aircraft. --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:TaskBombing( Vec2, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack ) - self:F2( { self.ControllableName, Vec2, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack } ) - --- Bombing = { --- id = 'Bombing', --- params = { --- point = Vec2, --- weaponType = number, --- expend = enum AI.Task.WeaponExpend, --- attackQty = number, --- direction = Azimuth, --- controllableAttack = boolean, --- } --- } - - local DCSTask - DCSTask = { id = 'Bombing', - params = { - point = Vec2, - weaponType = WeaponType, - expend = WeaponExpend, - attackQty = AttackQty, - direction = Direction, - controllableAttack = ControllableAttack, - }, - }, - - self:T3( { DCSTask } ) - return DCSTask -end - ---- (AIR) Orbit at a specified position at a specified alititude during a specified duration with a specified speed. --- @param #CONTROLLABLE self --- @param DCSTypes#Vec2 Point The point to hold the position. --- @param #number Altitude The altitude to hold the position. --- @param #number Speed The speed flying when holding the position. --- @return #CONTROLLABLE self -function CONTROLLABLE:TaskOrbitCircleAtVec2( Point, Altitude, Speed ) - self:F2( { self.ControllableName, Point, Altitude, Speed } ) - - -- pattern = enum AI.Task.OribtPattern, - -- point = Vec2, - -- point2 = Vec2, - -- speed = Distance, - -- altitude = Distance - - local LandHeight = land.getHeight( Point ) - - self:T3( { LandHeight } ) - - local DCSTask = { id = 'Orbit', - params = { pattern = AI.Task.OrbitPattern.CIRCLE, - point = Point, - speed = Speed, - altitude = Altitude + LandHeight - } - } - - - -- local AITask = { id = 'ControlledTask', - -- params = { task = { id = 'Orbit', - -- params = { pattern = AI.Task.OrbitPattern.CIRCLE, - -- point = Point, - -- speed = Speed, - -- altitude = Altitude + LandHeight - -- } - -- }, - -- stopCondition = { duration = Duration - -- } - -- } - -- } - -- ) - - return DCSTask -end - ---- (AIR) Orbit at the current position of the first unit of the controllable at a specified alititude. --- @param #CONTROLLABLE self --- @param #number Altitude The altitude to hold the position. --- @param #number Speed The speed flying when holding the position. --- @return #CONTROLLABLE self -function CONTROLLABLE:TaskOrbitCircle( Altitude, Speed ) - self:F2( { self.ControllableName, Altitude, Speed } ) - - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - local ControllablePoint = self:GetVec2() - return self:TaskOrbitCircleAtVec2( ControllablePoint, Altitude, Speed ) - end - - return nil -end - - - ---- (AIR) Hold position at the current position of the first unit of the controllable. --- @param #CONTROLLABLE self --- @param #number Duration The maximum duration in seconds to hold the position. --- @return #CONTROLLABLE self -function CONTROLLABLE:TaskHoldPosition() - self:F2( { self.ControllableName } ) - - return self:TaskOrbitCircle( 30, 10 ) -end - - - - ---- (AIR) Attacking the map object (building, structure, e.t.c). --- @param #CONTROLLABLE self --- @param DCSTypes#Vec2 Vec2 2D-coordinates of the point the map object is closest to. The distance between the point and the map object must not be greater than 2000 meters. Object id is not used here because Mission Editor doesn't support map object identificators. --- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. --- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. --- @param DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. --- @param #boolean ControllableAttack (optional) Flag indicates that the target must be engaged by all aircrafts of the controllable. Has effect only if the task is assigned to a controllable, not to a single aircraft. --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:TaskAttackMapObject( Vec2, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack ) - self:F2( { self.ControllableName, Vec2, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack } ) - --- AttackMapObject = { --- id = 'AttackMapObject', --- params = { --- point = Vec2, --- weaponType = number, --- expend = enum AI.Task.WeaponExpend, --- attackQty = number, --- direction = Azimuth, --- controllableAttack = boolean, --- } --- } - - local DCSTask - DCSTask = { id = 'AttackMapObject', - params = { - point = Vec2, - weaponType = WeaponType, - expend = WeaponExpend, - attackQty = AttackQty, - direction = Direction, - controllableAttack = ControllableAttack, - }, - }, - - self:T3( { DCSTask } ) - return DCSTask -end - - ---- (AIR) Delivering weapon on the runway. --- @param #CONTROLLABLE self --- @param Airbase#AIRBASE Airbase Airbase to attack. --- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. --- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. --- @param DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. --- @param #boolean ControllableAttack (optional) Flag indicates that the target must be engaged by all aircrafts of the controllable. Has effect only if the task is assigned to a controllable, not to a single aircraft. --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:TaskBombingRunway( Airbase, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack ) - self:F2( { self.ControllableName, Airbase, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack } ) - --- BombingRunway = { --- id = 'BombingRunway', --- params = { --- runwayId = AirdromeId, --- weaponType = number, --- expend = enum AI.Task.WeaponExpend, --- attackQty = number, --- direction = Azimuth, --- controllableAttack = boolean, --- } --- } - - local DCSTask - DCSTask = { id = 'BombingRunway', - params = { - point = Airbase:GetID(), - weaponType = WeaponType, - expend = WeaponExpend, - attackQty = AttackQty, - direction = Direction, - controllableAttack = ControllableAttack, - }, - }, - - self:T3( { DCSTask } ) - return DCSTask -end - - ---- (AIR) Refueling from the nearest tanker. No parameters. --- @param #CONTROLLABLE self --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:TaskRefueling() - self:F2( { self.ControllableName } ) - --- Refueling = { --- id = 'Refueling', --- params = {} --- } - - local DCSTask - DCSTask = { id = 'Refueling', - params = { - }, - }, - - self:T3( { DCSTask } ) - return DCSTask -end - - ---- (AIR HELICOPTER) Landing at the ground. For helicopters only. --- @param #CONTROLLABLE self --- @param DCSTypes#Vec2 Point The point where to land. --- @param #number Duration The duration in seconds to stay on the ground. --- @return #CONTROLLABLE self -function CONTROLLABLE:TaskLandAtVec2( Point, Duration ) - self:F2( { self.ControllableName, Point, Duration } ) - --- Land = { --- id= 'Land', --- params = { --- point = Vec2, --- durationFlag = boolean, --- duration = Time --- } --- } - - local DCSTask - if Duration and Duration > 0 then - DCSTask = { id = 'Land', - params = { - point = Point, - durationFlag = true, - duration = Duration, - }, - } - else - DCSTask = { id = 'Land', - params = { - point = Point, - durationFlag = false, - }, - } - end - - self:T3( DCSTask ) - return DCSTask -end - ---- (AIR) Land the controllable at a @{Zone#ZONE_RADIUS). --- @param #CONTROLLABLE self --- @param Zone#ZONE Zone The zone where to land. --- @param #number Duration The duration in seconds to stay on the ground. --- @return #CONTROLLABLE self -function CONTROLLABLE:TaskLandAtZone( Zone, Duration, RandomPoint ) - self:F2( { self.ControllableName, Zone, Duration, RandomPoint } ) - - local Point - if RandomPoint then - Point = Zone:GetRandomVec2() - else - Point = Zone:GetVec2() - end - - local DCSTask = self:TaskLandAtVec2( Point, Duration ) - - self:T3( DCSTask ) - return DCSTask -end - - - ---- (AIR) Following another airborne controllable. --- The unit / controllable will follow lead unit of another controllable, wingmens of both controllables will continue following their leaders. --- If another controllable is on land the unit / controllable will orbit around. --- @param #CONTROLLABLE self --- @param Controllable#CONTROLLABLE FollowControllable The controllable to be followed. --- @param DCSTypes#Vec3 Vec3 Position of the unit / lead unit of the controllable relative lead unit of another controllable in frame reference oriented by course of lead unit of another controllable. If another controllable is on land the unit / controllable will orbit around. --- @param #number LastWaypointIndex Detach waypoint of another controllable. Once reached the unit / controllable Follow task is finished. --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:TaskFollow( FollowControllable, Vec3, LastWaypointIndex ) - self:F2( { self.ControllableName, FollowControllable, Vec3, LastWaypointIndex } ) - --- Follow = { --- id = 'Follow', --- params = { --- groupId = Group.ID, --- pos = Vec3, --- lastWptIndexFlag = boolean, --- lastWptIndex = number --- } --- } - - local LastWaypointIndexFlag = false - if LastWaypointIndex then - LastWaypointIndexFlag = true - end - - local DCSTask - DCSTask = { - id = 'Follow', - params = { - groupId = FollowControllable:GetID(), - pos = Vec3, - lastWptIndexFlag = LastWaypointIndexFlag, - lastWptIndex = LastWaypointIndex - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - - ---- (AIR) Escort another airborne controllable. --- The unit / controllable will follow lead unit of another controllable, wingmens of both controllables will continue following their leaders. --- The unit / controllable will also protect that controllable from threats of specified types. --- @param #CONTROLLABLE self --- @param Controllable#CONTROLLABLE EscortControllable The controllable to be escorted. --- @param DCSTypes#Vec3 Vec3 Position of the unit / lead unit of the controllable relative lead unit of another controllable in frame reference oriented by course of lead unit of another controllable. If another controllable is on land the unit / controllable will orbit around. --- @param #number LastWaypointIndex Detach waypoint of another controllable. Once reached the unit / controllable Follow task is finished. --- @param #number EngagementDistanceMax Maximal distance from escorted controllable to threat. If the threat is already engaged by escort escort will disengage if the distance becomes greater than 1.5 * engagementDistMax. --- @param DCSTypes#AttributeNameArray TargetTypes Array of AttributeName that is contains threat categories allowed to engage. --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:TaskEscort( FollowControllable, Vec3, LastWaypointIndex, EngagementDistance, TargetTypes ) - self:F2( { self.ControllableName, FollowControllable, Vec3, LastWaypointIndex, EngagementDistance, TargetTypes } ) - --- Escort = { --- id = 'Escort', --- params = { --- groupId = Group.ID, --- pos = Vec3, --- lastWptIndexFlag = boolean, --- lastWptIndex = number, --- engagementDistMax = Distance, --- targetTypes = array of AttributeName, --- } --- } - - local LastWaypointIndexFlag = false - if LastWaypointIndex then - LastWaypointIndexFlag = true - end - - local DCSTask - DCSTask = { id = 'Escort', - params = { - groupId = FollowControllable:GetID(), - pos = Vec3, - lastWptIndexFlag = LastWaypointIndexFlag, - lastWptIndex = LastWaypointIndex, - engagementDistMax = EngagementDistance, - targetTypes = TargetTypes, - }, - }, - - self:T3( { DCSTask } ) - return DCSTask -end - - --- GROUND TASKS - ---- (GROUND) Fire at a VEC2 point until ammunition is finished. --- @param #CONTROLLABLE self --- @param DCSTypes#Vec2 Vec2 The point to fire at. --- @param DCSTypes#Distance Radius The radius of the zone to deploy the fire at. --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius ) - self:F2( { self.ControllableName, Vec2, Radius } ) - - -- FireAtPoint = { - -- id = 'FireAtPoint', - -- params = { - -- point = Vec2, - -- radius = Distance, - -- } - -- } - - local DCSTask - DCSTask = { id = 'FireAtPoint', - params = { - point = Vec2, - radius = Radius, - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - ---- (GROUND) Hold ground controllable from moving. --- @param #CONTROLLABLE self --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:TaskHold() - self:F2( { self.ControllableName } ) - --- Hold = { --- id = 'Hold', --- params = { --- } --- } - - local DCSTask - DCSTask = { id = 'Hold', - params = { - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - - --- TASKS FOR AIRBORNE AND GROUND UNITS/CONTROLLABLES - ---- (AIR + GROUND) The task makes the controllable/unit a FAC and orders the FAC to control the target (enemy ground controllable) destruction. --- The killer is player-controlled allied CAS-aircraft that is in contact with the FAC. --- If the task is assigned to the controllable lead unit will be a FAC. --- @param #CONTROLLABLE self --- @param Controllable#CONTROLLABLE AttackGroup Target CONTROLLABLE. --- @param #number WeaponType Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param DCSTypes#AI.Task.Designation Designation (optional) Designation type. --- @param #boolean Datalink (optional) Allows to use datalink to send the target information to attack aircraft. Enabled by default. --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:TaskFAC_AttackGroup( AttackGroup, WeaponType, Designation, Datalink ) - self:F2( { self.ControllableName, AttackGroup, WeaponType, Designation, Datalink } ) - --- FAC_AttackControllable = { --- id = 'FAC_AttackControllable', --- params = { --- groupId = Group.ID, --- weaponType = number, --- designation = enum AI.Task.Designation, --- datalink = boolean --- } --- } - - local DCSTask - DCSTask = { id = 'FAC_AttackControllable', - params = { - groupId = AttackGroup:GetID(), - weaponType = WeaponType, - designation = Designation, - datalink = Datalink, - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - --- EN-ROUTE TASKS FOR AIRBORNE CONTROLLABLES - ---- (AIR) Engaging targets of defined types. --- @param #CONTROLLABLE self --- @param DCSTypes#Distance Distance Maximal distance from the target to a route leg. If the target is on a greater distance it will be ignored. --- @param DCSTypes#AttributeNameArray TargetTypes Array of target categories allowed to engage. --- @param #number Priority All enroute tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:EnRouteTaskEngageTargets( Distance, TargetTypes, Priority ) - self:F2( { self.ControllableName, Distance, TargetTypes, Priority } ) - --- EngageTargets ={ --- id = 'EngageTargets', --- params = { --- maxDist = Distance, --- targetTypes = array of AttributeName, --- priority = number --- } --- } - - local DCSTask - DCSTask = { id = 'EngageTargets', - params = { - maxDist = Distance, - targetTypes = TargetTypes, - priority = Priority - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - - - ---- (AIR) Engaging a targets of defined types at circle-shaped zone. --- @param #CONTROLLABLE self --- @param DCSTypes#Vec2 Vec2 2D-coordinates of the zone. --- @param DCSTypes#Distance Radius Radius of the zone. --- @param DCSTypes#AttributeNameArray TargetTypes Array of target categories allowed to engage. --- @param #number Priority All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:EnRouteTaskEngageTargets( Vec2, Radius, TargetTypes, Priority ) - self:F2( { self.ControllableName, Vec2, Radius, TargetTypes, Priority } ) - --- EngageTargetsInZone = { --- id = 'EngageTargetsInZone', --- params = { --- point = Vec2, --- zoneRadius = Distance, --- targetTypes = array of AttributeName, --- priority = number --- } --- } - - local DCSTask - DCSTask = { id = 'EngageTargetsInZone', - params = { - point = Vec2, - zoneRadius = Radius, - targetTypes = TargetTypes, - priority = Priority - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - - ---- (AIR) Engaging a controllable. The task does not assign the target controllable to the unit/controllable to attack now; it just allows the unit/controllable to engage the target controllable as well as other assigned targets. --- @param #CONTROLLABLE self --- @param Controllable#CONTROLLABLE AttackGroup The Controllable to be attacked. --- @param #number Priority All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. --- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. --- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. --- @param DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. --- @param DCSTypes#Distance Altitude (optional) Desired attack start altitude. Controllable/aircraft will make its attacks from the altitude. If the altitude is too low or too high to use weapon aircraft/controllable will choose closest altitude to the desired attack start altitude. If the desired altitude is defined controllable/aircraft will not attack from safe altitude. --- @param #boolean AttackQtyLimit (optional) The flag determines how to interpret attackQty parameter. If the flag is true then attackQty is a limit on maximal attack quantity for "AttackControllable" and "AttackUnit" tasks. If the flag is false then attackQty is a desired attack quantity for "Bombing" and "BombingRunway" tasks. --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:EnRouteTaskEngageGroup( AttackGroup, Priority, WeaponType, WeaponExpend, AttackQty, Direction, Altitude, AttackQtyLimit ) - self:F2( { self.ControllableName, AttackGroup, Priority, WeaponType, WeaponExpend, AttackQty, Direction, Altitude, AttackQtyLimit } ) - - -- EngageControllable = { - -- id = 'EngageControllable ', - -- params = { - -- groupId = Group.ID, - -- weaponType = number, - -- expend = enum AI.Task.WeaponExpend, - -- attackQty = number, - -- directionEnabled = boolean, - -- direction = Azimuth, - -- altitudeEnabled = boolean, - -- altitude = Distance, - -- attackQtyLimit = boolean, - -- priority = number, - -- } - -- } - - local DirectionEnabled = nil - if Direction then - DirectionEnabled = true - end - - local AltitudeEnabled = nil - if Altitude then - AltitudeEnabled = true - end - - local DCSTask - DCSTask = { id = 'EngageControllable', - params = { - groupId = AttackGroup:GetID(), - weaponType = WeaponType, - expend = WeaponExpend, - attackQty = AttackQty, - directionEnabled = DirectionEnabled, - direction = Direction, - altitudeEnabled = AltitudeEnabled, - altitude = Altitude, - attackQtyLimit = AttackQtyLimit, - priority = Priority, - }, - }, - - self:T3( { DCSTask } ) - return DCSTask -end - - ---- (AIR) Attack the Unit. --- @param #CONTROLLABLE self --- @param Unit#UNIT AttackUnit The UNIT. --- @param #number Priority All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. --- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. --- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. --- @param DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. --- @param #boolean AttackQtyLimit (optional) The flag determines how to interpret attackQty parameter. If the flag is true then attackQty is a limit on maximal attack quantity for "AttackControllable" and "AttackUnit" tasks. If the flag is false then attackQty is a desired attack quantity for "Bombing" and "BombingRunway" tasks. --- @param #boolean ControllableAttack (optional) Flag indicates that the target must be engaged by all aircrafts of the controllable. Has effect only if the task is assigned to a controllable, not to a single aircraft. --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:EnRouteTaskEngageUnit( AttackUnit, Priority, WeaponType, WeaponExpend, AttackQty, Direction, AttackQtyLimit, ControllableAttack ) - self:F2( { self.ControllableName, AttackUnit, Priority, WeaponType, WeaponExpend, AttackQty, Direction, AttackQtyLimit, ControllableAttack } ) - - -- EngageUnit = { - -- id = 'EngageUnit', - -- params = { - -- unitId = Unit.ID, - -- weaponType = number, - -- expend = enum AI.Task.WeaponExpend - -- attackQty = number, - -- direction = Azimuth, - -- attackQtyLimit = boolean, - -- controllableAttack = boolean, - -- priority = number, - -- } - -- } - - local DCSTask - DCSTask = { id = 'EngageUnit', - params = { - unitId = AttackUnit:GetID(), - weaponType = WeaponType, - expend = WeaponExpend, - attackQty = AttackQty, - direction = Direction, - attackQtyLimit = AttackQtyLimit, - controllableAttack = ControllableAttack, - priority = Priority, - }, - }, - - self:T3( { DCSTask } ) - return DCSTask -end - - - ---- (AIR) Aircraft will act as an AWACS for friendly units (will provide them with information about contacts). No parameters. --- @param #CONTROLLABLE self --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:EnRouteTaskAWACS( ) - self:F2( { self.ControllableName } ) - --- AWACS = { --- id = 'AWACS', --- params = { --- } --- } - - local DCSTask - DCSTask = { id = 'AWACS', - params = { - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - - ---- (AIR) Aircraft will act as a tanker for friendly units. No parameters. --- @param #CONTROLLABLE self --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:EnRouteTaskTanker( ) - self:F2( { self.ControllableName } ) - --- Tanker = { --- id = 'Tanker', --- params = { --- } --- } - - local DCSTask - DCSTask = { id = 'Tanker', - params = { - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - - --- En-route tasks for ground units/controllables - ---- (GROUND) Ground unit (EW-radar) will act as an EWR for friendly units (will provide them with information about contacts). No parameters. --- @param #CONTROLLABLE self --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:EnRouteTaskEWR( ) - self:F2( { self.ControllableName } ) - --- EWR = { --- id = 'EWR', --- params = { --- } --- } - - local DCSTask - DCSTask = { id = 'EWR', - params = { - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - - --- En-route tasks for airborne and ground units/controllables - ---- (AIR + GROUND) The task makes the controllable/unit a FAC and lets the FAC to choose the target (enemy ground controllable) as well as other assigned targets. --- The killer is player-controlled allied CAS-aircraft that is in contact with the FAC. --- If the task is assigned to the controllable lead unit will be a FAC. --- @param #CONTROLLABLE self --- @param Controllable#CONTROLLABLE AttackGroup Target CONTROLLABLE. --- @param #number Priority All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. --- @param #number WeaponType Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param DCSTypes#AI.Task.Designation Designation (optional) Designation type. --- @param #boolean Datalink (optional) Allows to use datalink to send the target information to attack aircraft. Enabled by default. --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:EnRouteTaskFAC_EngageGroup( AttackGroup, Priority, WeaponType, Designation, Datalink ) - self:F2( { self.ControllableName, AttackGroup, WeaponType, Priority, Designation, Datalink } ) - --- FAC_EngageControllable = { --- id = 'FAC_EngageControllable', --- params = { --- groupId = Group.ID, --- weaponType = number, --- designation = enum AI.Task.Designation, --- datalink = boolean, --- priority = number, --- } --- } - - local DCSTask - DCSTask = { id = 'FAC_EngageControllable', - params = { - groupId = AttackGroup:GetID(), - weaponType = WeaponType, - designation = Designation, - datalink = Datalink, - priority = Priority, - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - - ---- (AIR + GROUND) The task makes the controllable/unit a FAC and lets the FAC to choose a targets (enemy ground controllable) around as well as other assigned targets. --- The killer is player-controlled allied CAS-aircraft that is in contact with the FAC. --- If the task is assigned to the controllable lead unit will be a FAC. --- @param #CONTROLLABLE self --- @param DCSTypes#Distance Radius The maximal distance from the FAC to a target. --- @param #number Priority All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:EnRouteTaskFAC( Radius, Priority ) - self:F2( { self.ControllableName, Radius, Priority } ) - --- FAC = { --- id = 'FAC', --- params = { --- radius = Distance, --- priority = number --- } --- } - - local DCSTask - DCSTask = { id = 'FAC', - params = { - radius = Radius, - priority = Priority - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - - - - ---- (AIR) Move the controllable to a Vec2 Point, wait for a defined duration and embark a controllable. --- @param #CONTROLLABLE self --- @param DCSTypes#Vec2 Point The point where to wait. --- @param #number Duration The duration in seconds to wait. --- @param #CONTROLLABLE EmbarkingControllable The controllable to be embarked. --- @return DCSTask#Task The DCS task structure -function CONTROLLABLE:TaskEmbarking( Point, Duration, EmbarkingControllable ) - self:F2( { self.ControllableName, Point, Duration, EmbarkingControllable.DCSControllable } ) - - local DCSTask - DCSTask = { id = 'Embarking', - params = { x = Point.x, - y = Point.y, - duration = Duration, - controllablesForEmbarking = { EmbarkingControllable.ControllableID }, - durationFlag = true, - distributionFlag = false, - distribution = {}, - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - ---- (GROUND) Embark to a Transport landed at a location. - ---- Move to a defined Vec2 Point, and embark to a controllable when arrived within a defined Radius. --- @param #CONTROLLABLE self --- @param DCSTypes#Vec2 Point The point where to wait. --- @param #number Radius The radius of the embarking zone around the Point. --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:TaskEmbarkToTransport( Point, Radius ) - self:F2( { self.ControllableName, Point, Radius } ) - - local DCSTask --DCSTask#Task - DCSTask = { id = 'EmbarkToTransport', - params = { x = Point.x, - y = Point.y, - zoneRadius = Radius, - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - - - ---- (AIR + GROUND) Return a mission task from a mission template. --- @param #CONTROLLABLE self --- @param #table TaskMission A table containing the mission task. --- @return DCSTask#Task -function CONTROLLABLE:TaskMission( TaskMission ) - self:F2( Points ) - - local DCSTask - DCSTask = { id = 'Mission', params = { TaskMission, }, } - - self:T3( { DCSTask } ) - return DCSTask -end - ---- Return a Misson task to follow a given route defined by Points. --- @param #CONTROLLABLE self --- @param #table Points A table of route points. --- @return DCSTask#Task -function CONTROLLABLE:TaskRoute( Points ) - self:F2( Points ) - - local DCSTask - DCSTask = { id = 'Mission', params = { route = { points = Points, }, }, } - - self:T3( { DCSTask } ) - return DCSTask -end - ---- (AIR + GROUND) Make the Controllable move to fly to a given point. --- @param #CONTROLLABLE self --- @param DCSTypes#Vec3 Point The destination point in Vec3 format. --- @param #number Speed The speed to travel. --- @return #CONTROLLABLE self -function CONTROLLABLE:TaskRouteToVec2( Point, Speed ) - self:F2( { Point, Speed } ) - - local ControllablePoint = self:GetUnit( 1 ):GetVec2() - - local PointFrom = {} - PointFrom.x = ControllablePoint.x - PointFrom.y = ControllablePoint.y - PointFrom.type = "Turning Point" - PointFrom.action = "Turning Point" - PointFrom.speed = Speed - PointFrom.speed_locked = true - PointFrom.properties = { - ["vnav"] = 1, - ["scale"] = 0, - ["angle"] = 0, - ["vangle"] = 0, - ["steer"] = 2, - } - - - local PointTo = {} - PointTo.x = Point.x - PointTo.y = Point.y - PointTo.type = "Turning Point" - PointTo.action = "Fly Over Point" - PointTo.speed = Speed - PointTo.speed_locked = true - PointTo.properties = { - ["vnav"] = 1, - ["scale"] = 0, - ["angle"] = 0, - ["vangle"] = 0, - ["steer"] = 2, - } - - - local Points = { PointFrom, PointTo } - - self:T3( Points ) - - self:Route( Points ) - - return self -end - ---- (AIR + GROUND) Make the Controllable move to a given point. --- @param #CONTROLLABLE self --- @param DCSTypes#Vec3 Point The destination point in Vec3 format. --- @param #number Speed The speed to travel. --- @return #CONTROLLABLE self -function CONTROLLABLE:TaskRouteToVec3( Point, Speed ) - self:F2( { Point, Speed } ) - - local ControllableVec3 = self:GetUnit( 1 ):GetVec3() - - local PointFrom = {} - PointFrom.x = ControllableVec3.x - PointFrom.y = ControllableVec3.z - PointFrom.alt = ControllableVec3.y - PointFrom.alt_type = "BARO" - PointFrom.type = "Turning Point" - PointFrom.action = "Turning Point" - PointFrom.speed = Speed - PointFrom.speed_locked = true - PointFrom.properties = { - ["vnav"] = 1, - ["scale"] = 0, - ["angle"] = 0, - ["vangle"] = 0, - ["steer"] = 2, - } - - - local PointTo = {} - PointTo.x = Point.x - PointTo.y = Point.z - PointTo.alt = Point.y - PointTo.alt_type = "BARO" - PointTo.type = "Turning Point" - PointTo.action = "Fly Over Point" - PointTo.speed = Speed - PointTo.speed_locked = true - PointTo.properties = { - ["vnav"] = 1, - ["scale"] = 0, - ["angle"] = 0, - ["vangle"] = 0, - ["steer"] = 2, - } - - - local Points = { PointFrom, PointTo } - - self:T3( Points ) - - self:Route( Points ) - - return self -end - - - ---- Make the controllable to follow a given route. --- @param #CONTROLLABLE self --- @param #table GoPoints A table of Route Points. --- @return #CONTROLLABLE self -function CONTROLLABLE:Route( GoPoints ) - self:F2( GoPoints ) - - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - local Points = routines.utils.deepCopy( GoPoints ) - local MissionTask = { id = 'Mission', params = { route = { points = Points, }, }, } - local Controller = self:_GetController() - --Controller.setTask( Controller, MissionTask ) - SCHEDULER:New( Controller, Controller.setTask, { MissionTask }, 1 ) - return self - end - - return nil -end - - - ---- (AIR + GROUND) Route the controllable to a given zone. --- The controllable final destination point can be randomized. --- A speed can be given in km/h. --- A given formation can be given. --- @param #CONTROLLABLE self --- @param Zone#ZONE Zone The zone where to route to. --- @param #boolean Randomize Defines whether to target point gets randomized within the Zone. --- @param #number Speed The speed. --- @param Base#FORMATION Formation The formation string. -function CONTROLLABLE:TaskRouteToZone( Zone, Randomize, Speed, Formation ) - self:F2( Zone ) - - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - - local ControllablePoint = self:GetVec2() - - local PointFrom = {} - PointFrom.x = ControllablePoint.x - PointFrom.y = ControllablePoint.y - PointFrom.type = "Turning Point" - PointFrom.action = "Cone" - PointFrom.speed = 20 / 1.6 - - - local PointTo = {} - local ZonePoint - - if Randomize then - ZonePoint = Zone:GetRandomVec2() - else - ZonePoint = Zone:GetVec2() - end - - PointTo.x = ZonePoint.x - PointTo.y = ZonePoint.y - PointTo.type = "Turning Point" - - if Formation then - PointTo.action = Formation - else - PointTo.action = "Cone" - end - - if Speed then - PointTo.speed = Speed - else - PointTo.speed = 20 / 1.6 - end - - local Points = { PointFrom, PointTo } - - self:T3( Points ) - - self:Route( Points ) - - return self - end - - return nil -end - ---- (AIR) Return the Controllable to an @{Airbase#AIRBASE} --- A speed can be given in km/h. --- A given formation can be given. --- @param #CONTROLLABLE self --- @param Airbase#AIRBASE ReturnAirbase The @{Airbase#AIRBASE} to return to. --- @param #number Speed (optional) The speed. --- @return #string The route -function CONTROLLABLE:RouteReturnToAirbase( ReturnAirbase, Speed ) - self:F2( { ReturnAirbase, Speed } ) - --- Example --- [4] = --- { --- ["alt"] = 45, --- ["type"] = "Land", --- ["action"] = "Landing", --- ["alt_type"] = "BARO", --- ["formation_template"] = "", --- ["properties"] = --- { --- ["vnav"] = 1, --- ["scale"] = 0, --- ["angle"] = 0, --- ["vangle"] = 0, --- ["steer"] = 2, --- }, -- end of ["properties"] --- ["ETA"] = 527.81058817743, --- ["airdromeId"] = 12, --- ["y"] = 243127.2973737, --- ["x"] = -5406.2803440839, --- ["name"] = "DictKey_WptName_53", --- ["speed"] = 138.88888888889, --- ["ETA_locked"] = false, --- ["task"] = --- { --- ["id"] = "ComboTask", --- ["params"] = --- { --- ["tasks"] = --- { --- }, -- end of ["tasks"] --- }, -- end of ["params"] --- }, -- end of ["task"] --- ["speed_locked"] = true, --- }, -- end of [4] - - - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - - local ControllablePoint = self:GetVec2() - local ControllableVelocity = self:GetMaxVelocity() - - local PointFrom = {} - PointFrom.x = ControllablePoint.x - PointFrom.y = ControllablePoint.y - PointFrom.type = "Turning Point" - PointFrom.action = "Turning Point" - PointFrom.speed = ControllableVelocity - - - local PointTo = {} - local AirbasePoint = ReturnAirbase:GetVec2() - - PointTo.x = AirbasePoint.x - PointTo.y = AirbasePoint.y - PointTo.type = "Land" - PointTo.action = "Landing" - PointTo.airdromeId = ReturnAirbase:GetID()-- Airdrome ID - self:T(PointTo.airdromeId) - --PointTo.alt = 0 - - local Points = { PointFrom, PointTo } - - self:T3( Points ) - - local Route = { points = Points, } - - return Route - end - - return nil -end - --- Commands - ---- Do Script command --- @param #CONTROLLABLE self --- @param #string DoScript --- @return #DCSCommand -function CONTROLLABLE:CommandDoScript( DoScript ) - - local DCSDoScript = { - id = "Script", - params = { - command = DoScript, - }, - } - - self:T3( DCSDoScript ) - return DCSDoScript -end - - ---- Return the mission template of the controllable. --- @param #CONTROLLABLE self --- @return #table The MissionTemplate --- TODO: Rework the method how to retrieve a template ... -function CONTROLLABLE:GetTaskMission() - self:F2( self.ControllableName ) - - return routines.utils.deepCopy( _DATABASE.Templates.Controllables[self.ControllableName].Template ) -end - ---- Return the mission route of the controllable. --- @param #CONTROLLABLE self --- @return #table The mission route defined by points. -function CONTROLLABLE:GetTaskRoute() - self:F2( self.ControllableName ) - - return routines.utils.deepCopy( _DATABASE.Templates.Controllables[self.ControllableName].Template.route.points ) -end - ---- Return the route of a controllable by using the @{Database#DATABASE} class. --- @param #CONTROLLABLE self --- @param #number Begin The route point from where the copy will start. The base route point is 0. --- @param #number End The route point where the copy will end. The End point is the last point - the End point. The last point has base 0. --- @param #boolean Randomize Randomization of the route, when true. --- @param #number Radius When randomization is on, the randomization is within the radius. -function CONTROLLABLE:CopyRoute( Begin, End, Randomize, Radius ) - self:F2( { Begin, End } ) - - local Points = {} - - -- Could be a Spawned Controllable - local ControllableName = string.match( self:GetName(), ".*#" ) - if ControllableName then - ControllableName = ControllableName:sub( 1, -2 ) - else - ControllableName = self:GetName() - end - - self:T3( { ControllableName } ) - - local Template = _DATABASE.Templates.Controllables[ControllableName].Template - - if Template then - if not Begin then - Begin = 0 - end - if not End then - End = 0 - end - - for TPointID = Begin + 1, #Template.route.points - End do - if Template.route.points[TPointID] then - Points[#Points+1] = routines.utils.deepCopy( Template.route.points[TPointID] ) - if Randomize then - if not Radius then - Radius = 500 - end - Points[#Points].x = Points[#Points].x + math.random( Radius * -1, Radius ) - Points[#Points].y = Points[#Points].y + math.random( Radius * -1, Radius ) - end - end - end - return Points - else - error( "Template not found for Controllable : " .. ControllableName ) - end - - return nil -end - - ---- Return the detected targets of the controllable. --- The optional parametes specify the detection methods that can be applied. --- If no detection method is given, the detection will use all the available methods by default. --- @param Controllable#CONTROLLABLE self --- @param #boolean DetectVisual (optional) --- @param #boolean DetectOptical (optional) --- @param #boolean DetectRadar (optional) --- @param #boolean DetectIRST (optional) --- @param #boolean DetectRWR (optional) --- @param #boolean DetectDLINK (optional) --- @return #table DetectedTargets -function CONTROLLABLE:GetDetectedTargets( DetectVisual, DetectOptical, DetectRadar, DetectIRST, DetectRWR, DetectDLINK ) - self:F2( self.ControllableName ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - local DetectionVisual = ( DetectVisual and DetectVisual == true ) and Controller.Detection.VISUAL or nil - local DetectionOptical = ( DetectOptical and DetectOptical == true ) and Controller.Detection.OPTICAL or nil - local DetectionRadar = ( DetectRadar and DetectRadar == true ) and Controller.Detection.RADAR or nil - local DetectionIRST = ( DetectIRST and DetectIRST == true ) and Controller.Detection.IRST or nil - local DetectionRWR = ( DetectRWR and DetectRWR == true ) and Controller.Detection.RWR or nil - local DetectionDLINK = ( DetectDLINK and DetectDLINK == true ) and Controller.Detection.DLINK or nil - - - return self:_GetController():getDetectedTargets( DetectionVisual, DetectionOptical, DetectionRadar, DetectionIRST, DetectionRWR, DetectionDLINK ) - end - - return nil -end - -function CONTROLLABLE:IsTargetDetected( DCSObject ) - self:F2( self.ControllableName ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - - local TargetIsDetected, TargetIsVisible, TargetLastTime, TargetKnowType, TargetKnowDistance, TargetLastPos, TargetLastVelocity - = self:_GetController().isTargetDetected( self:_GetController(), DCSObject, - Controller.Detection.VISUAL, - Controller.Detection.OPTIC, - Controller.Detection.RADAR, - Controller.Detection.IRST, - Controller.Detection.RWR, - Controller.Detection.DLINK - ) - return TargetIsDetected, TargetIsVisible, TargetLastTime, TargetKnowType, TargetKnowDistance, TargetLastPos, TargetLastVelocity - end - - return nil -end - --- Options - ---- Can the CONTROLLABLE hold their weapons? --- @param #CONTROLLABLE self --- @return #boolean -function CONTROLLABLE:OptionROEHoldFirePossible() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - if self:IsAir() or self:IsGround() or self:IsShip() then - return true - end - - return false - end - - return nil -end - ---- Holding weapons. --- @param Controllable#CONTROLLABLE self --- @return Controllable#CONTROLLABLE self -function CONTROLLABLE:OptionROEHoldFire() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - local Controller = self:_GetController() - - if self:IsAir() then - Controller:setOption( AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.WEAPON_HOLD ) - elseif self:IsGround() then - Controller:setOption( AI.Option.Ground.id.ROE, AI.Option.Ground.val.ROE.WEAPON_HOLD ) - elseif self:IsShip() then - Controller:setOption( AI.Option.Naval.id.ROE, AI.Option.Naval.val.ROE.WEAPON_HOLD ) - end - - return self - end - - return nil -end - ---- Can the CONTROLLABLE attack returning on enemy fire? --- @param #CONTROLLABLE self --- @return #boolean -function CONTROLLABLE:OptionROEReturnFirePossible() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - if self:IsAir() or self:IsGround() or self:IsShip() then - return true - end - - return false - end - - return nil -end - ---- Return fire. --- @param #CONTROLLABLE self --- @return #CONTROLLABLE self -function CONTROLLABLE:OptionROEReturnFire() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - local Controller = self:_GetController() - - if self:IsAir() then - Controller:setOption( AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.RETURN_FIRE ) - elseif self:IsGround() then - Controller:setOption( AI.Option.Ground.id.ROE, AI.Option.Ground.val.ROE.RETURN_FIRE ) - elseif self:IsShip() then - Controller:setOption( AI.Option.Naval.id.ROE, AI.Option.Naval.val.ROE.RETURN_FIRE ) - end - - return self - end - - return nil -end - ---- Can the CONTROLLABLE attack designated targets? --- @param #CONTROLLABLE self --- @return #boolean -function CONTROLLABLE:OptionROEOpenFirePossible() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - if self:IsAir() or self:IsGround() or self:IsShip() then - return true - end - - return false - end - - return nil -end - ---- Openfire. --- @param #CONTROLLABLE self --- @return #CONTROLLABLE self -function CONTROLLABLE:OptionROEOpenFire() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - local Controller = self:_GetController() - - if self:IsAir() then - Controller:setOption( AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.OPEN_FIRE ) - elseif self:IsGround() then - Controller:setOption( AI.Option.Ground.id.ROE, AI.Option.Ground.val.ROE.OPEN_FIRE ) - elseif self:IsShip() then - Controller:setOption( AI.Option.Naval.id.ROE, AI.Option.Naval.val.ROE.OPEN_FIRE ) - end - - return self - end - - return nil -end - ---- Can the CONTROLLABLE attack targets of opportunity? --- @param #CONTROLLABLE self --- @return #boolean -function CONTROLLABLE:OptionROEWeaponFreePossible() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - if self:IsAir() then - return true - end - - return false - end - - return nil -end - ---- Weapon free. --- @param #CONTROLLABLE self --- @return #CONTROLLABLE self -function CONTROLLABLE:OptionROEWeaponFree() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - local Controller = self:_GetController() - - if self:IsAir() then - Controller:setOption( AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.WEAPON_FREE ) - end - - return self - end - - return nil -end - ---- Can the CONTROLLABLE ignore enemy fire? --- @param #CONTROLLABLE self --- @return #boolean -function CONTROLLABLE:OptionROTNoReactionPossible() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - if self:IsAir() then - return true - end - - return false - end - - return nil -end - - ---- No evasion on enemy threats. --- @param #CONTROLLABLE self --- @return #CONTROLLABLE self -function CONTROLLABLE:OptionROTNoReaction() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - local Controller = self:_GetController() - - if self:IsAir() then - Controller:setOption( AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.NO_REACTION ) - end - - return self - end - - return nil -end - ---- Can the CONTROLLABLE evade using passive defenses? --- @param #CONTROLLABLE self --- @return #boolean -function CONTROLLABLE:OptionROTPassiveDefensePossible() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - if self:IsAir() then - return true - end - - return false - end - - return nil -end - ---- Evasion passive defense. --- @param #CONTROLLABLE self --- @return #CONTROLLABLE self -function CONTROLLABLE:OptionROTPassiveDefense() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - local Controller = self:_GetController() - - if self:IsAir() then - Controller:setOption( AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.PASSIVE_DEFENCE ) - end - - return self - end - - return nil -end - ---- Can the CONTROLLABLE evade on enemy fire? --- @param #CONTROLLABLE self --- @return #boolean -function CONTROLLABLE:OptionROTEvadeFirePossible() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - if self:IsAir() then - return true - end - - return false - end - - return nil -end - - ---- Evade on fire. --- @param #CONTROLLABLE self --- @return #CONTROLLABLE self -function CONTROLLABLE:OptionROTEvadeFire() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - local Controller = self:_GetController() - - if self:IsAir() then - Controller:setOption( AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.EVADE_FIRE ) - end - - return self - end - - return nil -end - ---- Can the CONTROLLABLE evade on fire using vertical manoeuvres? --- @param #CONTROLLABLE self --- @return #boolean -function CONTROLLABLE:OptionROTVerticalPossible() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - if self:IsAir() then - return true - end - - return false - end - - return nil -end - - ---- Evade on fire using vertical manoeuvres. --- @param #CONTROLLABLE self --- @return #CONTROLLABLE self -function CONTROLLABLE:OptionROTVertical() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - local Controller = self:_GetController() - - if self:IsAir() then - Controller:setOption( AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.BYPASS_AND_ESCAPE ) - end - - return self - end - - return nil -end - ---- Retrieve the controllable mission and allow to place function hooks within the mission waypoint plan. --- Use the method @{Controllable#CONTROLLABLE:WayPointFunction} to define the hook functions for specific waypoints. --- Use the method @{Controllable@CONTROLLABLE:WayPointExecute) to start the execution of the new mission plan. --- Note that when WayPointInitialize is called, the Mission of the controllable is RESTARTED! --- @param #CONTROLLABLE self --- @param #table WayPoints If WayPoints is given, then use the route. --- @return #CONTROLLABLE -function CONTROLLABLE:WayPointInitialize( WayPoints ) - self:F( { WayPoint, WayPointIndex, WayPointFunction } ) - - if WayPoints then - self.WayPoints = WayPoints - else - self.WayPoints = self:GetTaskRoute() - end - - return self -end - - ---- Registers a waypoint function that will be executed when the controllable moves over the WayPoint. --- @param #CONTROLLABLE self --- @param #number WayPoint The waypoint number. Note that the start waypoint on the route is WayPoint 1! --- @param #number WayPointIndex When defining multiple WayPoint functions for one WayPoint, use WayPointIndex to set the sequence of actions. --- @param #function WayPointFunction The waypoint function to be called when the controllable moves over the waypoint. The waypoint function takes variable parameters. --- @return #CONTROLLABLE -function CONTROLLABLE:WayPointFunction( WayPoint, WayPointIndex, WayPointFunction, ... ) - self:F2( { WayPoint, WayPointIndex, WayPointFunction } ) - - table.insert( self.WayPoints[WayPoint].task.params.tasks, WayPointIndex ) - self.WayPoints[WayPoint].task.params.tasks[WayPointIndex] = self:TaskFunction( WayPoint, WayPointIndex, WayPointFunction, arg ) - return self -end - - -function CONTROLLABLE:TaskFunction( WayPoint, WayPointIndex, FunctionString, FunctionArguments ) - self:F2( { WayPoint, WayPointIndex, FunctionString, FunctionArguments } ) - - local DCSTask - - local DCSScript = {} - DCSScript[#DCSScript+1] = "local MissionControllable = CONTROLLABLE:Find( ... ) " - - if FunctionArguments and #FunctionArguments > 0 then - DCSScript[#DCSScript+1] = FunctionString .. "( MissionControllable, " .. table.concat( FunctionArguments, "," ) .. ")" - else - DCSScript[#DCSScript+1] = FunctionString .. "( MissionControllable )" - end - - DCSTask = self:TaskWrappedAction( - self:CommandDoScript( - table.concat( DCSScript ) - ), WayPointIndex - ) - - self:T3( DCSTask ) - - return DCSTask - -end - ---- Executes the WayPoint plan. --- The function gets a WayPoint parameter, that you can use to restart the mission at a specific WayPoint. --- Note that when the WayPoint parameter is used, the new start mission waypoint of the controllable will be 1! --- @param #CONTROLLABLE self --- @param #number WayPoint The WayPoint from where to execute the mission. --- @param #number WaitTime The amount seconds to wait before initiating the mission. --- @return #CONTROLLABLE -function CONTROLLABLE:WayPointExecute( WayPoint, WaitTime ) - self:F( { WayPoint, WaitTime } ) - - if not WayPoint then - WayPoint = 1 - end - - -- When starting the mission from a certain point, the TaskPoints need to be deleted before the given WayPoint. - for TaskPointID = 1, WayPoint - 1 do - table.remove( self.WayPoints, 1 ) - end - - self:T3( self.WayPoints ) - - self:SetTask( self:TaskRoute( self.WayPoints ), WaitTime ) - - return self -end - --- Message APIs - ---- Returns a message with the callsign embedded (if there is one). --- @param #CONTROLLABLE self --- @param #string Message The message text --- @param DCSTypes#Duration Duration The duration of the message. --- @return Message#MESSAGE -function CONTROLLABLE:GetMessage( Message, Duration ) - - local DCSObject = self:GetDCSObject() - if DCSObject then - return MESSAGE:New( Message, Duration, self:GetCallsign() .. " (" .. self:GetTypeName() .. ")" ) - end - - return nil -end - ---- Send a message to all coalitions. --- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. --- @param #CONTROLLABLE self --- @param #string Message The message text --- @param DCSTypes#Duration Duration The duration of the message. -function CONTROLLABLE:MessageToAll( Message, Duration ) - self:F2( { Message, Duration } ) - - local DCSObject = self:GetDCSObject() - if DCSObject then - self:GetMessage( Message, Duration ):ToAll() - end - - return nil -end - ---- Send a message to the red coalition. --- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. --- @param #CONTROLLABLE self --- @param #string Message The message text --- @param DCSTYpes#Duration Duration The duration of the message. -function CONTROLLABLE:MessageToRed( Message, Duration ) - self:F2( { Message, Duration } ) - - local DCSObject = self:GetDCSObject() - if DCSObject then - self:GetMessage( Message, Duration ):ToRed() - end - - return nil -end - ---- Send a message to the blue coalition. --- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. --- @param #CONTROLLABLE self --- @param #string Message The message text --- @param DCSTypes#Duration Duration The duration of the message. -function CONTROLLABLE:MessageToBlue( Message, Duration ) - self:F2( { Message, Duration } ) - - local DCSObject = self:GetDCSObject() - if DCSObject then - self:GetMessage( Message, Duration ):ToBlue() - end - - return nil -end - ---- Send a message to a client. --- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. --- @param #CONTROLLABLE self --- @param #string Message The message text --- @param DCSTypes#Duration Duration The duration of the message. --- @param Client#CLIENT Client The client object receiving the message. -function CONTROLLABLE:MessageToClient( Message, Duration, Client ) - self:F2( { Message, Duration } ) - - local DCSObject = self:GetDCSObject() - if DCSObject then - self:GetMessage( Message, Duration ):ToClient( Client ) - end - - return nil -end - ---- Send a message to a @{Group}. --- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. --- @param #CONTROLLABLE self --- @param #string Message The message text --- @param DCSTypes#Duration Duration The duration of the message. --- @param Group#GROUP MessageGroup The GROUP object receiving the message. -function CONTROLLABLE:MessageToGroup( Message, Duration, MessageGroup ) - self:F2( { Message, Duration } ) - - local DCSObject = self:GetDCSObject() - if DCSObject then - if DCSObject:isExist() then - self:GetMessage( Message, Duration ):ToGroup( MessageGroup ) - end - end - - return nil -end - ---- Send a message to the players in the @{Group}. --- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. --- @param #CONTROLLABLE self --- @param #string Message The message text --- @param DCSTypes#Duration Duration The duration of the message. -function CONTROLLABLE:Message( Message, Duration ) - self:F2( { Message, Duration } ) - - local DCSObject = self:GetDCSObject() - if DCSObject then - self:GetMessage( Message, Duration ):ToGroup( self ) - end - - return nil -end - --- This module contains the SCHEDULER class. -- --- 1) @{Scheduler#SCHEDULER} class, extends @{Base#BASE} --- ===================================================== --- The @{Scheduler#SCHEDULER} class models time events calling given event handling functions. +-- # 1) @{Core.Scheduler#SCHEDULER} class, extends @{Core.Base#BASE} +-- +-- The @{Core.Scheduler#SCHEDULER} class creates schedule. -- --- 1.1) SCHEDULER constructor --- -------------------------- --- The SCHEDULER class is quite easy to use: +-- ## 1.1) SCHEDULER constructor +-- +-- The SCHEDULER class is quite easy to use, but note that the New constructor has variable parameters: -- --- * @{Scheduler#SCHEDULER.New}: Setup a new scheduler and start it with the specified parameters. +-- * @{Core.Scheduler#SCHEDULER.New}( nil ): Setup a new SCHEDULER object, which is persistently executed after garbage collection. +-- * @{Core.Scheduler#SCHEDULER.New}( Object ): Setup a new SCHEDULER object, which is linked to the Object. When the Object is nillified or destroyed, the SCHEDULER object will also be destroyed and stopped after garbage collection. +-- * @{Core.Scheduler#SCHEDULER.New}( nil, Function, FunctionArguments, Start, ... ): Setup a new persistent SCHEDULER object, and start a new schedule for the Function with the defined FunctionArguments according the Start and sequent parameters. +-- * @{Core.Scheduler#SCHEDULER.New}( Object, Function, FunctionArguments, Start, ... ): Setup a new SCHEDULER object, linked to Object, and start a new schedule for the Function with the defined FunctionArguments according the Start and sequent parameters. +-- +-- ## 1.2) SCHEDULER timer stopping and (re-)starting. -- --- 1.2) SCHEDULER timer stop and start --- ----------------------------------- -- The SCHEDULER can be stopped and restarted with the following methods: -- --- * @{Scheduler#SCHEDULER.Start}: (Re-)Start the scheduler. --- * @{Scheduler#SCHEDULER.Stop}: Stop the scheduler. +-- * @{Core.Scheduler#SCHEDULER.Start}(): (Re-)Start the schedules within the SCHEDULER object. If a CallID is provided to :Start(), only the schedule referenced by CallID will be (re-)started. +-- * @{Core.Scheduler#SCHEDULER.Stop}(): Stop the schedules within the SCHEDULER object. If a CallID is provided to :Stop(), then only the schedule referenced by CallID will be stopped. -- --- 1.3) Reschedule new time event --- ------------------------------ --- With @{Scheduler#SCHEDULER.Schedule} a new time event can be scheduled. +-- ## 1.3) Create a new schedule +-- +-- With @{Core.Scheduler#SCHEDULER.Schedule}() a new time event can be scheduled. This function is used by the :New() constructor when a new schedule is planned. -- -- === -- -- ### Contributions: -- --- * Mechanist : Concept & Testing +-- * FlightControl : Concept & Testing -- -- ### Authors: -- -- * FlightControl : Design & Programming -- +-- ### Test Missions: +-- +-- * SCH - Scheduler +-- -- === -- -- @module Scheduler @@ -6350,170 +3743,341 @@ end --- The SCHEDULER class -- @type SCHEDULER -- @field #number ScheduleID the ID of the scheduler. --- @extends Base#BASE +-- @extends Core.Base#BASE SCHEDULER = { ClassName = "SCHEDULER", + Schedules = {}, } --- SCHEDULER constructor. -- @param #SCHEDULER self --- @param #table TimeEventObject Specified for which Moose object the timer is setup. If a value of nil is provided, a scheduler will be setup without an object reference. --- @param #function TimeEventFunction The event function to be called when a timer event occurs. The event function needs to accept the parameters specified in TimeEventFunctionArguments. --- @param #table TimeEventFunctionArguments Optional arguments that can be given as part of scheduler. The arguments need to be given as a table { param1, param 2, ... }. --- @param #number StartSeconds Specifies the amount of seconds that will be waited before the scheduling is started, and the event function is called. --- @param #number RepeatSecondsInterval Specifies the interval in seconds when the scheduler will call the event function. --- @param #number RandomizationFactor Specifies a randomization factor between 0 and 1 to randomize the RepeatSecondsInterval. --- @param #number StopSeconds Specifies the amount of seconds when the scheduler will be stopped. +-- @param #table SchedulerObject Specified for which Moose object the timer is setup. If a value of nil is provided, a scheduler will be setup without an object reference. +-- @param #function SchedulerFunction The event function to be called when a timer event occurs. The event function needs to accept the parameters specified in SchedulerArguments. +-- @param #table SchedulerArguments Optional arguments that can be given as part of scheduler. The arguments need to be given as a table { param1, param 2, ... }. +-- @param #number Start Specifies the amount of seconds that will be waited before the scheduling is started, and the event function is called. +-- @param #number Repeat Specifies the interval in seconds when the scheduler will call the event function. +-- @param #number RandomizeFactor Specifies a randomization factor between 0 and 1 to randomize the Repeat. +-- @param #number Stop Specifies the amount of seconds when the scheduler will be stopped. -- @return #SCHEDULER self -function SCHEDULER:New( TimeEventObject, TimeEventFunction, TimeEventFunctionArguments, StartSeconds, RepeatSecondsInterval, RandomizationFactor, StopSeconds ) +-- @return #number The ScheduleID of the planned schedule. +function SCHEDULER:New( SchedulerObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop ) local self = BASE:Inherit( self, BASE:New() ) - self:F2( { TimeEventObject, TimeEventFunction, TimeEventFunctionArguments, StartSeconds, RepeatSecondsInterval, RandomizationFactor, StopSeconds } ) + self:F2( { Start, Repeat, RandomizeFactor, Stop } ) + local ScheduleID = nil + + if SchedulerFunction then + ScheduleID = self:Schedule( SchedulerObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop ) + end - self:Schedule( TimeEventObject, TimeEventFunction, TimeEventFunctionArguments, StartSeconds, RepeatSecondsInterval, RandomizationFactor, StopSeconds ) - - return self + return self, ScheduleID end +--function SCHEDULER:_Destructor() +-- --self:E("_Destructor") +-- +-- _SCHEDULEDISPATCHER:RemoveSchedule( self.CallID ) +--end + --- Schedule a new time event. Note that the schedule will only take place if the scheduler is *started*. Even for a single schedule event, the scheduler needs to be started also. -- @param #SCHEDULER self --- @param #table TimeEventObject Specified for which Moose object the timer is setup. If a value of nil is provided, a scheduler will be setup without an object reference. --- @param #function TimeEventFunction The event function to be called when a timer event occurs. The event function needs to accept the parameters specified in TimeEventFunctionArguments. --- @param #table TimeEventFunctionArguments Optional arguments that can be given as part of scheduler. The arguments need to be given as a table { param1, param 2, ... }. --- @param #number StartSeconds Specifies the amount of seconds that will be waited before the scheduling is started, and the event function is called. --- @param #number RepeatSecondsInterval Specifies the interval in seconds when the scheduler will call the event function. --- @param #number RandomizationFactor Specifies a randomization factor between 0 and 1 to randomize the RepeatSecondsInterval. --- @param #number StopSeconds Specifies the amount of seconds when the scheduler will be stopped. --- @return #SCHEDULER self -function SCHEDULER:Schedule( TimeEventObject, TimeEventFunction, TimeEventFunctionArguments, StartSeconds, RepeatSecondsInterval, RandomizationFactor, StopSeconds ) - self:F2( { TimeEventFunctionArguments, StartSeconds, RepeatSecondsInterval, RandomizationFactor, StopSeconds } ) +-- @param #table SchedulerObject Specified for which Moose object the timer is setup. If a value of nil is provided, a scheduler will be setup without an object reference. +-- @param #function SchedulerFunction The event function to be called when a timer event occurs. The event function needs to accept the parameters specified in SchedulerArguments. +-- @param #table SchedulerArguments Optional arguments that can be given as part of scheduler. The arguments need to be given as a table { param1, param 2, ... }. +-- @param #number Start Specifies the amount of seconds that will be waited before the scheduling is started, and the event function is called. +-- @param #number Repeat Specifies the interval in seconds when the scheduler will call the event function. +-- @param #number RandomizeFactor Specifies a randomization factor between 0 and 1 to randomize the Repeat. +-- @param #number Stop Specifies the amount of seconds when the scheduler will be stopped. +-- @return #number The ScheduleID of the planned schedule. +function SCHEDULER:Schedule( SchedulerObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop ) + self:F2( { Start, Repeat, RandomizeFactor, Stop } ) + self:T3( { SchedulerArguments } ) - self.TimeEventObject = TimeEventObject - self.TimeEventFunction = TimeEventFunction - self.TimeEventFunctionArguments = TimeEventFunctionArguments - self.StartSeconds = StartSeconds - self.Repeat = false - self.RepeatSecondsInterval = RepeatSecondsInterval or 0 - self.RandomizationFactor = RandomizationFactor or 0 - self.StopSeconds = StopSeconds - self.StartTime = timer.getTime() + self.SchedulerObject = SchedulerObject + + local ScheduleID = _SCHEDULEDISPATCHER:AddSchedule( + self, + SchedulerFunction, + SchedulerArguments, + Start, + Repeat, + RandomizeFactor, + Stop + ) + + self.Schedules[#self.Schedules+1] = ScheduleID - self:Start() + return ScheduleID +end +--- (Re-)Starts the schedules or a specific schedule if a valid ScheduleID is provided. +-- @param #SCHEDULER self +-- @param #number ScheduleID (optional) The ScheduleID of the planned (repeating) schedule. +function SCHEDULER:Start( ScheduleID ) + self:F3( { ScheduleID } ) + + _SCHEDULEDISPATCHER:Start( self, ScheduleID ) +end + +--- Stops the schedules or a specific schedule if a valid ScheduleID is provided. +-- @param #SCHEDULER self +-- @param #number ScheduleID (optional) The ScheduleID of the planned (repeating) schedule. +function SCHEDULER:Stop( ScheduleID ) + self:F3( { ScheduleID } ) + + _SCHEDULEDISPATCHER:Stop( self, ScheduleID ) +end + +--- Removes a specific schedule if a valid ScheduleID is provided. +-- @param #SCHEDULER self +-- @param #number ScheduleID (optional) The ScheduleID of the planned (repeating) schedule. +function SCHEDULER:Remove( ScheduleID ) + self:F3( { ScheduleID } ) + + _SCHEDULEDISPATCHER:Remove( self, ScheduleID ) +end + + + + + + + + + + + + + + + +--- This module defines the SCHEDULEDISPATCHER class, which is used by a central object called _SCHEDULEDISPATCHER. +-- +-- === +-- +-- Takes care of the creation and dispatching of scheduled functions for SCHEDULER objects. +-- +-- This class is tricky and needs some thorought explanation. +-- SCHEDULE classes are used to schedule functions for objects, or as persistent objects. +-- The SCHEDULEDISPATCHER class ensures that: +-- +-- - Scheduled functions are planned according the SCHEDULER object parameters. +-- - Scheduled functions are repeated when requested, according the SCHEDULER object parameters. +-- - Scheduled functions are automatically removed when the schedule is finished, according the SCHEDULER object parameters. +-- +-- The SCHEDULEDISPATCHER class will manage SCHEDULER object in memory during garbage collection: +-- - When a SCHEDULER object is not attached to another object (that is, it's first :Schedule() parameter is nil), then the SCHEDULER +-- object is _persistent_ within memory. +-- - When a SCHEDULER object *is* attached to another object, then the SCHEDULER object is _not persistent_ within memory after a garbage collection! +-- The none persistency of SCHEDULERS attached to objects is required to allow SCHEDULER objects to be garbage collectged, when the parent object is also desroyed or nillified and garbage collected. +-- Even when there are pending timer scheduled functions to be executed for the SCHEDULER object, +-- these will not be executed anymore when the SCHEDULER object has been destroyed. +-- +-- The SCHEDULEDISPATCHER allows multiple scheduled functions to be planned and executed for one SCHEDULER object. +-- The SCHEDULER object therefore keeps a table of "CallID's", which are returned after each planning of a new scheduled function by the SCHEDULEDISPATCHER. +-- The SCHEDULER object plans new scheduled functions through the @{Core.Scheduler#SCHEDULER.Schedule}() method. +-- The Schedule() method returns the CallID that is the reference ID for each planned schedule. +-- +-- === +-- +-- === +-- +-- ### Contributions: - +-- ### Authors: FlightControl : Design & Programming +-- +-- @module ScheduleDispatcher + +--- The SCHEDULEDISPATCHER structure +-- @type SCHEDULEDISPATCHER +SCHEDULEDISPATCHER = { + ClassName = "SCHEDULEDISPATCHER", + CallID = 0, +} + +function SCHEDULEDISPATCHER:New() + local self = BASE:Inherit( self, BASE:New() ) + self:F3() return self end ---- (Re-)Starts the scheduler. --- @param #SCHEDULER self --- @return #SCHEDULER self -function SCHEDULER:Start() - self:F2( self.TimeEventObject ) +--- Add a Schedule to the ScheduleDispatcher. +-- The development of this method was really tidy. +-- It is constructed as such that a garbage collection is executed on the weak tables, when the Scheduler is nillified. +-- Nothing of this code should be modified without testing it thoroughly. +-- @param #SCHEDULEDISPATCHER self +-- @param Core.Scheduler#SCHEDULER Scheduler +function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleArguments, Start, Repeat, Randomize, Stop ) + self:F2( { Scheduler, ScheduleFunction, ScheduleArguments, Start, Repeat, Randomize, Stop } ) - if self.RepeatSecondsInterval ~= 0 then - self.Repeat = true + self.CallID = self.CallID + 1 + + -- Initialize the ObjectSchedulers array, which is a weakly coupled table. + -- If the object used as the key is nil, then the garbage collector will remove the item from the Functions array. + self.PersistentSchedulers = self.PersistentSchedulers or {} + + -- Initialize the ObjectSchedulers array, which is a weakly coupled table. + -- If the object used as the key is nil, then the garbage collector will remove the item from the Functions array. + self.ObjectSchedulers = self.ObjectSchedulers or setmetatable( {}, { __mode = "v" } ) + + if Scheduler.SchedulerObject then + self.ObjectSchedulers[self.CallID] = Scheduler + self:T3( { self.CallID, self.ObjectSchedulers[self.CallID] } ) + else + self.PersistentSchedulers[self.CallID] = Scheduler + self:T3( { self.CallID, self.PersistentSchedulers[self.CallID] } ) end - if self.StartSeconds then - if self.ScheduleID then - timer.removeFunction( self.ScheduleID ) - end - self.ScheduleID = timer.scheduleFunction( self._Scheduler, self, timer.getTime() + self.StartSeconds + .01 ) - end - - return self -end + self.Schedule = self.Schedule or setmetatable( {}, { __mode = "k" } ) + self.Schedule[Scheduler] = {} + self.Schedule[Scheduler][self.CallID] = {} + self.Schedule[Scheduler][self.CallID].Function = ScheduleFunction + self.Schedule[Scheduler][self.CallID].Arguments = ScheduleArguments + self.Schedule[Scheduler][self.CallID].StartTime = timer.getTime() + ( Start or 0 ) + self.Schedule[Scheduler][self.CallID].Start = Start + .001 + self.Schedule[Scheduler][self.CallID].Repeat = Repeat + self.Schedule[Scheduler][self.CallID].Randomize = Randomize + self.Schedule[Scheduler][self.CallID].Stop = Stop ---- Stops the scheduler. --- @param #SCHEDULER self --- @return #SCHEDULER self -function SCHEDULER:Stop() - self:F2( self.TimeEventObject ) + self:T3( self.Schedule[Scheduler][self.CallID] ) - self.Repeat = false - if self.ScheduleID then - self:E( "Stop Schedule" ) - timer.removeFunction( self.ScheduleID ) - end - self.ScheduleID = nil + self.Schedule[Scheduler][self.CallID].CallHandler = function( CallID ) + self:F2( CallID ) - return self -end - --- Private Functions - ---- @param #SCHEDULER self -function SCHEDULER:_Scheduler() - self:F2( self.TimeEventFunctionArguments ) - - local ErrorHandler = function( errmsg ) - - env.info( "Error in SCHEDULER function:" .. errmsg ) - if debug ~= nil then - env.info( debug.traceback() ) + local ErrorHandler = function( errmsg ) + env.info( "Error in timer function: " .. errmsg ) + if debug ~= nil then + env.info( debug.traceback() ) + end + return errmsg end - return errmsg + local Scheduler = self.ObjectSchedulers[CallID] + if not Scheduler then + Scheduler = self.PersistentSchedulers[CallID] + end + + self:T3( { Scheduler = Scheduler } ) + + if Scheduler then + + local Schedule = self.Schedule[Scheduler][CallID] + + self:T3( { Schedule = Schedule } ) + + local ScheduleObject = Scheduler.SchedulerObject + --local ScheduleObjectName = Scheduler.SchedulerObject:GetNameAndClassID() + local ScheduleFunction = Schedule.Function + local ScheduleArguments = Schedule.Arguments + local Start = Schedule.Start + local Repeat = Schedule.Repeat or 0 + local Randomize = Schedule.Randomize or 0 + local Stop = Schedule.Stop or 0 + local ScheduleID = Schedule.ScheduleID + + local Status, Result + if ScheduleObject then + local function Timer() + return ScheduleFunction( ScheduleObject, unpack( ScheduleArguments ) ) + end + Status, Result = xpcall( Timer, ErrorHandler ) + else + local function Timer() + return ScheduleFunction( unpack( ScheduleArguments ) ) + end + Status, Result = xpcall( Timer, ErrorHandler ) + end + + local CurrentTime = timer.getTime() + local StartTime = CurrentTime + Start + + if Status and (( Result == nil ) or ( Result and Result ~= false ) ) then + if Repeat ~= 0 and ( Stop == 0 ) or ( Stop ~= 0 and CurrentTime <= StartTime + Stop ) then + local ScheduleTime = + CurrentTime + + Repeat + + math.random( + - ( Randomize * Repeat / 2 ), + ( Randomize * Repeat / 2 ) + ) + + 0.01 + self:T3( { Repeat = CallID, CurrentTime, ScheduleTime, ScheduleArguments } ) + return ScheduleTime -- returns the next time the function needs to be called. + else + self:Stop( Scheduler, CallID ) + end + else + self:Stop( Scheduler, CallID ) + end + else + --self:E( "Scheduled obscolete call for CallID: " .. CallID ) + end + + return nil end - local StartTime = self.StartTime - local StopSeconds = self.StopSeconds - local Repeat = self.Repeat - local RandomizationFactor = self.RandomizationFactor - local RepeatSecondsInterval = self.RepeatSecondsInterval - local ScheduleID = self.ScheduleID + self:Start( Scheduler, self.CallID ) + + return self.CallID +end - local Status, Result - if self.TimeEventObject then - Status, Result = xpcall( function() return self.TimeEventFunction( self.TimeEventObject, unpack( self.TimeEventFunctionArguments ) ) end, ErrorHandler ) - else - Status, Result = xpcall( function() return self.TimeEventFunction( unpack( self.TimeEventFunctionArguments ) ) end, ErrorHandler ) +function SCHEDULEDISPATCHER:RemoveSchedule( Scheduler, CallID ) + self:F2( { Remove = CallID, Scheduler = Scheduler } ) + + if CallID then + self:Stop( Scheduler, CallID ) + self.Schedule[Scheduler][CallID] = nil end +end - self:T( { "Timer Event2 .. " .. self.ScheduleID, Status, Result, StartTime, RepeatSecondsInterval, RandomizationFactor, StopSeconds } ) +function SCHEDULEDISPATCHER:Start( Scheduler, CallID ) + self:F2( { Start = CallID, Scheduler = Scheduler } ) - if Status and ( ( Result == nil ) or ( Result and Result ~= false ) ) then - if Repeat and ( not StopSeconds or ( StopSeconds and timer.getTime() <= StartTime + StopSeconds ) ) then - local ScheduleTime = - timer.getTime() + - self.RepeatSecondsInterval + - math.random( - - ( RandomizationFactor * RepeatSecondsInterval / 2 ), - ( RandomizationFactor * RepeatSecondsInterval / 2 ) - ) + - 0.01 - self:T( { self.TimeEventFunctionArguments, "Repeat:", timer.getTime(), ScheduleTime } ) - return ScheduleTime -- returns the next time the function needs to be called. - else - timer.removeFunction( ScheduleID ) - self.ScheduleID = nil + if CallID then + local Schedule = self.Schedule[Scheduler] + Schedule[CallID].ScheduleID = timer.scheduleFunction( + Schedule[CallID].CallHandler, + CallID, + timer.getTime() + Schedule[CallID].Start + ) + else + for CallID, Schedule in pairs( self.Schedule[Scheduler] ) do + self:Start( Scheduler, CallID ) -- Recursive end - else - timer.removeFunction( ScheduleID ) - self.ScheduleID = nil end +end - return nil +function SCHEDULEDISPATCHER:Stop( Scheduler, CallID ) + self:F2( { Stop = CallID, Scheduler = Scheduler } ) + + if CallID then + local Schedule = self.Schedule[Scheduler] + timer.removeFunction( Schedule[CallID].ScheduleID ) + else + for CallID, Schedule in pairs( self.Schedule[Scheduler] ) do + self:Stop( Scheduler, CallID ) -- Recursive + end + end end - - - - - - - - - - - - - ---- The EVENT class models an efficient event handling process between other classes and its units, weapons. +--- This module contains the EVENT class. +-- +-- === +-- +-- Takes care of EVENT dispatching between DCS events and event handling functions defined in MOOSE classes. +-- +-- === +-- +-- The above menus classes **are derived** from 2 main **abstract** classes defined within the MOOSE framework (so don't use these): +-- +-- === +-- +-- ### Contributions: - +-- ### Authors: FlightControl : Design & Programming +-- -- @module Event --- @author FlightControl --- The EVENT structure -- @type EVENT @@ -6558,13 +4122,13 @@ local _EVENTCODES = { -- @field weapon -- @field IniDCSUnit -- @field IniDCSUnitName --- @field Unit#UNIT IniUnit +-- @field Wrapper.Unit#UNIT IniUnit -- @field #string IniUnitName -- @field IniDCSGroup -- @field IniDCSGroupName -- @field TgtDCSUnit -- @field TgtDCSUnitName --- @field Unit#UNIT TgtUnit +-- @field Wrapper.Unit#UNIT TgtUnit -- @field #string TgtUnitName -- @field TgtDCSGroup -- @field TgtDCSGroupName @@ -6593,45 +4157,62 @@ end --- Initializes the Events structure for the event -- @param #EVENT self --- @param DCSWorld#world.event EventID --- @param #string EventClass +-- @param Dcs.DCSWorld#world.event EventID +-- @param Core.Base#BASE EventClass -- @return #EVENT.Events function EVENT:Init( EventID, EventClass ) self:F3( { _EVENTCODES[EventID], EventClass } ) - if not self.Events[EventID] then - self.Events[EventID] = {} + + if not self.Events[EventID] then + -- Create a WEAK table to ensure that the garbage collector is cleaning the event links when the object usage is cleaned. + self.Events[EventID] = setmetatable( {}, { __mode = "k" } ) + end + if not self.Events[EventID][EventClass] then - self.Events[EventID][EventClass] = {} + self.Events[EventID][EventClass] = setmetatable( {}, { __mode = "k" } ) end return self.Events[EventID][EventClass] end --- Removes an Events entry -- @param #EVENT self --- @param Base#BASE EventSelf The self instance of the class for which the event is. --- @param DCSWorld#world.event EventID +-- @param Core.Base#BASE EventClass The self instance of the class for which the event is. +-- @param Dcs.DCSWorld#world.event EventID -- @return #EVENT.Events -function EVENT:Remove( EventSelf, EventID ) - self:F3( { EventSelf, _EVENTCODES[EventID] } ) +function EVENT:Remove( EventClass, EventID ) + self:F3( { EventClass, _EVENTCODES[EventID] } ) - local EventClass = EventSelf:GetClassNameAndID() + local EventClass = EventClass self.Events[EventID][EventClass] = nil end +--- Clears all event subscriptions for a @{Core.Base#BASE} derived object. +-- @param #EVENT self +-- @param Core.Base#BASE EventObject +function EVENT:RemoveAll( EventObject ) + self:F3( { EventObject:GetClassNameAndID() } ) + + local EventClass = EventObject:GetClassNameAndID() + for EventID, EventData in pairs( self.Events ) do + self.Events[EventID][EventClass] = nil + end +end + + --- Create an OnDead event handler for a group -- @param #EVENT self -- @param #table EventTemplate -- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param EventSelf The self instance of the class for which the event is. +-- @param EventClass The instance of the class for which the event is. -- @param #function OnEventFunction -- @return #EVENT -function EVENT:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, OnEventFunction ) +function EVENT:OnEventForTemplate( EventTemplate, EventFunction, EventClass, OnEventFunction ) self:F2( EventTemplate.name ) for EventUnitID, EventUnit in pairs( EventTemplate.units ) do - OnEventFunction( self, EventUnit.name, EventFunction, EventSelf ) + OnEventFunction( self, EventUnit.name, EventFunction, EventClass ) end return self end @@ -6639,15 +4220,15 @@ end --- Set a new listener for an S_EVENT_X event independent from a unit or a weapon. -- @param #EVENT self -- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf The self instance of the class for which the event is. +-- @param Core.Base#BASE EventClass The self instance of the class for which the event is captured. When the event happens, the event process will be called in this class provided. -- @param EventID -- @return #EVENT -function EVENT:OnEventGeneric( EventFunction, EventSelf, EventID ) +function EVENT:OnEventGeneric( EventFunction, EventClass, EventID ) self:F2( { EventID } ) - local Event = self:Init( EventID, EventSelf:GetClassNameAndID() ) + local Event = self:Init( EventID, EventClass ) Event.EventFunction = EventFunction - Event.EventSelf = EventSelf + Event.EventClass = EventClass return self end @@ -6656,19 +4237,19 @@ end -- @param #EVENT self -- @param #string EventDCSUnitName -- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf The self instance of the class for which the event is. +-- @param Core.Base#BASE EventClass The self instance of the class for which the event is. -- @param EventID -- @return #EVENT -function EVENT:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, EventID ) +function EVENT:OnEventForUnit( EventDCSUnitName, EventFunction, EventClass, EventID ) self:F2( EventDCSUnitName ) - local Event = self:Init( EventID, EventSelf:GetClassNameAndID() ) + local Event = self:Init( EventID, EventClass ) if not Event.IniUnit then Event.IniUnit = {} end Event.IniUnit[EventDCSUnitName] = {} Event.IniUnit[EventDCSUnitName].EventFunction = EventFunction - Event.IniUnit[EventDCSUnitName].EventSelf = EventSelf + Event.IniUnit[EventDCSUnitName].EventClass = EventClass return self end @@ -6676,14 +4257,14 @@ do -- OnBirth --- Create an OnBirth event handler for a group -- @param #EVENT self - -- @param Group#GROUP EventGroup + -- @param Wrapper.Group#GROUP EventGroup -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param EventSelf The self instance of the class for which the event is. + -- @param EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnBirthForTemplate( EventTemplate, EventFunction, EventSelf ) + function EVENT:OnBirthForTemplate( EventTemplate, EventFunction, EventClass ) self:F2( EventTemplate.name ) - self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnBirthForUnit ) + self:OnEventForTemplate( EventTemplate, EventFunction, EventClass, self.OnBirthForUnit ) return self end @@ -6691,12 +4272,12 @@ do -- OnBirth --- Set a new listener for an S_EVENT_BIRTH event, and registers the unit born. -- @param #EVENT self -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf + -- @param Base#BASE EventClass -- @return #EVENT - function EVENT:OnBirth( EventFunction, EventSelf ) + function EVENT:OnBirth( EventFunction, EventClass ) self:F2() - self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_BIRTH ) + self:OnEventGeneric( EventFunction, EventClass, world.event.S_EVENT_BIRTH ) return self end @@ -6705,24 +4286,24 @@ do -- OnBirth -- @param #EVENT self -- @param #string EventDCSUnitName The id of the unit for the event to be handled. -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf + -- @param Base#BASE EventClass -- @return #EVENT - function EVENT:OnBirthForUnit( EventDCSUnitName, EventFunction, EventSelf ) + function EVENT:OnBirthForUnit( EventDCSUnitName, EventFunction, EventClass ) self:F2( EventDCSUnitName ) - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_BIRTH ) + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventClass, world.event.S_EVENT_BIRTH ) return self end --- Stop listening to S_EVENT_BIRTH event. -- @param #EVENT self - -- @param Base#BASE EventSelf + -- @param Base#BASE EventClass -- @return #EVENT - function EVENT:OnBirthRemove( EventSelf ) + function EVENT:OnBirthRemove( EventClass ) self:F2() - self:Remove( EventSelf, world.event.S_EVENT_BIRTH ) + self:Remove( EventClass, world.event.S_EVENT_BIRTH ) return self end @@ -6734,14 +4315,14 @@ do -- OnCrash --- Create an OnCrash event handler for a group -- @param #EVENT self - -- @param Group#GROUP EventGroup + -- @param Wrapper.Group#GROUP EventGroup -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param EventSelf The self instance of the class for which the event is. + -- @param EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnCrashForTemplate( EventTemplate, EventFunction, EventSelf ) + function EVENT:OnCrashForTemplate( EventTemplate, EventFunction, EventClass ) self:F2( EventTemplate.name ) - self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnCrashForUnit ) + self:OnEventForTemplate( EventTemplate, EventFunction, EventClass, self.OnCrashForUnit ) return self end @@ -6749,12 +4330,12 @@ do -- OnCrash --- Set a new listener for an S_EVENT_CRASH event. -- @param #EVENT self -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf + -- @param Base#BASE EventClass -- @return #EVENT - function EVENT:OnCrash( EventFunction, EventSelf ) + function EVENT:OnCrash( EventFunction, EventClass ) self:F2() - self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_CRASH ) + self:OnEventGeneric( EventFunction, EventClass, world.event.S_EVENT_CRASH ) return self end @@ -6763,24 +4344,24 @@ do -- OnCrash -- @param #EVENT self -- @param #string EventDCSUnitName -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @param Base#BASE EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnCrashForUnit( EventDCSUnitName, EventFunction, EventSelf ) + function EVENT:OnCrashForUnit( EventDCSUnitName, EventFunction, EventClass ) self:F2( EventDCSUnitName ) - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_CRASH ) + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventClass, world.event.S_EVENT_CRASH ) return self end --- Stop listening to S_EVENT_CRASH event. -- @param #EVENT self - -- @param Base#BASE EventSelf + -- @param Base#BASE EventClass -- @return #EVENT - function EVENT:OnCrashRemove( EventSelf ) + function EVENT:OnCrashRemove( EventClass ) self:F2() - self:Remove( EventSelf, world.event.S_EVENT_CRASH ) + self:Remove( EventClass, world.event.S_EVENT_CRASH ) return self end @@ -6791,14 +4372,14 @@ do -- OnDead --- Create an OnDead event handler for a group -- @param #EVENT self - -- @param Group#GROUP EventGroup + -- @param Wrapper.Group#GROUP EventGroup -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param EventSelf The self instance of the class for which the event is. + -- @param EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnDeadForTemplate( EventTemplate, EventFunction, EventSelf ) + function EVENT:OnDeadForTemplate( EventTemplate, EventFunction, EventClass ) self:F2( EventTemplate.name ) - self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnDeadForUnit ) + self:OnEventForTemplate( EventTemplate, EventFunction, EventClass, self.OnDeadForUnit ) return self end @@ -6806,12 +4387,12 @@ do -- OnDead --- Set a new listener for an S_EVENT_DEAD event. -- @param #EVENT self -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf + -- @param Base#BASE EventClass -- @return #EVENT - function EVENT:OnDead( EventFunction, EventSelf ) + function EVENT:OnDead( EventFunction, EventClass ) self:F2() - self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_DEAD ) + self:OnEventGeneric( EventFunction, EventClass, world.event.S_EVENT_DEAD ) return self end @@ -6821,24 +4402,24 @@ do -- OnDead -- @param #EVENT self -- @param #string EventDCSUnitName -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @param Base#BASE EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnDeadForUnit( EventDCSUnitName, EventFunction, EventSelf ) + function EVENT:OnDeadForUnit( EventDCSUnitName, EventFunction, EventClass ) self:F2( EventDCSUnitName ) - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_DEAD ) + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventClass, world.event.S_EVENT_DEAD ) return self end --- Stop listening to S_EVENT_DEAD event. -- @param #EVENT self - -- @param Base#BASE EventSelf + -- @param Base#BASE EventClass -- @return #EVENT - function EVENT:OnDeadRemove( EventSelf ) + function EVENT:OnDeadRemove( EventClass ) self:F2() - self:Remove( EventSelf, world.event.S_EVENT_DEAD ) + self:Remove( EventClass, world.event.S_EVENT_DEAD ) return self end @@ -6851,12 +4432,12 @@ do -- OnPilotDead --- Set a new listener for an S_EVENT_PILOT_DEAD event. -- @param #EVENT self -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf + -- @param Base#BASE EventClass -- @return #EVENT - function EVENT:OnPilotDead( EventFunction, EventSelf ) + function EVENT:OnPilotDead( EventFunction, EventClass ) self:F2() - self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_PILOT_DEAD ) + self:OnEventGeneric( EventFunction, EventClass, world.event.S_EVENT_PILOT_DEAD ) return self end @@ -6865,24 +4446,24 @@ do -- OnPilotDead -- @param #EVENT self -- @param #string EventDCSUnitName -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @param Base#BASE EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnPilotDeadForUnit( EventDCSUnitName, EventFunction, EventSelf ) + function EVENT:OnPilotDeadForUnit( EventDCSUnitName, EventFunction, EventClass ) self:F2( EventDCSUnitName ) - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_PILOT_DEAD ) + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventClass, world.event.S_EVENT_PILOT_DEAD ) return self end --- Stop listening to S_EVENT_PILOT_DEAD event. -- @param #EVENT self - -- @param Base#BASE EventSelf + -- @param Base#BASE EventClass -- @return #EVENT - function EVENT:OnPilotDeadRemove( EventSelf ) + function EVENT:OnPilotDeadRemove( EventClass ) self:F2() - self:Remove( EventSelf, world.event.S_EVENT_PILOT_DEAD ) + self:Remove( EventClass, world.event.S_EVENT_PILOT_DEAD ) return self end @@ -6894,12 +4475,12 @@ do -- OnLand -- @param #EVENT self -- @param #table EventTemplate -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param EventSelf The self instance of the class for which the event is. + -- @param EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnLandForTemplate( EventTemplate, EventFunction, EventSelf ) + function EVENT:OnLandForTemplate( EventTemplate, EventFunction, EventClass ) self:F2( EventTemplate.name ) - self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnLandForUnit ) + self:OnEventForTemplate( EventTemplate, EventFunction, EventClass, self.OnLandForUnit ) return self end @@ -6908,24 +4489,24 @@ do -- OnLand -- @param #EVENT self -- @param #string EventDCSUnitName -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @param Base#BASE EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnLandForUnit( EventDCSUnitName, EventFunction, EventSelf ) + function EVENT:OnLandForUnit( EventDCSUnitName, EventFunction, EventClass ) self:F2( EventDCSUnitName ) - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_LAND ) + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventClass, world.event.S_EVENT_LAND ) return self end --- Stop listening to S_EVENT_LAND event. -- @param #EVENT self - -- @param Base#BASE EventSelf + -- @param Base#BASE EventClass -- @return #EVENT - function EVENT:OnLandRemove( EventSelf ) + function EVENT:OnLandRemove( EventClass ) self:F2() - self:Remove( EventSelf, world.event.S_EVENT_LAND ) + self:Remove( EventClass, world.event.S_EVENT_LAND ) return self end @@ -6938,12 +4519,12 @@ do -- OnTakeOff -- @param #EVENT self -- @param #table EventTemplate -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param EventSelf The self instance of the class for which the event is. + -- @param EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnTakeOffForTemplate( EventTemplate, EventFunction, EventSelf ) + function EVENT:OnTakeOffForTemplate( EventTemplate, EventFunction, EventClass ) self:F2( EventTemplate.name ) - self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnTakeOffForUnit ) + self:OnEventForTemplate( EventTemplate, EventFunction, EventClass, self.OnTakeOffForUnit ) return self end @@ -6952,24 +4533,24 @@ do -- OnTakeOff -- @param #EVENT self -- @param #string EventDCSUnitName -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @param Base#BASE EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnTakeOffForUnit( EventDCSUnitName, EventFunction, EventSelf ) + function EVENT:OnTakeOffForUnit( EventDCSUnitName, EventFunction, EventClass ) self:F2( EventDCSUnitName ) - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_TAKEOFF ) + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventClass, world.event.S_EVENT_TAKEOFF ) return self end --- Stop listening to S_EVENT_TAKEOFF event. -- @param #EVENT self - -- @param Base#BASE EventSelf + -- @param Base#BASE EventClass -- @return #EVENT - function EVENT:OnTakeOffRemove( EventSelf ) + function EVENT:OnTakeOffRemove( EventClass ) self:F2() - self:Remove( EventSelf, world.event.S_EVENT_TAKEOFF ) + self:Remove( EventClass, world.event.S_EVENT_TAKEOFF ) return self end @@ -6983,12 +4564,12 @@ do -- OnEngineShutDown -- @param #EVENT self -- @param #table EventTemplate -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param EventSelf The self instance of the class for which the event is. + -- @param EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnEngineShutDownForTemplate( EventTemplate, EventFunction, EventSelf ) + function EVENT:OnEngineShutDownForTemplate( EventTemplate, EventFunction, EventClass ) self:F2( EventTemplate.name ) - self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnEngineShutDownForUnit ) + self:OnEventForTemplate( EventTemplate, EventFunction, EventClass, self.OnEngineShutDownForUnit ) return self end @@ -6997,24 +4578,24 @@ do -- OnEngineShutDown -- @param #EVENT self -- @param #string EventDCSUnitName -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @param Base#BASE EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnEngineShutDownForUnit( EventDCSUnitName, EventFunction, EventSelf ) + function EVENT:OnEngineShutDownForUnit( EventDCSUnitName, EventFunction, EventClass ) self:F2( EventDCSUnitName ) - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_ENGINE_SHUTDOWN ) + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventClass, world.event.S_EVENT_ENGINE_SHUTDOWN ) return self end --- Stop listening to S_EVENT_ENGINE_SHUTDOWN event. -- @param #EVENT self - -- @param Base#BASE EventSelf + -- @param Base#BASE EventClass -- @return #EVENT - function EVENT:OnEngineShutDownRemove( EventSelf ) + function EVENT:OnEngineShutDownRemove( EventClass ) self:F2() - self:Remove( EventSelf, world.event.S_EVENT_ENGINE_SHUTDOWN ) + self:Remove( EventClass, world.event.S_EVENT_ENGINE_SHUTDOWN ) return self end @@ -7027,24 +4608,24 @@ do -- OnEngineStartUp -- @param #EVENT self -- @param #string EventDCSUnitName -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @param Base#BASE EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnEngineStartUpForUnit( EventDCSUnitName, EventFunction, EventSelf ) + function EVENT:OnEngineStartUpForUnit( EventDCSUnitName, EventFunction, EventClass ) self:F2( EventDCSUnitName ) - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_ENGINE_STARTUP ) + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventClass, world.event.S_EVENT_ENGINE_STARTUP ) return self end --- Stop listening to S_EVENT_ENGINE_STARTUP event. -- @param #EVENT self - -- @param Base#BASE EventSelf + -- @param Base#BASE EventClass -- @return #EVENT - function EVENT:OnEngineStartUpRemove( EventSelf ) + function EVENT:OnEngineStartUpRemove( EventClass ) self:F2() - self:Remove( EventSelf, world.event.S_EVENT_ENGINE_STARTUP ) + self:Remove( EventClass, world.event.S_EVENT_ENGINE_STARTUP ) return self end @@ -7055,12 +4636,12 @@ do -- OnShot --- Set a new listener for an S_EVENT_SHOT event. -- @param #EVENT self -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @param Base#BASE EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnShot( EventFunction, EventSelf ) + function EVENT:OnShot( EventFunction, EventClass ) self:F2() - self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_SHOT ) + self:OnEventGeneric( EventFunction, EventClass, world.event.S_EVENT_SHOT ) return self end @@ -7069,24 +4650,24 @@ do -- OnShot -- @param #EVENT self -- @param #string EventDCSUnitName -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @param Base#BASE EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnShotForUnit( EventDCSUnitName, EventFunction, EventSelf ) + function EVENT:OnShotForUnit( EventDCSUnitName, EventFunction, EventClass ) self:F2( EventDCSUnitName ) - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_SHOT ) + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventClass, world.event.S_EVENT_SHOT ) return self end --- Stop listening to S_EVENT_SHOT event. -- @param #EVENT self - -- @param Base#BASE EventSelf + -- @param Base#BASE EventClass -- @return #EVENT - function EVENT:OnShotRemove( EventSelf ) + function EVENT:OnShotRemove( EventClass ) self:F2() - self:Remove( EventSelf, world.event.S_EVENT_SHOT ) + self:Remove( EventClass, world.event.S_EVENT_SHOT ) return self end @@ -7099,12 +4680,12 @@ do -- OnHit --- Set a new listener for an S_EVENT_HIT event. -- @param #EVENT self -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @param Base#BASE EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnHit( EventFunction, EventSelf ) + function EVENT:OnHit( EventFunction, EventClass ) self:F2() - self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_HIT ) + self:OnEventGeneric( EventFunction, EventClass, world.event.S_EVENT_HIT ) return self end @@ -7113,24 +4694,24 @@ do -- OnHit -- @param #EVENT self -- @param #string EventDCSUnitName -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @param Base#BASE EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnHitForUnit( EventDCSUnitName, EventFunction, EventSelf ) + function EVENT:OnHitForUnit( EventDCSUnitName, EventFunction, EventClass ) self:F2( EventDCSUnitName ) - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_HIT ) + self:OnEventForUnit( EventDCSUnitName, EventFunction, EventClass, world.event.S_EVENT_HIT ) return self end --- Stop listening to S_EVENT_HIT event. -- @param #EVENT self - -- @param Base#BASE EventSelf + -- @param Base#BASE EventClass -- @return #EVENT - function EVENT:OnHitRemove( EventSelf ) + function EVENT:OnHitRemove( EventClass ) self:F2() - self:Remove( EventSelf, world.event.S_EVENT_HIT ) + self:Remove( EventClass, world.event.S_EVENT_HIT ) return self end @@ -7142,24 +4723,24 @@ do -- OnPlayerEnterUnit --- Set a new listener for an S_EVENT_PLAYER_ENTER_UNIT event. -- @param #EVENT self -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @param Base#BASE EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnPlayerEnterUnit( EventFunction, EventSelf ) + function EVENT:OnPlayerEnterUnit( EventFunction, EventClass ) self:F2() - self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_PLAYER_ENTER_UNIT ) + self:OnEventGeneric( EventFunction, EventClass, world.event.S_EVENT_PLAYER_ENTER_UNIT ) return self end --- Stop listening to S_EVENT_PLAYER_ENTER_UNIT event. -- @param #EVENT self - -- @param Base#BASE EventSelf + -- @param Base#BASE EventClass -- @return #EVENT - function EVENT:OnPlayerEnterRemove( EventSelf ) + function EVENT:OnPlayerEnterRemove( EventClass ) self:F2() - self:Remove( EventSelf, world.event.S_EVENT_PLAYER_ENTER_UNIT ) + self:Remove( EventClass, world.event.S_EVENT_PLAYER_ENTER_UNIT ) return self end @@ -7170,24 +4751,24 @@ do -- OnPlayerLeaveUnit --- Set a new listener for an S_EVENT_PLAYER_LEAVE_UNIT event. -- @param #EVENT self -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf The self instance of the class for which the event is. + -- @param Base#BASE EventClass The self instance of the class for which the event is. -- @return #EVENT - function EVENT:OnPlayerLeaveUnit( EventFunction, EventSelf ) + function EVENT:OnPlayerLeaveUnit( EventFunction, EventClass ) self:F2() - self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_PLAYER_LEAVE_UNIT ) + self:OnEventGeneric( EventFunction, EventClass, world.event.S_EVENT_PLAYER_LEAVE_UNIT ) return self end --- Stop listening to S_EVENT_PLAYER_LEAVE_UNIT event. -- @param #EVENT self - -- @param Base#BASE EventSelf + -- @param Base#BASE EventClass -- @return #EVENT - function EVENT:OnPlayerLeaveRemove( EventSelf ) + function EVENT:OnPlayerLeaveRemove( EventClass ) self:F2() - self:Remove( EventSelf, world.event.S_EVENT_PLAYER_LEAVE_UNIT ) + self:Remove( EventClass, world.event.S_EVENT_PLAYER_LEAVE_UNIT ) return self end @@ -7207,6 +4788,10 @@ function EVENT:onEvent( Event ) Event.IniDCSUnitName = Event.IniDCSUnit:getName() Event.IniUnitName = Event.IniDCSUnitName Event.IniUnit = UNIT:FindByName( Event.IniDCSUnitName ) + if not Event.IniUnit then + -- Unit can be a CLIENT. Most likely this will be the case ... + Event.IniUnit = CLIENT:FindByName( Event.IniDCSUnitName, '', true ) + end Event.IniDCSGroupName = "" if Event.IniDCSGroup and Event.IniDCSGroup:isExist() then Event.IniDCSGroupName = Event.IniDCSGroup:getName() @@ -7230,16 +4815,21 @@ function EVENT:onEvent( Event ) Event.WeaponName = Event.Weapon:getTypeName() --Event.WeaponTgtDCSUnit = Event.Weapon:getTarget() end - self:E( { _EVENTCODES[Event.id], Event.initiator, Event.IniDCSUnitName, Event.target, Event.TgtDCSUnitName, Event.weapon, Event.WeaponName } ) - for ClassName, EventData in pairs( self.Events[Event.id] ) do + self:E( { _EVENTCODES[Event.id], Event, Event.IniDCSUnitName, Event.TgtDCSUnitName } ) + + -- Okay, we got the event from DCS. Now loop the self.Events[] table for the received Event.id, and for each EventData registered, check if a function needs to be called. + for EventClass, EventData in pairs( self.Events[Event.id] ) do + -- If the EventData is for a UNIT, the call directly the EventClass EventFunction for that UNIT. if Event.IniDCSUnitName and EventData.IniUnit and EventData.IniUnit[Event.IniDCSUnitName] then - self:T( { "Calling event function for class ", ClassName, " unit ", Event.IniUnitName } ) - EventData.IniUnit[Event.IniDCSUnitName].EventFunction( EventData.IniUnit[Event.IniDCSUnitName].EventSelf, Event ) + self:T( { "Calling EventFunction for Class ", EventClass:GetClassNameAndID(), ", Unit ", Event.IniUnitName } ) + EventData.IniUnit[Event.IniDCSUnitName].EventFunction( EventData.IniUnit[Event.IniDCSUnitName].EventClass, Event ) else + -- If the EventData is not bound to a specific unit, then call the EventClass EventFunction. + -- Note that here the EventFunction will need to implement and determine the logic for the relevant source- or target unit, or weapon. if Event.IniDCSUnit and not EventData.IniUnit then - if ClassName == EventData.EventSelf:GetClassNameAndID() then - self:T( { "Calling event function for class ", ClassName } ) - EventData.EventFunction( EventData.EventSelf, Event ) + if EventClass == EventData.EventClass then + self:T( { "Calling EventFunction for Class ", EventClass:GetClassNameAndID() } ) + EventData.EventFunction( EventData.EventClass, Event ) end end end @@ -7266,17 +4856,17 @@ end -- -- ### To manage **main menus**, the classes begin with **MENU_**: -- --- * @{Menu#MENU_MISSION}: Manages main menus for whole mission file. --- * @{Menu#MENU_COALITION}: Manages main menus for whole coalition. --- * @{Menu#MENU_GROUP}: Manages main menus for GROUPs. --- * @{Menu#MENU_CLIENT}: Manages main menus for CLIENTs. This manages menus for units with the skill level "Client". +-- * @{Core.Menu#MENU_MISSION}: Manages main menus for whole mission file. +-- * @{Core.Menu#MENU_COALITION}: Manages main menus for whole coalition. +-- * @{Core.Menu#MENU_GROUP}: Manages main menus for GROUPs. +-- * @{Core.Menu#MENU_CLIENT}: Manages main menus for CLIENTs. This manages menus for units with the skill level "Client". -- -- ### To manage **command menus**, which are menus that allow the player to issue **functions**, the classes begin with **MENU_COMMAND_**: -- --- * @{Menu#MENU_MISSION_COMMAND}: Manages command menus for whole mission file. --- * @{Menu#MENU_COALITION_COMMAND}: Manages command menus for whole coalition. --- * @{Menu#MENU_GROUP_COMMAND}: Manages command menus for GROUPs. --- * @{Menu#MENU_CLIENT_COMMAND}: Manages command menus for CLIENTs. This manages menus for units with the skill level "Client". +-- * @{Core.Menu#MENU_MISSION_COMMAND}: Manages command menus for whole mission file. +-- * @{Core.Menu#MENU_COALITION_COMMAND}: Manages command menus for whole coalition. +-- * @{Core.Menu#MENU_GROUP_COMMAND}: Manages command menus for GROUPs. +-- * @{Core.Menu#MENU_CLIENT_COMMAND}: Manages command menus for CLIENTs. This manages menus for units with the skill level "Client". -- -- === -- @@ -7288,11 +4878,11 @@ end -- These are simply abstract base classes defining a couple of fields that are used by the -- derived MENU_ classes to manage menus. -- --- 1.1) @{Menu#MENU_BASE} class, extends @{Base#BASE} +-- 1.1) @{Core.Menu#MENU_BASE} class, extends @{Core.Base#BASE} -- -------------------------------------------------- -- The @{#MENU_BASE} class defines the main MENU class where other MENU classes are derived from. -- --- 1.2) @{Menu#MENU_COMMAND_BASE} class, extends @{Base#BASE} +-- 1.2) @{Core.Menu#MENU_COMMAND_BASE} class, extends @{Core.Base#BASE} -- ---------------------------------------------------------- -- The @{#MENU_COMMAND_BASE} class defines the main MENU class where other MENU COMMAND_ classes are derived from, in order to set commands. -- @@ -7304,15 +4894,15 @@ end -- ====================== -- The underlying classes manage the menus for a complete mission file. -- --- 2.1) @{Menu#MENU_MISSION} class, extends @{Menu#MENU_BASE} +-- 2.1) @{Menu#MENU_MISSION} class, extends @{Core.Menu#MENU_BASE} -- --------------------------------------------------------- --- The @{Menu#MENU_MISSION} class manages the main menus for a complete mission. +-- The @{Core.Menu#MENU_MISSION} class manages the main menus for a complete mission. -- You can add menus with the @{#MENU_MISSION.New} method, which constructs a MENU_MISSION object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_MISSION.Remove}. -- --- 2.2) @{Menu#MENU_MISSION_COMMAND} class, extends @{Menu#MENU_COMMAND_BASE} +-- 2.2) @{Menu#MENU_MISSION_COMMAND} class, extends @{Core.Menu#MENU_COMMAND_BASE} -- ------------------------------------------------------------------------- --- The @{Menu#MENU_MISSION_COMMAND} class manages the command menus for a complete mission, which allow players to execute functions during mission execution. +-- The @{Core.Menu#MENU_MISSION_COMMAND} class manages the command menus for a complete mission, which allow players to execute functions during mission execution. -- You can add menus with the @{#MENU_MISSION_COMMAND.New} method, which constructs a MENU_MISSION_COMMAND object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_MISSION_COMMAND.Remove}. -- @@ -7322,15 +4912,15 @@ end -- ========================= -- The underlying classes manage the menus for whole coalitions. -- --- 3.1) @{Menu#MENU_COALITION} class, extends @{Menu#MENU_BASE} +-- 3.1) @{Menu#MENU_COALITION} class, extends @{Core.Menu#MENU_BASE} -- ------------------------------------------------------------ --- The @{Menu#MENU_COALITION} class manages the main menus for coalitions. +-- The @{Core.Menu#MENU_COALITION} class manages the main menus for coalitions. -- You can add menus with the @{#MENU_COALITION.New} method, which constructs a MENU_COALITION object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_COALITION.Remove}. -- --- 3.2) @{Menu#MENU_COALITION_COMMAND} class, extends @{Menu#MENU_COMMAND_BASE} +-- 3.2) @{Menu#MENU_COALITION_COMMAND} class, extends @{Core.Menu#MENU_COMMAND_BASE} -- ---------------------------------------------------------------------------- --- The @{Menu#MENU_COALITION_COMMAND} class manages the command menus for coalitions, which allow players to execute functions during mission execution. +-- The @{Core.Menu#MENU_COALITION_COMMAND} class manages the command menus for coalitions, which allow players to execute functions during mission execution. -- You can add menus with the @{#MENU_COALITION_COMMAND.New} method, which constructs a MENU_COALITION_COMMAND object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_COALITION_COMMAND.Remove}. -- @@ -7340,15 +4930,15 @@ end -- ===================== -- The underlying classes manage the menus for groups. Note that groups can be inactive, alive or can be destroyed. -- --- 4.1) @{Menu#MENU_GROUP} class, extends @{Menu#MENU_BASE} +-- 4.1) @{Menu#MENU_GROUP} class, extends @{Core.Menu#MENU_BASE} -- -------------------------------------------------------- --- The @{Menu#MENU_GROUP} class manages the main menus for coalitions. +-- The @{Core.Menu#MENU_GROUP} class manages the main menus for coalitions. -- You can add menus with the @{#MENU_GROUP.New} method, which constructs a MENU_GROUP object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_GROUP.Remove}. -- --- 4.2) @{Menu#MENU_GROUP_COMMAND} class, extends @{Menu#MENU_COMMAND_BASE} +-- 4.2) @{Menu#MENU_GROUP_COMMAND} class, extends @{Core.Menu#MENU_COMMAND_BASE} -- ------------------------------------------------------------------------ --- The @{Menu#MENU_GROUP_COMMAND} class manages the command menus for coalitions, which allow players to execute functions during mission execution. +-- The @{Core.Menu#MENU_GROUP_COMMAND} class manages the command menus for coalitions, which allow players to execute functions during mission execution. -- You can add menus with the @{#MENU_GROUP_COMMAND.New} method, which constructs a MENU_GROUP_COMMAND object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_GROUP_COMMAND.Remove}. -- @@ -7358,15 +4948,15 @@ end -- ====================== -- The underlying classes manage the menus for units with skill level client or player. -- --- 5.1) @{Menu#MENU_CLIENT} class, extends @{Menu#MENU_BASE} +-- 5.1) @{Menu#MENU_CLIENT} class, extends @{Core.Menu#MENU_BASE} -- --------------------------------------------------------- --- The @{Menu#MENU_CLIENT} class manages the main menus for coalitions. +-- The @{Core.Menu#MENU_CLIENT} class manages the main menus for coalitions. -- You can add menus with the @{#MENU_CLIENT.New} method, which constructs a MENU_CLIENT object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_CLIENT.Remove}. -- --- 5.2) @{Menu#MENU_CLIENT_COMMAND} class, extends @{Menu#MENU_COMMAND_BASE} +-- 5.2) @{Menu#MENU_CLIENT_COMMAND} class, extends @{Core.Menu#MENU_COMMAND_BASE} -- ------------------------------------------------------------------------- --- The @{Menu#MENU_CLIENT_COMMAND} class manages the command menus for coalitions, which allow players to execute functions during mission execution. +-- The @{Core.Menu#MENU_CLIENT_COMMAND} class manages the command menus for coalitions, which allow players to execute functions during mission execution. -- You can add menus with the @{#MENU_CLIENT_COMMAND.New} method, which constructs a MENU_CLIENT_COMMAND object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_CLIENT_COMMAND.Remove}. -- @@ -7602,7 +5192,7 @@ do -- MENU_COALITION --- MENU_COALITION constructor. Creates a new MENU_COALITION object and creates the menu for a complete coalition. -- @param #MENU_COALITION self - -- @param DCSCoalition#coalition.side Coalition The coalition owning the menu. + -- @param Dcs.DCSCoalition#coalition.side Coalition The coalition owning the menu. -- @param #string MenuText The text for the menu. -- @param #table ParentMenu The parent menu. This parameter can be ignored if you want the menu to be located at the perent menu of DCS world (under F10 other). -- @return #MENU_COALITION self @@ -7671,7 +5261,7 @@ do -- MENU_COALITION_COMMAND --- MENU_COALITION constructor. Creates a new radio command item for a coalition, which can invoke a function with parameters. -- @param #MENU_COALITION_COMMAND self - -- @param DCSCoalition#coalition.side Coalition The coalition owning the menu. + -- @param Dcs.DCSCoalition#coalition.side Coalition The coalition owning the menu. -- @param #string MenuText The text for the menu. -- @param Menu#MENU_COALITION ParentMenu The parent menu. -- @param CommandMenuFunction A function that is called when the menu key is pressed. @@ -7744,7 +5334,7 @@ do -- MENU_CLIENT -- MenuStatus[MenuClientName]:Remove() -- end -- - -- --- @param Client#CLIENT MenuClient + -- --- @param Wrapper.Client#CLIENT MenuClient -- local function AddStatusMenu( MenuClient ) -- local MenuClientName = MenuClient:GetName() -- -- This would create a menu for the red coalition under the MenuCoalitionRed menu object. @@ -7777,7 +5367,7 @@ do -- MENU_CLIENT --- MENU_CLIENT constructor. Creates a new radio menu item for a client. -- @param #MENU_CLIENT self - -- @param Client#CLIENT Client The Client owning the menu. + -- @param Wrapper.Client#CLIENT Client The Client owning the menu. -- @param #string MenuText The text for the menu. -- @param #table ParentMenu The parent menu. -- @return #MENU_CLIENT self @@ -7869,7 +5459,7 @@ do -- MENU_CLIENT --- MENU_CLIENT_COMMAND constructor. Creates a new radio command item for a client, which can invoke a function with parameters. -- @param #MENU_CLIENT_COMMAND self - -- @param Client#CLIENT Client The Client owning the menu. + -- @param Wrapper.Client#CLIENT Client The Client owning the menu. -- @param #string MenuText The text for the menu. -- @param #MENU_BASE ParentMenu The parent menu. -- @param CommandMenuFunction A function that is called when the menu key is pressed. @@ -7970,7 +5560,7 @@ do -- MenuStatus[MenuGroupName]:Remove() -- end -- - -- --- @param Group#GROUP MenuGroup + -- --- @param Wrapper.Group#GROUP MenuGroup -- local function AddStatusMenu( MenuGroup ) -- local MenuGroupName = MenuGroup:GetName() -- -- This would create a menu for the red coalition under the MenuCoalitionRed menu object. @@ -8004,51 +5594,41 @@ do --- MENU_GROUP constructor. Creates a new radio menu item for a group. -- @param #MENU_GROUP self - -- @param Group#GROUP MenuGroup The Group owning the menu. + -- @param Wrapper.Group#GROUP MenuGroup The Group owning the menu. -- @param #string MenuText The text for the menu. -- @param #table ParentMenu The parent menu. -- @return #MENU_GROUP self function MENU_GROUP:New( MenuGroup, MenuText, ParentMenu ) - -- Arrange meta tables - local MenuParentPath = {} - if ParentMenu ~= nil then - MenuParentPath = ParentMenu.MenuPath - end - - local self = BASE:Inherit( self, MENU_BASE:New( MenuText, MenuParentPath ) ) - self:F( { MenuGroup, MenuText, ParentMenu } ) - - self.MenuGroup = MenuGroup - self.MenuGroupID = MenuGroup:GetID() - self.MenuParentPath = MenuParentPath - self.MenuText = MenuText - self.ParentMenu = ParentMenu + -- Determine if the menu was not already created and already visible at the group. + -- If it is visible, then return the cached self, otherwise, create self and cache it. - self.Menus = {} - - if not _MENUGROUPS[self.MenuGroupID] then - _MENUGROUPS[self.MenuGroupID] = {} + MenuGroup._Menus = MenuGroup._Menus or {} + local Path = ( ParentMenu and ( table.concat( ParentMenu.MenuPath or {}, "@" ) .. "@" .. MenuText ) ) or MenuText + if MenuGroup._Menus[Path] then + self = MenuGroup._Menus[Path] + else + self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) ) + MenuGroup._Menus[Path] = self + + self.Menus = {} + + self.MenuGroup = MenuGroup + self.Path = Path + self.MenuGroupID = MenuGroup:GetID() + self.MenuText = MenuText + self.ParentMenu = ParentMenu + + self:T( { "Adding Menu ", MenuText, self.MenuParentPath } ) + self.MenuPath = missionCommands.addSubMenuForGroup( self.MenuGroupID, MenuText, self.MenuParentPath ) + + if ParentMenu and ParentMenu.Menus then + ParentMenu.Menus[self.MenuPath] = self + end end - local MenuPath = _MENUGROUPS[self.MenuGroupID] - - self:T( { MenuGroup:GetName(), MenuPath[table.concat(MenuParentPath)], MenuParentPath, MenuText } ) - - local MenuPathID = table.concat(MenuParentPath) .. "/" .. MenuText - if MenuPath[MenuPathID] then - missionCommands.removeItemForGroup( self.MenuGroupID, MenuPath[MenuPathID] ) - end - - self:T( { "Adding for MenuPath ", MenuText, MenuParentPath } ) - self.MenuPath = missionCommands.addSubMenuForGroup( self.MenuGroupID, MenuText, MenuParentPath ) - MenuPath[MenuPathID] = self.MenuPath - - self:T( { self.MenuGroupID, self.MenuPath } ) - - if ParentMenu and ParentMenu.Menus then - ParentMenu.Menus[self.MenuPath] = self - end + self:F( { MenuGroup:GetName(), MenuText, ParentMenu.MenuPath } ) + return self end @@ -8068,23 +5648,20 @@ do -- @param #MENU_GROUP self -- @return #nil function MENU_GROUP:Remove() - self:F( self.MenuPath ) + self:F( { self.MenuGroupID, self.MenuPath } ) self:RemoveSubMenus() - if not _MENUGROUPS[self.MenuGroupID] then - _MENUGROUPS[self.MenuGroupID] = {} - end + if self.MenuGroup._Menus[self.Path] then + self = self.MenuGroup._Menus[self.Path] - local MenuPath = _MENUGROUPS[self.MenuGroupID] - - if MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] then - MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] = nil - end - - missionCommands.removeItemForGroup( self.MenuGroupID, self.MenuPath ) - if self.ParentMenu then - self.ParentMenu.Menus[self.MenuPath] = nil + missionCommands.removeItemForGroup( self.MenuGroupID, self.MenuPath ) + if self.ParentMenu then + self.ParentMenu.Menus[self.MenuPath] = nil + end + self:E( self.MenuGroup._Menus[self.Path] ) + self.MenuGroup._Menus[self.Path] = nil + self = nil end return nil end @@ -8099,7 +5676,7 @@ do --- Creates a new radio command item for a group -- @param #MENU_GROUP_COMMAND self - -- @param Group#GROUP MenuGroup The Group owning the menu. + -- @param Wrapper.Group#GROUP MenuGroup The Group owning the menu. -- @param MenuText The text for the menu. -- @param ParentMenu The parent menu. -- @param CommandMenuFunction A function that is called when the menu key is pressed. @@ -8107,32 +5684,30 @@ do -- @return Menu#MENU_GROUP_COMMAND self function MENU_GROUP_COMMAND:New( MenuGroup, MenuText, ParentMenu, CommandMenuFunction, ... ) - local self = BASE:Inherit( self, MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, arg ) ) - - self.MenuGroup = MenuGroup - self.MenuGroupID = MenuGroup:GetID() - self.MenuText = MenuText - self.ParentMenu = ParentMenu - - if not _MENUGROUPS[self.MenuGroupID] then - _MENUGROUPS[self.MenuGroupID] = {} + MenuGroup._Menus = MenuGroup._Menus or {} + local Path = ( ParentMenu and ( table.concat( ParentMenu.MenuPath or {}, "@" ) .. "@" .. MenuText ) ) or MenuText + if MenuGroup._Menus[Path] then + self = MenuGroup._Menus[Path] + else + self = BASE:Inherit( self, MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, arg ) ) + MenuGroup._Menus[Path] = self + + self.Path = Path + self.MenuGroup = MenuGroup + self.MenuGroupID = MenuGroup:GetID() + self.MenuText = MenuText + self.ParentMenu = ParentMenu + + self:T( { "Adding Command Menu ", MenuText, self.MenuParentPath } ) + self.MenuPath = missionCommands.addCommandForGroup( self.MenuGroupID, MenuText, self.MenuParentPath, self.MenuCallHandler, arg ) + + if ParentMenu and ParentMenu.Menus then + ParentMenu.Menus[self.MenuPath] = self + end end - - local MenuPath = _MENUGROUPS[self.MenuGroupID] - - self:T( { MenuGroup:GetName(), MenuPath[table.concat(self.MenuParentPath)], self.MenuParentPath, MenuText, CommandMenuFunction, arg } ) - - local MenuPathID = table.concat(self.MenuParentPath) .. "/" .. MenuText - if MenuPath[MenuPathID] then - missionCommands.removeItemForGroup( self.MenuGroupID, MenuPath[MenuPathID] ) - end - - self:T( { "Adding for MenuPath ", MenuText, self.MenuParentPath } ) - self.MenuPath = missionCommands.addCommandForGroup( self.MenuGroupID, MenuText, self.MenuParentPath, self.MenuCallHandler, arg ) - MenuPath[MenuPathID] = self.MenuPath - - ParentMenu.Menus[self.MenuPath] = self - + + self:F( { MenuGroup:GetName(), MenuText, ParentMenu.MenuPath } ) + return self end @@ -8140,1693 +5715,24 @@ do -- @param #MENU_GROUP_COMMAND self -- @return #nil function MENU_GROUP_COMMAND:Remove() - self:F( self.MenuPath ) - - if not _MENUGROUPS[self.MenuGroupID] then - _MENUGROUPS[self.MenuGroupID] = {} - end - - local MenuPath = _MENUGROUPS[self.MenuGroupID] + self:F( { self.MenuGroupID, self.MenuPath } ) + if self.MenuGroup._Menus[self.Path] then + self = self.MenuGroup._Menus[self.Path] - if MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] then - MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] = nil + missionCommands.removeItemForGroup( self.MenuGroupID, self.MenuPath ) + self.ParentMenu.Menus[self.MenuPath] = nil + self:E( self.MenuGroup._Menus[self.Path] ) + self.MenuGroup._Menus[self.Path] = nil + self = nil end - missionCommands.removeItemForGroup( self.MenuGroupID, self.MenuPath ) - self.ParentMenu.Menus[self.MenuPath] = nil return nil end end ---- This module contains the GROUP class. --- --- 1) @{Group#GROUP} class, extends @{Controllable#CONTROLLABLE} --- ============================================================= --- The @{Group#GROUP} class is a wrapper class to handle the DCS Group objects: --- --- * Support all DCS Group APIs. --- * Enhance with Group specific APIs not in the DCS Group API set. --- * Handle local Group Controller. --- * Manage the "state" of the DCS Group. --- --- **IMPORTANT: ONE SHOULD NEVER SANATIZE these GROUP OBJECT REFERENCES! (make the GROUP object references nil).** --- --- 1.1) GROUP reference methods --- ----------------------- --- For each DCS Group object alive within a running mission, a GROUP wrapper object (instance) will be created within the _@{DATABASE} object. --- This is done at the beginning of the mission (when the mission starts), and dynamically when new DCS Group objects are spawned (using the @{SPAWN} class). --- --- The GROUP class does not contain a :New() method, rather it provides :Find() methods to retrieve the object reference --- using the DCS Group or the DCS GroupName. --- --- Another thing to know is that GROUP objects do not "contain" the DCS Group object. --- The GROUP methods will reference the DCS Group object by name when it is needed during API execution. --- If the DCS Group object does not exist or is nil, the GROUP methods will return nil and log an exception in the DCS.log file. --- --- The GROUP class provides the following functions to retrieve quickly the relevant GROUP instance: --- --- * @{#GROUP.Find}(): Find a GROUP instance from the _DATABASE object using a DCS Group object. --- * @{#GROUP.FindByName}(): Find a GROUP instance from the _DATABASE object using a DCS Group name. --- --- 1.2) GROUP task methods --- ----------------------- --- Several group task methods are available that help you to prepare tasks. --- These methods return a string consisting of the task description, which can then be given to either a --- @{Controllable#CONTROLLABLE.PushTask} or @{Controllable#CONTROLLABLE.SetTask} method to assign the task to the GROUP. --- Tasks are specific for the category of the GROUP, more specific, for AIR, GROUND or AIR and GROUND. --- Each task description where applicable indicates for which group category the task is valid. --- There are 2 main subdivisions of tasks: Assigned tasks and EnRoute tasks. --- --- ### 1.2.1) Assigned task methods --- --- Assigned task methods make the group execute the task where the location of the (possible) targets of the task are known before being detected. --- This is different from the EnRoute tasks, where the targets of the task need to be detected before the task can be executed. --- --- Find below a list of the **assigned task** methods: --- --- * @{Controllable#CONTROLLABLE.TaskAttackGroup}: (AIR) Attack a Group. --- * @{Controllable#CONTROLLABLE.TaskAttackMapObject}: (AIR) Attacking the map object (building, structure, e.t.c). --- * @{Controllable#CONTROLLABLE.TaskAttackUnit}: (AIR) Attack the Unit. --- * @{Controllable#CONTROLLABLE.TaskBombing}: (Controllable#CONTROLLABLEDelivering weapon at the point on the ground. --- * @{Controllable#CONTROLLABLE.TaskBombingRunway}: (AIR) Delivering weapon on the runway. --- * @{Controllable#CONTROLLABLE.TaskEmbarking}: (AIR) Move the group to a Vec2 Point, wait for a defined duration and embark a group. --- * @{Controllable#CONTROLLABLE.TaskEmbarkToTransport}: (GROUND) Embark to a Transport landed at a location. --- * @{Controllable#CONTROLLABLE.TaskEscort}: (AIR) Escort another airborne group. --- * @{Controllable#CONTROLLABLE.TaskFAC_AttackGroup}: (AIR + GROUND) The task makes the group/unit a FAC and orders the FAC to control the target (enemy ground group) destruction. --- * @{Controllable#CONTROLLABLE.TaskFireAtPoint}: (GROUND) Fire at a VEC2 point until ammunition is finished. --- * @{Controllable#CONTROLLABLE.TaskFollow}: (AIR) Following another airborne group. --- * @{Controllable#CONTROLLABLE.TaskHold}: (GROUND) Hold ground group from moving. --- * @{Controllable#CONTROLLABLE.TaskHoldPosition}: (AIR) Hold position at the current position of the first unit of the group. --- * @{Controllable#CONTROLLABLE.TaskLand}: (AIR HELICOPTER) Landing at the ground. For helicopters only. --- * @{Controllable#CONTROLLABLE.TaskLandAtZone}: (AIR) Land the group at a @{Zone#ZONE_RADIUS). --- * @{Controllable#CONTROLLABLE.TaskOrbitCircle}: (AIR) Orbit at the current position of the first unit of the group at a specified alititude. --- * @{Controllable#CONTROLLABLE.TaskOrbitCircleAtVec2}: (AIR) Orbit at a specified position at a specified alititude during a specified duration with a specified speed. --- * @{Controllable#CONTROLLABLE.TaskRefueling}: (AIR) Refueling from the nearest tanker. No parameters. --- * @{Controllable#CONTROLLABLE.TaskRoute}: (AIR + GROUND) Return a Misson task to follow a given route defined by Points. --- * @{Controllable#CONTROLLABLE.TaskRouteToVec2}: (AIR + GROUND) Make the Group move to a given point. --- * @{Controllable#CONTROLLABLE.TaskRouteToVec3}: (AIR + GROUND) Make the Group move to a given point. --- * @{Controllable#CONTROLLABLE.TaskRouteToZone}: (AIR + GROUND) Route the group to a given zone. --- * @{Controllable#CONTROLLABLE.TaskReturnToBase}: (AIR) Route the group to an airbase. --- --- ### 1.2.2) EnRoute task methods --- --- EnRoute tasks require the targets of the task need to be detected by the group (using its sensors) before the task can be executed: --- --- * @{Controllable#CONTROLLABLE.EnRouteTaskAWACS}: (AIR) Aircraft will act as an AWACS for friendly units (will provide them with information about contacts). No parameters. --- * @{Controllable#CONTROLLABLE.EnRouteTaskEngageGroup}: (AIR) Engaging a group. The task does not assign the target group to the unit/group to attack now; it just allows the unit/group to engage the target group as well as other assigned targets. --- * @{Controllable#CONTROLLABLE.EnRouteTaskEngageTargets}: (AIR) Engaging targets of defined types. --- * @{Controllable#CONTROLLABLE.EnRouteTaskEWR}: (AIR) Attack the Unit. --- * @{Controllable#CONTROLLABLE.EnRouteTaskFAC}: (AIR + GROUND) The task makes the group/unit a FAC and lets the FAC to choose a targets (enemy ground group) around as well as other assigned targets. --- * @{Controllable#CONTROLLABLE.EnRouteTaskFAC_EngageGroup}: (AIR + GROUND) The task makes the group/unit a FAC and lets the FAC to choose the target (enemy ground group) as well as other assigned targets. --- * @{Controllable#CONTROLLABLE.EnRouteTaskTanker}: (AIR) Aircraft will act as a tanker for friendly units. No parameters. --- --- ### 1.2.3) Preparation task methods --- --- There are certain task methods that allow to tailor the task behaviour: --- --- * @{Controllable#CONTROLLABLE.TaskWrappedAction}: Return a WrappedAction Task taking a Command. --- * @{Controllable#CONTROLLABLE.TaskCombo}: Return a Combo Task taking an array of Tasks. --- * @{Controllable#CONTROLLABLE.TaskCondition}: Return a condition section for a controlled task. --- * @{Controllable#CONTROLLABLE.TaskControlled}: Return a Controlled Task taking a Task and a TaskCondition. --- --- ### 1.2.4) Obtain the mission from group templates --- --- Group templates contain complete mission descriptions. Sometimes you want to copy a complete mission from a group and assign it to another: --- --- * @{Controllable#CONTROLLABLE.TaskMission}: (AIR + GROUND) Return a mission task from a mission template. --- --- 1.3) GROUP Command methods --- -------------------------- --- Group **command methods** prepare the execution of commands using the @{Controllable#CONTROLLABLE.SetCommand} method: --- --- * @{Controllable#CONTROLLABLE.CommandDoScript}: Do Script command. --- * @{Controllable#CONTROLLABLE.CommandSwitchWayPoint}: Perform a switch waypoint command. --- --- 1.4) GROUP Option methods --- ------------------------- --- Group **Option methods** change the behaviour of the Group while being alive. --- --- ### 1.4.1) Rule of Engagement: --- --- * @{Controllable#CONTROLLABLE.OptionROEWeaponFree} --- * @{Controllable#CONTROLLABLE.OptionROEOpenFire} --- * @{Controllable#CONTROLLABLE.OptionROEReturnFire} --- * @{Controllable#CONTROLLABLE.OptionROEEvadeFire} --- --- To check whether an ROE option is valid for a specific group, use: --- --- * @{Controllable#CONTROLLABLE.OptionROEWeaponFreePossible} --- * @{Controllable#CONTROLLABLE.OptionROEOpenFirePossible} --- * @{Controllable#CONTROLLABLE.OptionROEReturnFirePossible} --- * @{Controllable#CONTROLLABLE.OptionROEEvadeFirePossible} --- --- ### 1.4.2) Rule on thread: --- --- * @{Controllable#CONTROLLABLE.OptionROTNoReaction} --- * @{Controllable#CONTROLLABLE.OptionROTPassiveDefense} --- * @{Controllable#CONTROLLABLE.OptionROTEvadeFire} --- * @{Controllable#CONTROLLABLE.OptionROTVertical} --- --- To test whether an ROT option is valid for a specific group, use: --- --- * @{Controllable#CONTROLLABLE.OptionROTNoReactionPossible} --- * @{Controllable#CONTROLLABLE.OptionROTPassiveDefensePossible} --- * @{Controllable#CONTROLLABLE.OptionROTEvadeFirePossible} --- * @{Controllable#CONTROLLABLE.OptionROTVerticalPossible} --- --- 1.5) GROUP Zone validation methods --- ---------------------------------- --- The group can be validated whether it is completely, partly or not within a @{Zone}. --- Use the following Zone validation methods on the group: --- --- * @{#GROUP.IsCompletelyInZone}: Returns true if all units of the group are within a @{Zone}. --- * @{#GROUP.IsPartlyInZone}: Returns true if some units of the group are within a @{Zone}. --- * @{#GROUP.IsNotInZone}: Returns true if none of the group units of the group are within a @{Zone}. --- --- The zone can be of any @{Zone} class derived from @{Zone#ZONE_BASE}. So, these methods are polymorphic to the zones tested on. --- --- @module Group --- @author FlightControl - ---- The GROUP class --- @type GROUP --- @extends Controllable#CONTROLLABLE --- @field #string GroupName The name of the group. -GROUP = { - ClassName = "GROUP", -} - ---- Create a new GROUP from a DCSGroup --- @param #GROUP self --- @param DCSGroup#Group GroupName The DCS Group name --- @return #GROUP self -function GROUP:Register( GroupName ) - local self = BASE:Inherit( self, CONTROLLABLE:New( GroupName ) ) - self:F2( GroupName ) - self.GroupName = GroupName - return self -end - --- Reference methods. - ---- Find the GROUP wrapper class instance using the DCS Group. --- @param #GROUP self --- @param DCSGroup#Group DCSGroup The DCS Group. --- @return #GROUP The GROUP. -function GROUP:Find( DCSGroup ) - - local GroupName = DCSGroup:getName() -- Group#GROUP - local GroupFound = _DATABASE:FindGroup( GroupName ) - return GroupFound -end - ---- Find the created GROUP using the DCS Group Name. --- @param #GROUP self --- @param #string GroupName The DCS Group Name. --- @return #GROUP The GROUP. -function GROUP:FindByName( GroupName ) - - local GroupFound = _DATABASE:FindGroup( GroupName ) - return GroupFound -end - --- DCS Group methods support. - ---- Returns the DCS Group. --- @param #GROUP self --- @return DCSGroup#Group The DCS Group. -function GROUP:GetDCSObject() - local DCSGroup = Group.getByName( self.GroupName ) - - if DCSGroup then - return DCSGroup - end - - return nil -end - - ---- Returns if the DCS Group is alive. --- When the group exists at run-time, this method will return true, otherwise false. --- @param #GROUP self --- @return #boolean true if the DCS Group is alive. -function GROUP:IsAlive() - self:F2( self.GroupName ) - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local GroupIsAlive = DCSGroup:isExist() - self:T3( GroupIsAlive ) - return GroupIsAlive - end - - return nil -end - ---- Destroys the DCS Group and all of its DCS Units. --- Note that this destroy method also raises a destroy event at run-time. --- So all event listeners will catch the destroy event of this DCS Group. --- @param #GROUP self -function GROUP:Destroy() - self:F2( self.GroupName ) - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - for Index, UnitData in pairs( DCSGroup:getUnits() ) do - self:CreateEventCrash( timer.getTime(), UnitData ) - end - DCSGroup:destroy() - DCSGroup = nil - end - - return nil -end - ---- Returns category of the DCS Group. --- @param #GROUP self --- @return DCSGroup#Group.Category The category ID -function GROUP:GetCategory() - self:F2( self.GroupName ) - - local DCSGroup = self:GetDCSObject() - if DCSGroup then - local GroupCategory = DCSGroup:getCategory() - self:T3( GroupCategory ) - return GroupCategory - end - - return nil -end - ---- Returns the category name of the DCS Group. --- @param #GROUP self --- @return #string Category name = Helicopter, Airplane, Ground Unit, Ship -function GROUP:GetCategoryName() - self:F2( self.GroupName ) - - local DCSGroup = self:GetDCSObject() - if DCSGroup then - local CategoryNames = { - [Group.Category.AIRPLANE] = "Airplane", - [Group.Category.HELICOPTER] = "Helicopter", - [Group.Category.GROUND] = "Ground Unit", - [Group.Category.SHIP] = "Ship", - } - local GroupCategory = DCSGroup:getCategory() - self:T3( GroupCategory ) - - return CategoryNames[GroupCategory] - end - - return nil -end - - ---- Returns the coalition of the DCS Group. --- @param #GROUP self --- @return DCSCoalitionObject#coalition.side The coalition side of the DCS Group. -function GROUP:GetCoalition() - self:F2( self.GroupName ) - - local DCSGroup = self:GetDCSObject() - if DCSGroup then - local GroupCoalition = DCSGroup:getCoalition() - self:T3( GroupCoalition ) - return GroupCoalition - end - - return nil -end - ---- Returns the country of the DCS Group. --- @param #GROUP self --- @return DCScountry#country.id The country identifier. --- @return #nil The DCS Group is not existing or alive. -function GROUP:GetCountry() - self:F2( self.GroupName ) - - local DCSGroup = self:GetDCSObject() - if DCSGroup then - local GroupCountry = DCSGroup:getUnit(1):getCountry() - self:T3( GroupCountry ) - return GroupCountry - end - - return nil -end - ---- Returns the UNIT wrapper class with number UnitNumber. --- If the underlying DCS Unit does not exist, the method will return nil. . --- @param #GROUP self --- @param #number UnitNumber The number of the UNIT wrapper class to be returned. --- @return Unit#UNIT The UNIT wrapper class. -function GROUP:GetUnit( UnitNumber ) - self:F2( { self.GroupName, UnitNumber } ) - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local UnitFound = UNIT:Find( DCSGroup:getUnit( UnitNumber ) ) - self:T3( UnitFound.UnitName ) - self:T2( UnitFound ) - return UnitFound - end - - return nil -end - ---- Returns the DCS Unit with number UnitNumber. --- If the underlying DCS Unit does not exist, the method will return nil. . --- @param #GROUP self --- @param #number UnitNumber The number of the DCS Unit to be returned. --- @return DCSUnit#Unit The DCS Unit. -function GROUP:GetDCSUnit( UnitNumber ) - self:F2( { self.GroupName, UnitNumber } ) - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local DCSUnitFound = DCSGroup:getUnit( UnitNumber ) - self:T3( DCSUnitFound ) - return DCSUnitFound - end - - return nil -end - ---- Returns current size of the DCS Group. --- If some of the DCS Units of the DCS Group are destroyed the size of the DCS Group is changed. --- @param #GROUP self --- @return #number The DCS Group size. -function GROUP:GetSize() - self:F2( { self.GroupName } ) - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local GroupSize = DCSGroup:getSize() - self:T3( GroupSize ) - return GroupSize - end - - return nil -end - ---- ---- Returns the initial size of the DCS Group. --- If some of the DCS Units of the DCS Group are destroyed, the initial size of the DCS Group is unchanged. --- @param #GROUP self --- @return #number The DCS Group initial size. -function GROUP:GetInitialSize() - self:F2( { self.GroupName } ) - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local GroupInitialSize = DCSGroup:getInitialSize() - self:T3( GroupInitialSize ) - return GroupInitialSize - end - - return nil -end - ---- Returns the UNITs wrappers of the DCS Units of the DCS Group. --- @param #GROUP self --- @return #table The UNITs wrappers. -function GROUP:GetUnits() - self:F2( { self.GroupName } ) - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local DCSUnits = DCSGroup:getUnits() - local Units = {} - for Index, UnitData in pairs( DCSUnits ) do - Units[#Units+1] = UNIT:Find( UnitData ) - end - self:T3( Units ) - return Units - end - - return nil -end - - ---- Returns the DCS Units of the DCS Group. --- @param #GROUP self --- @return #table The DCS Units. -function GROUP:GetDCSUnits() - self:F2( { self.GroupName } ) - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local DCSUnits = DCSGroup:getUnits() - self:T3( DCSUnits ) - return DCSUnits - end - - return nil -end - - ---- Activates a GROUP. --- @param #GROUP self -function GROUP:Activate() - self:F2( { self.GroupName } ) - trigger.action.activateGroup( self:GetDCSObject() ) - return self:GetDCSObject() -end - - ---- Gets the type name of the group. --- @param #GROUP self --- @return #string The type name of the group. -function GROUP:GetTypeName() - self:F2( self.GroupName ) - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local GroupTypeName = DCSGroup:getUnit(1):getTypeName() - self:T3( GroupTypeName ) - return( GroupTypeName ) - end - - return nil -end - ---- Gets the CallSign of the first DCS Unit of the DCS Group. --- @param #GROUP self --- @return #string The CallSign of the first DCS Unit of the DCS Group. -function GROUP:GetCallsign() - self:F2( self.GroupName ) - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local GroupCallSign = DCSGroup:getUnit(1):getCallsign() - self:T3( GroupCallSign ) - return GroupCallSign - end - - return nil -end - ---- Returns the current point (Vec2 vector) of the first DCS Unit in the DCS Group. --- @param #GROUP self --- @return DCSTypes#Vec2 Current Vec2 point of the first DCS Unit of the DCS Group. -function GROUP:GetVec2() - self:F2( self.GroupName ) - - local UnitPoint = self:GetUnit(1) - UnitPoint:GetVec2() - local GroupPointVec2 = UnitPoint:GetVec2() - self:T3( GroupPointVec2 ) - return GroupPointVec2 -end - ---- Returns the current Vec3 vector of the first DCS Unit in the GROUP. --- @return DCSTypes#Vec3 Current Vec3 of the first DCS Unit of the GROUP. -function GROUP:GetVec3() - self:F2( self.GroupName ) - - local GroupVec3 = self:GetUnit(1):GetVec3() - self:T3( GroupVec3 ) - return GroupVec3 -end - - - --- Is Zone Functions - ---- Returns true if all units of the group are within a @{Zone}. --- @param #GROUP self --- @param Zone#ZONE_BASE Zone The zone to test. --- @return #boolean Returns true if the Group is completely within the @{Zone#ZONE_BASE} -function GROUP:IsCompletelyInZone( Zone ) - self:F2( { self.GroupName, Zone } ) - - for UnitID, UnitData in pairs( self:GetUnits() ) do - local Unit = UnitData -- Unit#UNIT - -- TODO: Rename IsPointVec3InZone to IsVec3InZone - if Zone:IsPointVec3InZone( Unit:GetVec3() ) then - else - return false - end - end - - return true -end - ---- Returns true if some units of the group are within a @{Zone}. --- @param #GROUP self --- @param Zone#ZONE_BASE Zone The zone to test. --- @return #boolean Returns true if the Group is completely within the @{Zone#ZONE_BASE} -function GROUP:IsPartlyInZone( Zone ) - self:F2( { self.GroupName, Zone } ) - - for UnitID, UnitData in pairs( self:GetUnits() ) do - local Unit = UnitData -- Unit#UNIT - if Zone:IsPointVec3InZone( Unit:GetVec3() ) then - return true - end - end - - return false -end - ---- Returns true if none of the group units of the group are within a @{Zone}. --- @param #GROUP self --- @param Zone#ZONE_BASE Zone The zone to test. --- @return #boolean Returns true if the Group is completely within the @{Zone#ZONE_BASE} -function GROUP:IsNotInZone( Zone ) - self:F2( { self.GroupName, Zone } ) - - for UnitID, UnitData in pairs( self:GetUnits() ) do - local Unit = UnitData -- Unit#UNIT - if Zone:IsPointVec3InZone( Unit:GetVec3() ) then - return false - end - end - - return true -end - ---- Returns if the group is of an air category. --- If the group is a helicopter or a plane, then this method will return true, otherwise false. --- @param #GROUP self --- @return #boolean Air category evaluation result. -function GROUP:IsAir() - self:F2( self.GroupName ) - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local IsAirResult = DCSGroup:getCategory() == Group.Category.AIRPLANE or DCSGroup:getCategory() == Group.Category.HELICOPTER - self:T3( IsAirResult ) - return IsAirResult - end - - return nil -end - ---- Returns if the DCS Group contains Helicopters. --- @param #GROUP self --- @return #boolean true if DCS Group contains Helicopters. -function GROUP:IsHelicopter() - self:F2( self.GroupName ) - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local GroupCategory = DCSGroup:getCategory() - self:T2( GroupCategory ) - return GroupCategory == Group.Category.HELICOPTER - end - - return nil -end - ---- Returns if the DCS Group contains AirPlanes. --- @param #GROUP self --- @return #boolean true if DCS Group contains AirPlanes. -function GROUP:IsAirPlane() - self:F2() - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local GroupCategory = DCSGroup:getCategory() - self:T2( GroupCategory ) - return GroupCategory == Group.Category.AIRPLANE - end - - return nil -end - ---- Returns if the DCS Group contains Ground troops. --- @param #GROUP self --- @return #boolean true if DCS Group contains Ground troops. -function GROUP:IsGround() - self:F2() - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local GroupCategory = DCSGroup:getCategory() - self:T2( GroupCategory ) - return GroupCategory == Group.Category.GROUND - end - - return nil -end - ---- Returns if the DCS Group contains Ships. --- @param #GROUP self --- @return #boolean true if DCS Group contains Ships. -function GROUP:IsShip() - self:F2() - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local GroupCategory = DCSGroup:getCategory() - self:T2( GroupCategory ) - return GroupCategory == Group.Category.SHIP - end - - return nil -end - ---- Returns if all units of the group are on the ground or landed. --- If all units of this group are on the ground, this function will return true, otherwise false. --- @param #GROUP self --- @return #boolean All units on the ground result. -function GROUP:AllOnGround() - self:F2() - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local AllOnGroundResult = true - - for Index, UnitData in pairs( DCSGroup:getUnits() ) do - if UnitData:inAir() then - AllOnGroundResult = false - end - end - - self:T3( AllOnGroundResult ) - return AllOnGroundResult - end - - return nil -end - ---- Returns the current maximum velocity of the group. --- Each unit within the group gets evaluated, and the maximum velocity (= the unit which is going the fastest) is returned. --- @param #GROUP self --- @return #number Maximum velocity found. -function GROUP:GetMaxVelocity() - self:F2() - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local MaxVelocity = 0 - - for Index, UnitData in pairs( DCSGroup:getUnits() ) do - - local Velocity = UnitData:getVelocity() - local VelocityTotal = math.abs( Velocity.x ) + math.abs( Velocity.y ) + math.abs( Velocity.z ) - - if VelocityTotal < MaxVelocity then - MaxVelocity = VelocityTotal - end - end - - return MaxVelocity - end - - return nil -end - ---- Returns the current minimum height of the group. --- Each unit within the group gets evaluated, and the minimum height (= the unit which is the lowest elevated) is returned. --- @param #GROUP self --- @return #number Minimum height found. -function GROUP:GetMinHeight() - self:F2() - -end - ---- Returns the current maximum height of the group. --- Each unit within the group gets evaluated, and the maximum height (= the unit which is the highest elevated) is returned. --- @param #GROUP self --- @return #number Maximum height found. -function GROUP:GetMaxHeight() - self:F2() - -end - --- SPAWNING - ---- Respawn the @{GROUP} using a (tweaked) template of the Group. --- The template must be retrieved with the @{Group#GROUP.GetTemplate}() function. --- The template contains all the definitions as declared within the mission file. --- To understand templates, do the following: --- --- * unpack your .miz file into a directory using 7-zip. --- * browse in the directory created to the file **mission**. --- * open the file and search for the country group definitions. --- --- Your group template will contain the fields as described within the mission file. --- --- This function will: --- --- * Get the current position and heading of the group. --- * When the group is alive, it will tweak the template x, y and heading coordinates of the group and the embedded units to the current units positions. --- * Then it will destroy the current alive group. --- * And it will respawn the group using your new template definition. --- @param Group#GROUP self --- @param #table Template The template of the Group retrieved with GROUP:GetTemplate() -function GROUP:Respawn( Template ) - - local Vec3 = self:GetVec3() - Template.x = Vec3.x - Template.y = Vec3.z - --Template.x = nil - --Template.y = nil - - self:E( #Template.units ) - for UnitID, UnitData in pairs( self:GetUnits() ) do - local GroupUnit = UnitData -- Unit#UNIT - self:E( GroupUnit:GetName() ) - if GroupUnit:IsAlive() then - local GroupUnitVec3 = GroupUnit:GetVec3() - local GroupUnitHeading = GroupUnit:GetHeading() - Template.units[UnitID].alt = GroupUnitVec3.y - Template.units[UnitID].x = GroupUnitVec3.x - Template.units[UnitID].y = GroupUnitVec3.z - Template.units[UnitID].heading = GroupUnitHeading - self:E( { UnitID, Template.units[UnitID], Template.units[UnitID] } ) - end - end - - self:Destroy() - _DATABASE:Spawn( Template ) -end - ---- Returns the group template from the @{DATABASE} (_DATABASE object). --- @param #GROUP self --- @return #table -function GROUP:GetTemplate() - local GroupName = self:GetName() - self:E( GroupName ) - return _DATABASE:GetGroupTemplate( GroupName ) -end - ---- Sets the controlled status in a Template. --- @param #GROUP self --- @param #boolean Controlled true is controlled, false is uncontrolled. --- @return #table -function GROUP:SetTemplateControlled( Template, Controlled ) - Template.uncontrolled = not Controlled - return Template -end - ---- Sets the CountryID of the group in a Template. --- @param #GROUP self --- @param DCScountry#country.id CountryID The country ID. --- @return #table -function GROUP:SetTemplateCountry( Template, CountryID ) - Template.CountryID = CountryID - return Template -end - ---- Sets the CoalitionID of the group in a Template. --- @param #GROUP self --- @param DCSCoalitionObject#coalition.side CoalitionID The coalition ID. --- @return #table -function GROUP:SetTemplateCoalition( Template, CoalitionID ) - Template.CoalitionID = CoalitionID - return Template -end - - - - ---- Return the mission template of the group. --- @param #GROUP self --- @return #table The MissionTemplate -function GROUP:GetTaskMission() - self:F2( self.GroupName ) - - return routines.utils.deepCopy( _DATABASE.Templates.Groups[self.GroupName].Template ) -end - ---- Return the mission route of the group. --- @param #GROUP self --- @return #table The mission route defined by points. -function GROUP:GetTaskRoute() - self:F2( self.GroupName ) - - return routines.utils.deepCopy( _DATABASE.Templates.Groups[self.GroupName].Template.route.points ) -end - ---- Return the route of a group by using the @{Database#DATABASE} class. --- @param #GROUP self --- @param #number Begin The route point from where the copy will start. The base route point is 0. --- @param #number End The route point where the copy will end. The End point is the last point - the End point. The last point has base 0. --- @param #boolean Randomize Randomization of the route, when true. --- @param #number Radius When randomization is on, the randomization is within the radius. -function GROUP:CopyRoute( Begin, End, Randomize, Radius ) - self:F2( { Begin, End } ) - - local Points = {} - - -- Could be a Spawned Group - local GroupName = string.match( self:GetName(), ".*#" ) - if GroupName then - GroupName = GroupName:sub( 1, -2 ) - else - GroupName = self:GetName() - end - - self:T3( { GroupName } ) - - local Template = _DATABASE.Templates.Groups[GroupName].Template - - if Template then - if not Begin then - Begin = 0 - end - if not End then - End = 0 - end - - for TPointID = Begin + 1, #Template.route.points - End do - if Template.route.points[TPointID] then - Points[#Points+1] = routines.utils.deepCopy( Template.route.points[TPointID] ) - if Randomize then - if not Radius then - Radius = 500 - end - Points[#Points].x = Points[#Points].x + math.random( Radius * -1, Radius ) - Points[#Points].y = Points[#Points].y + math.random( Radius * -1, Radius ) - end - end - end - return Points - else - error( "Template not found for Group : " .. GroupName ) - end - - return nil -end - - ---- This module contains the UNIT class. --- --- 1) @{Unit#UNIT} class, extends @{Controllable#CONTROLLABLE} --- =========================================================== --- The @{Unit#UNIT} class is a wrapper class to handle the DCS Unit objects: --- --- * Support all DCS Unit APIs. --- * Enhance with Unit specific APIs not in the DCS Unit API set. --- * Handle local Unit Controller. --- * Manage the "state" of the DCS Unit. --- --- --- 1.1) UNIT reference methods --- ---------------------- --- For each DCS Unit object alive within a running mission, a UNIT wrapper object (instance) will be created within the _@{DATABASE} object. --- This is done at the beginning of the mission (when the mission starts), and dynamically when new DCS Unit objects are spawned (using the @{SPAWN} class). --- --- The UNIT class **does not contain a :New()** method, rather it provides **:Find()** methods to retrieve the object reference --- using the DCS Unit or the DCS UnitName. --- --- Another thing to know is that UNIT objects do not "contain" the DCS Unit object. --- The UNIT methods will reference the DCS Unit object by name when it is needed during API execution. --- If the DCS Unit object does not exist or is nil, the UNIT methods will return nil and log an exception in the DCS.log file. --- --- The UNIT class provides the following functions to retrieve quickly the relevant UNIT instance: --- --- * @{#UNIT.Find}(): Find a UNIT instance from the _DATABASE object using a DCS Unit object. --- * @{#UNIT.FindByName}(): Find a UNIT instance from the _DATABASE object using a DCS Unit name. --- --- IMPORTANT: ONE SHOULD NEVER SANATIZE these UNIT OBJECT REFERENCES! (make the UNIT object references nil). --- --- 1.2) DCS UNIT APIs --- ------------------ --- The DCS Unit APIs are used extensively within MOOSE. The UNIT class has for each DCS Unit API a corresponding method. --- To be able to distinguish easily in your code the difference between a UNIT API call and a DCS Unit API call, --- the first letter of the method is also capitalized. So, by example, the DCS Unit method @{DCSUnit#Unit.getName}() --- is implemented in the UNIT class as @{#UNIT.GetName}(). --- --- 1.3) Smoke, Flare Units --- ----------------------- --- The UNIT class provides methods to smoke or flare units easily. --- The @{#UNIT.SmokeBlue}(), @{#UNIT.SmokeGreen}(),@{#UNIT.SmokeOrange}(), @{#UNIT.SmokeRed}(), @{#UNIT.SmokeRed}() methods --- will smoke the unit in the corresponding color. Note that smoking a unit is done at the current position of the DCS Unit. --- When the DCS Unit moves for whatever reason, the smoking will still continue! --- The @{#UNIT.FlareGreen}(), @{#UNIT.FlareRed}(), @{#UNIT.FlareWhite}(), @{#UNIT.FlareYellow}() --- methods will fire off a flare in the air with the corresponding color. Note that a flare is a one-off shot and its effect is of very short duration. --- --- 1.4) Location Position, Point --- ----------------------------- --- The UNIT class provides methods to obtain the current point or position of the DCS Unit. --- The @{#UNIT.GetPointVec2}(), @{#UNIT.GetVec3}() will obtain the current **location** of the DCS Unit in a Vec2 (2D) or a **point** in a Vec3 (3D) vector respectively. --- If you want to obtain the complete **3D position** including ori�ntation and direction vectors, consult the @{#UNIT.GetPositionVec3}() method respectively. --- --- 1.5) Test if alive --- ------------------ --- The @{#UNIT.IsAlive}(), @{#UNIT.IsActive}() methods determines if the DCS Unit is alive, meaning, it is existing and active. --- --- 1.6) Test for proximity --- ----------------------- --- The UNIT class contains methods to test the location or proximity against zones or other objects. --- --- ### 1.6.1) Zones --- To test whether the Unit is within a **zone**, use the @{#UNIT.IsInZone}() or the @{#UNIT.IsNotInZone}() methods. Any zone can be tested on, but the zone must be derived from @{Zone#ZONE_BASE}. --- --- ### 1.6.2) Units --- Test if another DCS Unit is within a given radius of the current DCS Unit, use the @{#UNIT.OtherUnitInRadius}() method. --- --- @module Unit --- @author FlightControl - - - - - ---- The UNIT class --- @type UNIT --- @extends Controllable#CONTROLLABLE --- @field #UNIT.FlareColor FlareColor --- @field #UNIT.SmokeColor SmokeColor -UNIT = { - ClassName="UNIT", - FlareColor = { - Green = trigger.flareColor.Green, - Red = trigger.flareColor.Red, - White = trigger.flareColor.White, - Yellow = trigger.flareColor.Yellow - }, - SmokeColor = { - Green = trigger.smokeColor.Green, - Red = trigger.smokeColor.Red, - White = trigger.smokeColor.White, - Orange = trigger.smokeColor.Orange, - Blue = trigger.smokeColor.Blue - }, - } - ---- FlareColor --- @type UNIT.FlareColor --- @field Green --- @field Red --- @field White --- @field Yellow - ---- SmokeColor --- @type UNIT.SmokeColor --- @field Green --- @field Red --- @field White --- @field Orange --- @field Blue - ---- Unit.SensorType --- @type Unit.SensorType --- @field OPTIC --- @field RADAR --- @field IRST --- @field RWR - - --- Registration. - ---- Create a new UNIT from DCSUnit. --- @param #UNIT self --- @param #string UnitName The name of the DCS unit. --- @return Unit#UNIT -function UNIT:Register( UnitName ) - local self = BASE:Inherit( self, CONTROLLABLE:New( UnitName ) ) - self.UnitName = UnitName - return self -end - --- Reference methods. - ---- Finds a UNIT from the _DATABASE using a DCSUnit object. --- @param #UNIT self --- @param DCSUnit#Unit DCSUnit An existing DCS Unit object reference. --- @return Unit#UNIT self -function UNIT:Find( DCSUnit ) - - local UnitName = DCSUnit:getName() - local UnitFound = _DATABASE:FindUnit( UnitName ) - return UnitFound -end - ---- Find a UNIT in the _DATABASE using the name of an existing DCS Unit. --- @param #UNIT self --- @param #string UnitName The Unit Name. --- @return Unit#UNIT self -function UNIT:FindByName( UnitName ) - - local UnitFound = _DATABASE:FindUnit( UnitName ) - return UnitFound -end - ---- Return the name of the UNIT. --- @param #UNIT self --- @return #string The UNIT name. -function UNIT:Name() - - return self.UnitName -end - - ---- @param #UNIT self --- @return DCSUnit#Unit -function UNIT:GetDCSObject() - - local DCSUnit = Unit.getByName( self.UnitName ) - - if DCSUnit then - return DCSUnit - end - - return nil -end - ---- Respawn the @{Unit} using a (tweaked) template of the parent Group. --- --- This function will: --- --- * Get the current position and heading of the group. --- * When the unit is alive, it will tweak the template x, y and heading coordinates of the group and the embedded units to the current units positions. --- * Then it will respawn the re-modelled group. --- --- @param Unit#UNIT self --- @param DCSTypes#Vec3 SpawnVec3 The position where to Spawn the new Unit at. --- @param #number Heading The heading of the unit respawn. -function UNIT:ReSpawn( SpawnVec3, Heading ) - - local SpawnGroupTemplate = UTILS.DeepCopy( _DATABASE:GetGroupTemplateFromUnitName( self:Name() ) ) - self:T( SpawnGroupTemplate ) - local SpawnGroup = self:GetGroup() - - if SpawnGroup then - - local Vec3 = SpawnGroup:GetVec3() - SpawnGroupTemplate.x = Vec3.x - SpawnGroupTemplate.y = Vec3.z - - self:E( #SpawnGroupTemplate.units ) - for UnitID, UnitData in pairs( SpawnGroup:GetUnits() ) do - local GroupUnit = UnitData -- Unit#UNIT - self:E( GroupUnit:GetName() ) - if GroupUnit:IsAlive() then - local GroupUnitVec3 = GroupUnit:GetVec3() - local GroupUnitHeading = GroupUnit:GetHeading() - SpawnGroupTemplate.units[UnitID].alt = GroupUnitVec3.y - SpawnGroupTemplate.units[UnitID].x = GroupUnitVec3.x - SpawnGroupTemplate.units[UnitID].y = GroupUnitVec3.z - SpawnGroupTemplate.units[UnitID].heading = GroupUnitHeading - self:E( { UnitID, SpawnGroupTemplate.units[UnitID], SpawnGroupTemplate.units[UnitID] } ) - end - end - end - - for UnitTemplateID, UnitTemplateData in pairs( SpawnGroupTemplate.units ) do - self:T( UnitTemplateData.name ) - if UnitTemplateData.name == self:Name() then - self:T("Adjusting") - SpawnGroupTemplate.units[UnitTemplateID].alt = SpawnVec3.y - SpawnGroupTemplate.units[UnitTemplateID].x = SpawnVec3.x - SpawnGroupTemplate.units[UnitTemplateID].y = SpawnVec3.z - SpawnGroupTemplate.units[UnitTemplateID].heading = Heading - self:E( { UnitTemplateID, SpawnGroupTemplate.units[UnitTemplateID], SpawnGroupTemplate.units[UnitTemplateID] } ) - end - end - - _DATABASE:Spawn( SpawnGroupTemplate ) -end - - - ---- Returns if the unit is activated. --- @param Unit#UNIT self --- @return #boolean true if Unit is activated. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:IsActive() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - - local UnitIsActive = DCSUnit:isActive() - return UnitIsActive - end - - return nil -end - - - ---- Returns the Unit's callsign - the localized string. --- @param Unit#UNIT self --- @return #string The Callsign of the Unit. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetCallsign() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitCallSign = DCSUnit:getCallsign() - return UnitCallSign - end - - self:E( self.ClassName .. " " .. self.UnitName .. " not found!" ) - return nil -end - - ---- Returns name of the player that control the unit or nil if the unit is controlled by A.I. --- @param Unit#UNIT self --- @return #string Player Name --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetPlayerName() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - - local PlayerName = DCSUnit:getPlayerName() - if PlayerName == nil then - PlayerName = "" - end - return PlayerName - end - - return nil -end - ---- Returns the unit's number in the group. --- The number is the same number the unit has in ME. --- It may not be changed during the mission. --- If any unit in the group is destroyed, the numbers of another units will not be changed. --- @param Unit#UNIT self --- @return #number The Unit number. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetNumber() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitNumber = DCSUnit:getNumber() - return UnitNumber - end - - return nil -end - ---- Returns the unit's group if it exist and nil otherwise. --- @param Unit#UNIT self --- @return Group#GROUP The Group of the Unit. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetGroup() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitGroup = GROUP:Find( DCSUnit:getGroup() ) - return UnitGroup - end - - return nil -end - - --- Need to add here functions to check if radar is on and which object etc. - ---- Returns the prefix name of the DCS Unit. A prefix name is a part of the name before a '#'-sign. --- DCS Units spawned with the @{SPAWN} class contain a '#'-sign to indicate the end of the (base) DCS Unit name. --- The spawn sequence number and unit number are contained within the name after the '#' sign. --- @param Unit#UNIT self --- @return #string The name of the DCS Unit. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetPrefix() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitPrefix = string.match( self.UnitName, ".*#" ):sub( 1, -2 ) - self:T3( UnitPrefix ) - return UnitPrefix - end - - return nil -end - ---- Returns the Unit's ammunition. --- @param Unit#UNIT self --- @return DCSUnit#Unit.Ammo --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetAmmo() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitAmmo = DCSUnit:getAmmo() - return UnitAmmo - end - - return nil -end - ---- Returns the unit sensors. --- @param Unit#UNIT self --- @return DCSUnit#Unit.Sensors --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetSensors() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitSensors = DCSUnit:getSensors() - return UnitSensors - end - - return nil -end - --- Need to add here a function per sensortype --- unit:hasSensors(Unit.SensorType.RADAR, Unit.RadarType.AS) - ---- Returns if the unit has sensors of a certain type. --- @param Unit#UNIT self --- @return #boolean returns true if the unit has specified types of sensors. This function is more preferable than Unit.getSensors() if you don't want to get information about all the unit's sensors, and just want to check if the unit has specified types of sensors. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:HasSensors( ... ) - self:F2( arg ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local HasSensors = DCSUnit:hasSensors( unpack( arg ) ) - return HasSensors - end - - return nil -end - ---- Returns if the unit is SEADable. --- @param Unit#UNIT self --- @return #boolean returns true if the unit is SEADable. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:HasSEAD() - self:F2() - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitSEADAttributes = DCSUnit:getDesc().attributes - - local HasSEAD = false - if UnitSEADAttributes["RADAR_BAND1_FOR_ARM"] and UnitSEADAttributes["RADAR_BAND1_FOR_ARM"] == true or - UnitSEADAttributes["RADAR_BAND2_FOR_ARM"] and UnitSEADAttributes["RADAR_BAND2_FOR_ARM"] == true then - HasSEAD = true - end - return HasSEAD - end - - return nil -end - ---- Returns two values: --- --- * First value indicates if at least one of the unit's radar(s) is on. --- * Second value is the object of the radar's interest. Not nil only if at least one radar of the unit is tracking a target. --- @param Unit#UNIT self --- @return #boolean Indicates if at least one of the unit's radar(s) is on. --- @return DCSObject#Object The object of the radar's interest. Not nil only if at least one radar of the unit is tracking a target. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetRadar() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitRadarOn, UnitRadarObject = DCSUnit:getRadar() - return UnitRadarOn, UnitRadarObject - end - - return nil, nil -end - ---- Returns relative amount of fuel (from 0.0 to 1.0) the unit has in its internal tanks. If there are additional fuel tanks the value may be greater than 1.0. --- @param Unit#UNIT self --- @return #number The relative amount of fuel (from 0.0 to 1.0). --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetFuel() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitFuel = DCSUnit:getFuel() - return UnitFuel - end - - return nil -end - ---- Returns the unit's health. Dead units has health <= 1.0. --- @param Unit#UNIT self --- @return #number The Unit's health value. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetLife() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitLife = DCSUnit:getLife() - return UnitLife - end - - return nil -end - ---- Returns the Unit's initial health. --- @param Unit#UNIT self --- @return #number The Unit's initial health value. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetLife0() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitLife0 = DCSUnit:getLife0() - return UnitLife0 - end - - return nil -end - ---- Returns the Unit's A2G threat level on a scale from 1 to 10 ... --- The following threat levels are foreseen: --- --- * Threat level 0: Unit is unarmed. --- * Threat level 1: Unit is infantry. --- * Threat level 2: Unit is an infantry vehicle. --- * Threat level 3: Unit is ground artillery. --- * Threat level 4: Unit is a tank. --- * Threat level 5: Unit is a modern tank or ifv with ATGM. --- * Threat level 6: Unit is a AAA. --- * Threat level 7: Unit is a SAM or manpad, IR guided. --- * Threat level 8: Unit is a Short Range SAM, radar guided. --- * Threat level 9: Unit is a Medium Range SAM, radar guided. --- * Threat level 10: Unit is a Long Range SAM, radar guided. -function UNIT:GetThreatLevel() - - local Attributes = self:GetDesc().attributes - local ThreatLevel = 0 - - local ThreatLevels = { - "Unarmed", - "Infantry", - "Old Tanks & APCs", - "Tanks & IFVs without ATGM", - "Tanks & IFV with ATGM", - "Modern Tanks", - "AAA", - "IR Guided SAMs", - "SR SAMs", - "MR SAMs", - "LR SAMs" - } - - self:T2( Attributes ) - - if Attributes["LR SAM"] then ThreatLevel = 10 - elseif Attributes["MR SAM"] then ThreatLevel = 9 - elseif Attributes["SR SAM"] and - not Attributes["IR Guided SAM"] then ThreatLevel = 8 - elseif ( Attributes["SR SAM"] or Attributes["MANPADS"] ) and - Attributes["IR Guided SAM"] then ThreatLevel = 7 - elseif Attributes["AAA"] then ThreatLevel = 6 - elseif Attributes["Modern Tanks"] then ThreatLevel = 5 - elseif ( Attributes["Tanks"] or Attributes["IFV"] ) and - Attributes["ATGM"] then ThreatLevel = 4 - elseif ( Attributes["Tanks"] or Attributes["IFV"] ) and - not Attributes["ATGM"] then ThreatLevel = 3 - elseif Attributes["Old Tanks"] or Attributes["APC"] then ThreatLevel = 2 - elseif Attributes["Infantry"] then ThreatLevel = 1 - end - - self:T2( ThreatLevel ) - return ThreatLevel, ThreatLevels[ThreatLevel+1] - -end - - --- Is functions - ---- Returns true if the unit is within a @{Zone}. --- @param #UNIT self --- @param Zone#ZONE_BASE Zone The zone to test. --- @return #boolean Returns true if the unit is within the @{Zone#ZONE_BASE} -function UNIT:IsInZone( Zone ) - self:F2( { self.UnitName, Zone } ) - - if self:IsAlive() then - local IsInZone = Zone:IsPointVec3InZone( self:GetVec3() ) - - self:T( { IsInZone } ) - return IsInZone - end - - return false -end - ---- Returns true if the unit is not within a @{Zone}. --- @param #UNIT self --- @param Zone#ZONE_BASE Zone The zone to test. --- @return #boolean Returns true if the unit is not within the @{Zone#ZONE_BASE} -function UNIT:IsNotInZone( Zone ) - self:F2( { self.UnitName, Zone } ) - - if self:IsAlive() then - local IsInZone = not Zone:IsPointVec3InZone( self:GetVec3() ) - - self:T( { IsInZone } ) - return IsInZone - else - return false - end -end - - ---- Returns true if there is an **other** DCS Unit within a radius of the current 2D point of the DCS Unit. --- @param Unit#UNIT self --- @param Unit#UNIT AwaitUnit The other UNIT wrapper object. --- @param Radius The radius in meters with the DCS Unit in the centre. --- @return true If the other DCS Unit is within the radius of the 2D point of the DCS Unit. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:OtherUnitInRadius( AwaitUnit, Radius ) - self:F2( { self.UnitName, AwaitUnit.UnitName, Radius } ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitVec3 = self:GetVec3() - local AwaitUnitVec3 = AwaitUnit:GetVec3() - - if (((UnitVec3.x - AwaitUnitVec3.x)^2 + (UnitVec3.z - AwaitUnitVec3.z)^2)^0.5 <= Radius) then - self:T3( "true" ) - return true - else - self:T3( "false" ) - return false - end - end - - return nil -end - - - ---- Signal a flare at the position of the UNIT. --- @param #UNIT self -function UNIT:Flare( FlareColor ) - self:F2() - trigger.action.signalFlare( self:GetVec3(), FlareColor , 0 ) -end - ---- Signal a white flare at the position of the UNIT. --- @param #UNIT self -function UNIT:FlareWhite() - self:F2() - trigger.action.signalFlare( self:GetVec3(), trigger.flareColor.White , 0 ) -end - ---- Signal a yellow flare at the position of the UNIT. --- @param #UNIT self -function UNIT:FlareYellow() - self:F2() - trigger.action.signalFlare( self:GetVec3(), trigger.flareColor.Yellow , 0 ) -end - ---- Signal a green flare at the position of the UNIT. --- @param #UNIT self -function UNIT:FlareGreen() - self:F2() - trigger.action.signalFlare( self:GetVec3(), trigger.flareColor.Green , 0 ) -end - ---- Signal a red flare at the position of the UNIT. --- @param #UNIT self -function UNIT:FlareRed() - self:F2() - local Vec3 = self:GetVec3() - if Vec3 then - trigger.action.signalFlare( Vec3, trigger.flareColor.Red, 0 ) - end -end - ---- Smoke the UNIT. --- @param #UNIT self -function UNIT:Smoke( SmokeColor, Range ) - self:F2() - if Range then - trigger.action.smoke( self:GetRandomVec3( Range ), SmokeColor ) - else - trigger.action.smoke( self:GetVec3(), SmokeColor ) - end - -end - ---- Smoke the UNIT Green. --- @param #UNIT self -function UNIT:SmokeGreen() - self:F2() - trigger.action.smoke( self:GetVec3(), trigger.smokeColor.Green ) -end - ---- Smoke the UNIT Red. --- @param #UNIT self -function UNIT:SmokeRed() - self:F2() - trigger.action.smoke( self:GetVec3(), trigger.smokeColor.Red ) -end - ---- Smoke the UNIT White. --- @param #UNIT self -function UNIT:SmokeWhite() - self:F2() - trigger.action.smoke( self:GetVec3(), trigger.smokeColor.White ) -end - ---- Smoke the UNIT Orange. --- @param #UNIT self -function UNIT:SmokeOrange() - self:F2() - trigger.action.smoke( self:GetVec3(), trigger.smokeColor.Orange ) -end - ---- Smoke the UNIT Blue. --- @param #UNIT self -function UNIT:SmokeBlue() - self:F2() - trigger.action.smoke( self:GetVec3(), trigger.smokeColor.Blue ) -end - --- Is methods - ---- Returns if the unit is of an air category. --- If the unit is a helicopter or a plane, then this method will return true, otherwise false. --- @param #UNIT self --- @return #boolean Air category evaluation result. -function UNIT:IsAir() - self:F2() - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitDescriptor = DCSUnit:getDesc() - self:T3( { UnitDescriptor.category, Unit.Category.AIRPLANE, Unit.Category.HELICOPTER } ) - - local IsAirResult = ( UnitDescriptor.category == Unit.Category.AIRPLANE ) or ( UnitDescriptor.category == Unit.Category.HELICOPTER ) - - self:T3( IsAirResult ) - return IsAirResult - end - - return nil -end - ---- Returns if the unit is of an ground category. --- If the unit is a ground vehicle or infantry, this method will return true, otherwise false. --- @param #UNIT self --- @return #boolean Ground category evaluation result. -function UNIT:IsGround() - self:F2() - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitDescriptor = DCSUnit:getDesc() - self:T3( { UnitDescriptor.category, Unit.Category.GROUND_UNIT } ) - - local IsGroundResult = ( UnitDescriptor.category == Unit.Category.GROUND_UNIT ) - - self:T3( IsGroundResult ) - return IsGroundResult - end - - return nil -end - ---- Returns if the unit is a friendly unit. --- @param #UNIT self --- @return #boolean IsFriendly evaluation result. -function UNIT:IsFriendly( FriendlyCoalition ) - self:F2() - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitCoalition = DCSUnit:getCoalition() - self:T3( { UnitCoalition, FriendlyCoalition } ) - - local IsFriendlyResult = ( UnitCoalition == FriendlyCoalition ) - - self:E( IsFriendlyResult ) - return IsFriendlyResult - end - - return nil -end - ---- Returns if the unit is of a ship category. --- If the unit is a ship, this method will return true, otherwise false. --- @param #UNIT self --- @return #boolean Ship category evaluation result. -function UNIT:IsShip() - self:F2() - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitDescriptor = DCSUnit:getDesc() - self:T3( { UnitDescriptor.category, Unit.Category.SHIP } ) - - local IsShipResult = ( UnitDescriptor.category == Unit.Category.SHIP ) - - self:T3( IsShipResult ) - return IsShipResult - end - - return nil -end - ---- This module contains the ZONE classes, inherited from @{Zone#ZONE_BASE}. +--- This module contains the ZONE classes, inherited from @{Core.Zone#ZONE_BASE}. -- There are essentially two core functions that zones accomodate: -- -- * Test if an object is within the zone boundaries. @@ -9835,7 +5741,7 @@ end -- The object classes are using the zone classes to test the zone boundaries, which can take various forms: -- -- * Test if completely within the zone. --- * Test if partly within the zone (for @{Group#GROUP} objects). +-- * Test if partly within the zone (for @{Wrapper.Group#GROUP} objects). -- * Test if not in the zone. -- * Distance to the nearest intersecting point of the zone. -- * Distance to the center of the zone. @@ -9843,53 +5749,125 @@ end -- -- Each of these ZONE classes have a zone name, and specific parameters defining the zone type: -- --- * @{Zone#ZONE_BASE}: The ZONE_BASE class defining the base for all other zone classes. --- * @{Zone#ZONE_RADIUS}: The ZONE_RADIUS class defined by a zone name, a location and a radius. --- * @{Zone#ZONE}: The ZONE class, defined by the zone name as defined within the Mission Editor. --- * @{Zone#ZONE_UNIT}: The ZONE_UNIT class defines by a zone around a @{Unit#UNIT} with a radius. --- * @{Zone#ZONE_GROUP}: The ZONE_GROUP class defines by a zone around a @{Group#GROUP} with a radius. --- * @{Zone#ZONE_POLYGON}: The ZONE_POLYGON class defines by a sequence of @{Group#GROUP} waypoints within the Mission Editor, forming a polygon. --- --- Each zone implements two polymorphic functions defined in @{Zone#ZONE_BASE}: --- --- * @{#ZONE_BASE.IsPointVec2InZone}: Returns if a location is within the zone. --- * @{#ZONE_BASE.IsPointVec3InZone}: Returns if a point is within the zone. +-- * @{Core.Zone#ZONE_BASE}: The ZONE_BASE class defining the base for all other zone classes. +-- * @{Core.Zone#ZONE_RADIUS}: The ZONE_RADIUS class defined by a zone name, a location and a radius. +-- * @{Core.Zone#ZONE}: The ZONE class, defined by the zone name as defined within the Mission Editor. +-- * @{Core.Zone#ZONE_UNIT}: The ZONE_UNIT class defines by a zone around a @{Wrapper.Unit#UNIT} with a radius. +-- * @{Core.Zone#ZONE_GROUP}: The ZONE_GROUP class defines by a zone around a @{Wrapper.Group#GROUP} with a radius. +-- * @{Core.Zone#ZONE_POLYGON}: The ZONE_POLYGON class defines by a sequence of @{Wrapper.Group#GROUP} waypoints within the Mission Editor, forming a polygon. -- -- === -- --- 1) @{Zone#ZONE_BASE} class, extends @{Base#BASE} +-- 1) @{Core.Zone#ZONE_BASE} class, extends @{Core.Base#BASE} -- ================================================ --- The ZONE_BASE class defining the base for all other zone classes. +-- This class is an abstract BASE class for derived classes, and is not meant to be instantiated. +-- +-- ### 1.1) Each zone has a name: +-- +-- * @{#ZONE_BASE.GetName}(): Returns the name of the zone. +-- +-- ### 1.2) Each zone implements two polymorphic functions defined in @{Core.Zone#ZONE_BASE}: +-- +-- * @{#ZONE_BASE.IsPointVec2InZone}(): Returns if a @{Core.Point#POINT_VEC2} is within the zone. +-- * @{#ZONE_BASE.IsPointVec3InZone}(): Returns if a @{Core.Point#POINT_VEC3} is within the zone. +-- +-- ### 1.3) A zone has a probability factor that can be set to randomize a selection between zones: +-- +-- * @{#ZONE_BASE.SetRandomizeProbability}(): Set the randomization probability of a zone to be selected, taking a value between 0 and 1 ( 0 = 0%, 1 = 100% ) +-- * @{#ZONE_BASE.GetRandomizeProbability}(): Get the randomization probability of a zone to be selected, passing a value between 0 and 1 ( 0 = 0%, 1 = 100% ) +-- * @{#ZONE_BASE.GetZoneMaybe}(): Get the zone taking into account the randomization probability. nil is returned if this zone is not a candidate. +-- +-- ### 1.4) A zone manages Vectors: +-- +-- * @{#ZONE_BASE.GetVec2}(): Returns the @{Dcs.DCSTypes#Vec2} coordinate of the zone. +-- * @{#ZONE_BASE.GetRandomVec2}(): Define a random @{Dcs.DCSTypes#Vec2} within the zone. +-- +-- ### 1.5) A zone has a bounding square: +-- +-- * @{#ZONE_BASE.GetBoundingSquare}(): Get the outer most bounding square of the zone. +-- +-- ### 1.6) A zone can be marked: +-- +-- * @{#ZONE_BASE.SmokeZone}(): Smokes the zone boundaries in a color. +-- * @{#ZONE_BASE.FlareZone}(): Flares the zone boundaries in a color. -- -- === -- --- 2) @{Zone#ZONE_RADIUS} class, extends @{Zone#ZONE_BASE} +-- 2) @{Core.Zone#ZONE_RADIUS} class, extends @{Core.Zone#ZONE_BASE} -- ======================================================= -- The ZONE_RADIUS class defined by a zone name, a location and a radius. +-- This class implements the inherited functions from Core.Zone#ZONE_BASE taking into account the own zone format and properties. +-- +-- ### 2.1) @{Core.Zone#ZONE_RADIUS} constructor: +-- +-- * @{#ZONE_BASE.New}(): Constructor. +-- +-- ### 2.2) Manage the radius of the zone: +-- +-- * @{#ZONE_BASE.SetRadius}(): Sets the radius of the zone. +-- * @{#ZONE_BASE.GetRadius}(): Returns the radius of the zone. +-- +-- ### 2.3) Manage the location of the zone: +-- +-- * @{#ZONE_BASE.SetVec2}(): Sets the @{Dcs.DCSTypes#Vec2} of the zone. +-- * @{#ZONE_BASE.GetVec2}(): Returns the @{Dcs.DCSTypes#Vec2} of the zone. +-- * @{#ZONE_BASE.GetVec3}(): Returns the @{Dcs.DCSTypes#Vec3} of the zone, taking an additional height parameter. -- -- === -- --- 3) @{Zone#ZONE} class, extends @{Zone#ZONE_RADIUS} +-- 3) @{Core.Zone#ZONE} class, extends @{Core.Zone#ZONE_RADIUS} -- ========================================== -- The ZONE class, defined by the zone name as defined within the Mission Editor. +-- This class implements the inherited functions from {Core.Zone#ZONE_RADIUS} taking into account the own zone format and properties. -- -- === -- --- 4) @{Zone#ZONE_UNIT} class, extends @{Zone#ZONE_RADIUS} +-- 4) @{Core.Zone#ZONE_UNIT} class, extends @{Core.Zone#ZONE_RADIUS} -- ======================================================= --- The ZONE_UNIT class defined by a zone around a @{Unit#UNIT} with a radius. +-- The ZONE_UNIT class defined by a zone around a @{Wrapper.Unit#UNIT} with a radius. +-- This class implements the inherited functions from @{Core.Zone#ZONE_RADIUS} taking into account the own zone format and properties. -- -- === -- --- 5) @{Zone#ZONE_GROUP} class, extends @{Zone#ZONE_RADIUS} +-- 5) @{Core.Zone#ZONE_GROUP} class, extends @{Core.Zone#ZONE_RADIUS} -- ======================================================= --- The ZONE_GROUP class defines by a zone around a @{Group#GROUP} with a radius. The current leader of the group defines the center of the zone. +-- The ZONE_GROUP class defines by a zone around a @{Wrapper.Group#GROUP} with a radius. The current leader of the group defines the center of the zone. +-- This class implements the inherited functions from @{Core.Zone#ZONE_RADIUS} taking into account the own zone format and properties. -- -- === -- --- 6) @{Zone#ZONE_POLYGON} class, extends @{Zone#ZONE_BASE} +-- 6) @{Core.Zone#ZONE_POLYGON_BASE} class, extends @{Core.Zone#ZONE_BASE} -- ======================================================== --- The ZONE_POLYGON class defined by a sequence of @{Group#GROUP} waypoints within the Mission Editor, forming a polygon. +-- The ZONE_POLYGON_BASE class defined by a sequence of @{Wrapper.Group#GROUP} waypoints within the Mission Editor, forming a polygon. +-- This class implements the inherited functions from @{Core.Zone#ZONE_RADIUS} taking into account the own zone format and properties. +-- This class is an abstract BASE class for derived classes, and is not meant to be instantiated. +-- +-- === +-- +-- 7) @{Core.Zone#ZONE_POLYGON} class, extends @{Core.Zone#ZONE_POLYGON_BASE} +-- ================================================================ +-- The ZONE_POLYGON class defined by a sequence of @{Wrapper.Group#GROUP} waypoints within the Mission Editor, forming a polygon. +-- This class implements the inherited functions from @{Core.Zone#ZONE_RADIUS} taking into account the own zone format and properties. +-- +-- ==== +-- +-- **API CHANGE HISTORY** +-- ====================== +-- +-- The underlying change log documents the API changes. Please read this carefully. The following notation is used: +-- +-- * **Added** parts are expressed in bold type face. +-- * _Removed_ parts are expressed in italic type face. +-- +-- Hereby the change log: +-- +-- 2016-08-15: ZONE_BASE:**GetName()** added. +-- +-- 2016-08-15: ZONE_BASE:**SetZoneProbability( ZoneProbability )** added. +-- +-- 2016-08-15: ZONE_BASE:**GetZoneProbability()** added. +-- +-- 2016-08-15: ZONE_BASE:**GetZoneMaybe()** added. -- -- === -- @@ -9900,18 +5878,21 @@ end --- The ZONE_BASE class -- @type ZONE_BASE -- @field #string ZoneName Name of the zone. --- @extends Base#BASE +-- @field #number ZoneProbability A value between 0 and 1. 0 = 0% and 1 = 100% probability. +-- @extends Core.Base#BASE ZONE_BASE = { ClassName = "ZONE_BASE", + ZoneName = "", + ZoneProbability = 1, } --- The ZONE_BASE.BoundingSquare -- @type ZONE_BASE.BoundingSquare --- @field DCSTypes#Distance x1 The lower x coordinate (left down) --- @field DCSTypes#Distance y1 The lower y coordinate (left down) --- @field DCSTypes#Distance x2 The higher x coordinate (right up) --- @field DCSTypes#Distance y2 The higher y coordinate (right up) +-- @field Dcs.DCSTypes#Distance x1 The lower x coordinate (left down) +-- @field Dcs.DCSTypes#Distance y1 The lower y coordinate (left down) +-- @field Dcs.DCSTypes#Distance x2 The higher x coordinate (right up) +-- @field Dcs.DCSTypes#Distance y2 The higher y coordinate (right up) --- ZONE_BASE constructor @@ -9927,9 +5908,17 @@ function ZONE_BASE:New( ZoneName ) return self end +--- Returns the name of the zone. +-- @param #ZONE_BASE self +-- @return #string The name of the zone. +function ZONE_BASE:GetName() + self:F2() + + return self.ZoneName +end --- Returns if a location is within the zone. -- @param #ZONE_BASE self --- @param DCSTypes#Vec2 Vec2 The location to test. +-- @param Dcs.DCSTypes#Vec2 Vec2 The location to test. -- @return #boolean true if the location is within the zone. function ZONE_BASE:IsPointVec2InZone( Vec2 ) self:F2( Vec2 ) @@ -9939,7 +5928,7 @@ end --- Returns if a point is within the zone. -- @param #ZONE_BASE self --- @param DCSTypes#Vec3 Vec3 The point to test. +-- @param Dcs.DCSTypes#Vec3 Vec3 The point to test. -- @return #boolean true if the point is within the zone. function ZONE_BASE:IsPointVec3InZone( Vec3 ) self:F2( Vec3 ) @@ -9949,7 +5938,7 @@ function ZONE_BASE:IsPointVec3InZone( Vec3 ) return InZone end ---- Returns the Vec2 coordinate of the zone. +--- Returns the @{Dcs.DCSTypes#Vec2} coordinate of the zone. -- @param #ZONE_BASE self -- @return #nil. function ZONE_BASE:GetVec2() @@ -9957,9 +5946,9 @@ function ZONE_BASE:GetVec2() return nil end ---- Define a random @{DCSTypes#Vec2} within the zone. +--- Define a random @{Dcs.DCSTypes#Vec2} within the zone. -- @param #ZONE_BASE self --- @return #nil The Vec2 coordinates. +-- @return Dcs.DCSTypes#Vec2 The Vec2 coordinates. function ZONE_BASE:GetRandomVec2() return nil end @@ -9975,27 +5964,61 @@ end --- Smokes the zone boundaries in a color. -- @param #ZONE_BASE self --- @param SmokeColor The smoke color. +-- @param Utilities.Utils#SMOKECOLOR SmokeColor The smoke color. function ZONE_BASE:SmokeZone( SmokeColor ) self:F2( SmokeColor ) end +--- Set the randomization probability of a zone to be selected. +-- @param #ZONE_BASE self +-- @param ZoneProbability A value between 0 and 1. 0 = 0% and 1 = 100% probability. +function ZONE_BASE:SetZoneProbability( ZoneProbability ) + self:F2( ZoneProbability ) + + self.ZoneProbability = ZoneProbability or 1 + return self +end + +--- Get the randomization probability of a zone to be selected. +-- @param #ZONE_BASE self +-- @return #number A value between 0 and 1. 0 = 0% and 1 = 100% probability. +function ZONE_BASE:GetZoneProbability() + self:F2() + + return self.ZoneProbability +end + +--- Get the zone taking into account the randomization probability of a zone to be selected. +-- @param #ZONE_BASE self +-- @return #ZONE_BASE The zone is selected taking into account the randomization probability factor. +-- @return #nil The zone is not selected taking into account the randomization probability factor. +function ZONE_BASE:GetZoneMaybe() + self:F2() + + local Randomization = math.random() + if Randomization <= self.ZoneProbability then + return self + else + return nil + end +end + --- The ZONE_RADIUS class, defined by a zone name, a location and a radius. -- @type ZONE_RADIUS --- @field DCSTypes#Vec2 Vec2 The current location of the zone. --- @field DCSTypes#Distance Radius The radius of the zone. --- @extends Zone#ZONE_BASE +-- @field Dcs.DCSTypes#Vec2 Vec2 The current location of the zone. +-- @field Dcs.DCSTypes#Distance Radius The radius of the zone. +-- @extends Core.Zone#ZONE_BASE ZONE_RADIUS = { ClassName="ZONE_RADIUS", } ---- Constructor of ZONE_RADIUS, taking the zone name, the zone location and a radius. +--- Constructor of @{#ZONE_RADIUS}, taking the zone name, the zone location and a radius. -- @param #ZONE_RADIUS self -- @param #string ZoneName Name of the zone. --- @param DCSTypes#Vec2 Vec2 The location of the zone. --- @param DCSTypes#Distance Radius The radius of the zone. +-- @param Dcs.DCSTypes#Vec2 Vec2 The location of the zone. +-- @param Dcs.DCSTypes#Distance Radius The radius of the zone. -- @return #ZONE_RADIUS self function ZONE_RADIUS:New( ZoneName, Vec2, Radius ) local self = BASE:Inherit( self, ZONE_BASE:New( ZoneName ) ) @@ -10009,7 +6032,7 @@ end --- Smokes the zone boundaries in a color. -- @param #ZONE_RADIUS self --- @param #POINT_VEC3.SmokeColor SmokeColor The smoke color. +-- @param Utilities.Utils#SMOKECOLOR SmokeColor The smoke color. -- @param #number Points (optional) The amount of points in the circle. -- @return #ZONE_RADIUS self function ZONE_RADIUS:SmokeZone( SmokeColor, Points ) @@ -10036,9 +6059,9 @@ end --- Flares the zone boundaries in a color. -- @param #ZONE_RADIUS self --- @param #POINT_VEC3.FlareColor FlareColor The flare color. +-- @param Utilities.Utils#FLARECOLOR FlareColor The flare color. -- @param #number Points (optional) The amount of points in the circle. --- @param DCSTypes#Azimuth Azimuth (optional) Azimuth The azimuth of the flare. +-- @param Dcs.DCSTypes#Azimuth Azimuth (optional) Azimuth The azimuth of the flare. -- @return #ZONE_RADIUS self function ZONE_RADIUS:FlareZone( FlareColor, Points, Azimuth ) self:F2( { FlareColor, Azimuth } ) @@ -10063,7 +6086,7 @@ end --- Returns the radius of the zone. -- @param #ZONE_RADIUS self --- @return DCSTypes#Distance The radius of the zone. +-- @return Dcs.DCSTypes#Distance The radius of the zone. function ZONE_RADIUS:GetRadius() self:F2( self.ZoneName ) @@ -10074,8 +6097,8 @@ end --- Sets the radius of the zone. -- @param #ZONE_RADIUS self --- @param DCSTypes#Distance Radius The radius of the zone. --- @return DCSTypes#Distance The radius of the zone. +-- @param Dcs.DCSTypes#Distance Radius The radius of the zone. +-- @return Dcs.DCSTypes#Distance The radius of the zone. function ZONE_RADIUS:SetRadius( Radius ) self:F2( self.ZoneName ) @@ -10085,9 +6108,9 @@ function ZONE_RADIUS:SetRadius( Radius ) return self.Radius end ---- Returns the location of the zone. +--- Returns the @{Dcs.DCSTypes#Vec2} of the zone. -- @param #ZONE_RADIUS self --- @return DCSTypes#Vec2 The location of the zone. +-- @return Dcs.DCSTypes#Vec2 The location of the zone. function ZONE_RADIUS:GetVec2() self:F2( self.ZoneName ) @@ -10096,11 +6119,11 @@ function ZONE_RADIUS:GetVec2() return self.Vec2 end ---- Sets the location of the zone. +--- Sets the @{Dcs.DCSTypes#Vec2} of the zone. -- @param #ZONE_RADIUS self --- @param DCSTypes#Vec2 Vec2 The new location of the zone. --- @return DCSTypes#Vec2 The new location of the zone. -function ZONE_RADIUS:SetPointVec2( Vec2 ) +-- @param Dcs.DCSTypes#Vec2 Vec2 The new location of the zone. +-- @return Dcs.DCSTypes#Vec2 The new location of the zone. +function ZONE_RADIUS:SetVec2( Vec2 ) self:F2( self.ZoneName ) self.Vec2 = Vec2 @@ -10110,10 +6133,10 @@ function ZONE_RADIUS:SetPointVec2( Vec2 ) return self.Vec2 end ---- Returns the @{DCSTypes#Vec3} of the ZONE_RADIUS. +--- Returns the @{Dcs.DCSTypes#Vec3} of the ZONE_RADIUS. -- @param #ZONE_RADIUS self --- @param DCSTypes#Distance Height The height to add to the land height where the center of the zone is located. --- @return DCSTypes#Vec3 The point of the zone. +-- @param Dcs.DCSTypes#Distance Height The height to add to the land height where the center of the zone is located. +-- @return Dcs.DCSTypes#Vec3 The point of the zone. function ZONE_RADIUS:GetVec3( Height ) self:F2( { self.ZoneName, Height } ) @@ -10130,7 +6153,7 @@ end --- Returns if a location is within the zone. -- @param #ZONE_RADIUS self --- @param DCSTypes#Vec2 Vec2 The location to test. +-- @param Dcs.DCSTypes#Vec2 Vec2 The location to test. -- @return #boolean true if the location is within the zone. function ZONE_RADIUS:IsPointVec2InZone( Vec2 ) self:F2( Vec2 ) @@ -10148,7 +6171,7 @@ end --- Returns if a point is within the zone. -- @param #ZONE_RADIUS self --- @param DCSTypes#Vec3 Vec3 The point to test. +-- @param Dcs.DCSTypes#Vec3 Vec3 The point to test. -- @return #boolean true if the point is within the zone. function ZONE_RADIUS:IsPointVec3InZone( Vec3 ) self:F2( Vec3 ) @@ -10160,7 +6183,7 @@ end --- Returns a random location within the zone. -- @param #ZONE_RADIUS self --- @return DCSTypes#Vec2 The random location within the zone. +-- @return Dcs.DCSTypes#Vec2 The random location within the zone. function ZONE_RADIUS:GetRandomVec2() self:F( self.ZoneName ) @@ -10180,7 +6203,7 @@ end --- The ZONE class, defined by the zone name as defined within the Mission Editor. The location and the radius are automatically collected from the mission settings. -- @type ZONE --- @extends Zone#ZONE_RADIUS +-- @extends Core.Zone#ZONE_RADIUS ZONE = { ClassName="ZONE", } @@ -10208,10 +6231,10 @@ function ZONE:New( ZoneName ) end ---- The ZONE_UNIT class defined by a zone around a @{Unit#UNIT} with a radius. +--- The ZONE_UNIT class defined by a zone around a @{Wrapper.Unit#UNIT} with a radius. -- @type ZONE_UNIT --- @field Unit#UNIT ZoneUNIT --- @extends Zone#ZONE_RADIUS +-- @field Wrapper.Unit#UNIT ZoneUNIT +-- @extends Core.Zone#ZONE_RADIUS ZONE_UNIT = { ClassName="ZONE_UNIT", } @@ -10219,8 +6242,8 @@ ZONE_UNIT = { --- Constructor to create a ZONE_UNIT instance, taking the zone name, a zone unit and a radius. -- @param #ZONE_UNIT self -- @param #string ZoneName Name of the zone. --- @param Unit#UNIT ZoneUNIT The unit as the center of the zone. --- @param DCSTypes#Distance Radius The radius of the zone. +-- @param Wrapper.Unit#UNIT ZoneUNIT The unit as the center of the zone. +-- @param Dcs.DCSTypes#Distance Radius The radius of the zone. -- @return #ZONE_UNIT self function ZONE_UNIT:New( ZoneName, ZoneUNIT, Radius ) local self = BASE:Inherit( self, ZONE_RADIUS:New( ZoneName, ZoneUNIT:GetVec2(), Radius ) ) @@ -10233,9 +6256,9 @@ function ZONE_UNIT:New( ZoneName, ZoneUNIT, Radius ) end ---- Returns the current location of the @{Unit#UNIT}. +--- Returns the current location of the @{Wrapper.Unit#UNIT}. -- @param #ZONE_UNIT self --- @return DCSTypes#Vec2 The location of the zone based on the @{Unit#UNIT}location. +-- @return Dcs.DCSTypes#Vec2 The location of the zone based on the @{Wrapper.Unit#UNIT}location. function ZONE_UNIT:GetVec2() self:F( self.ZoneName ) @@ -10254,29 +6277,30 @@ end --- Returns a random location within the zone. -- @param #ZONE_UNIT self --- @return DCSTypes#Vec2 The random location within the zone. +-- @return Dcs.DCSTypes#Vec2 The random location within the zone. function ZONE_UNIT:GetRandomVec2() self:F( self.ZoneName ) - local Point = {} - local PointVec2 = self.ZoneUNIT:GetPointVec2() - if not PointVec2 then - PointVec2 = self.LastVec2 + local RandomVec2 = {} + local Vec2 = self.ZoneUNIT:GetVec2() + + if not Vec2 then + Vec2 = self.LastVec2 end local angle = math.random() * math.pi*2; - Point.x = PointVec2.x + math.cos( angle ) * math.random() * self:GetRadius(); - Point.y = PointVec2.y + math.sin( angle ) * math.random() * self:GetRadius(); + RandomVec2.x = Vec2.x + math.cos( angle ) * math.random() * self:GetRadius(); + RandomVec2.y = Vec2.y + math.sin( angle ) * math.random() * self:GetRadius(); - self:T( { Point } ) + self:T( { RandomVec2 } ) - return Point + return RandomVec2 end ---- Returns the @{DCSTypes#Vec3} of the ZONE_UNIT. +--- Returns the @{Dcs.DCSTypes#Vec3} of the ZONE_UNIT. -- @param #ZONE_UNIT self --- @param DCSTypes#Distance Height The height to add to the land height where the center of the zone is located. --- @return DCSTypes#Vec3 The point of the zone. +-- @param Dcs.DCSTypes#Distance Height The height to add to the land height where the center of the zone is located. +-- @return Dcs.DCSTypes#Vec3 The point of the zone. function ZONE_UNIT:GetVec3( Height ) self:F2( self.ZoneName ) @@ -10293,17 +6317,17 @@ end --- The ZONE_GROUP class defined by a zone around a @{Group}, taking the average center point of all the units within the Group, with a radius. -- @type ZONE_GROUP --- @field Group#GROUP ZoneGROUP --- @extends Zone#ZONE_RADIUS +-- @field Wrapper.Group#GROUP ZoneGROUP +-- @extends Core.Zone#ZONE_RADIUS ZONE_GROUP = { ClassName="ZONE_GROUP", } ---- Constructor to create a ZONE_GROUP instance, taking the zone name, a zone @{Group#GROUP} and a radius. +--- Constructor to create a ZONE_GROUP instance, taking the zone name, a zone @{Wrapper.Group#GROUP} and a radius. -- @param #ZONE_GROUP self -- @param #string ZoneName Name of the zone. --- @param Group#GROUP ZoneGROUP The @{Group} as the center of the zone. --- @param DCSTypes#Distance Radius The radius of the zone. +-- @param Wrapper.Group#GROUP ZoneGROUP The @{Group} as the center of the zone. +-- @param Dcs.DCSTypes#Distance Radius The radius of the zone. -- @return #ZONE_GROUP self function ZONE_GROUP:New( ZoneName, ZoneGROUP, Radius ) local self = BASE:Inherit( self, ZONE_RADIUS:New( ZoneName, ZoneGROUP:GetVec2(), Radius ) ) @@ -10317,7 +6341,7 @@ end --- Returns the current location of the @{Group}. -- @param #ZONE_GROUP self --- @return DCSTypes#Vec2 The location of the zone based on the @{Group} location. +-- @return Dcs.DCSTypes#Vec2 The location of the zone based on the @{Group} location. function ZONE_GROUP:GetVec2() self:F( self.ZoneName ) @@ -10330,7 +6354,7 @@ end --- Returns a random location within the zone of the @{Group}. -- @param #ZONE_GROUP self --- @return DCSTypes#Vec2 The random location of the zone based on the @{Group} location. +-- @return Dcs.DCSTypes#Vec2 The random location of the zone based on the @{Group} location. function ZONE_GROUP:GetRandomVec2() self:F( self.ZoneName ) @@ -10350,23 +6374,23 @@ end -- Polygons ---- The ZONE_POLYGON_BASE class defined by an array of @{DCSTypes#Vec2}, forming a polygon. +--- The ZONE_POLYGON_BASE class defined by an array of @{Dcs.DCSTypes#Vec2}, forming a polygon. -- @type ZONE_POLYGON_BASE --- @field #ZONE_POLYGON_BASE.ListVec2 Polygon The polygon defined by an array of @{DCSTypes#Vec2}. --- @extends Zone#ZONE_BASE +-- @field #ZONE_POLYGON_BASE.ListVec2 Polygon The polygon defined by an array of @{Dcs.DCSTypes#Vec2}. +-- @extends Core.Zone#ZONE_BASE ZONE_POLYGON_BASE = { ClassName="ZONE_POLYGON_BASE", } --- A points array. -- @type ZONE_POLYGON_BASE.ListVec2 --- @list +-- @list ---- Constructor to create a ZONE_POLYGON_BASE instance, taking the zone name and an array of @{DCSTypes#Vec2}, forming a polygon. --- The @{Group#GROUP} waypoints define the polygon corners. The first and the last point are automatically connected. +--- Constructor to create a ZONE_POLYGON_BASE instance, taking the zone name and an array of @{Dcs.DCSTypes#Vec2}, forming a polygon. +-- The @{Wrapper.Group#GROUP} waypoints define the polygon corners. The first and the last point are automatically connected. -- @param #ZONE_POLYGON_BASE self -- @param #string ZoneName Name of the zone. --- @param #ZONE_POLYGON_BASE.ListVec2 PointsArray An array of @{DCSTypes#Vec2}, forming a polygon.. +-- @param #ZONE_POLYGON_BASE.ListVec2 PointsArray An array of @{Dcs.DCSTypes#Vec2}, forming a polygon.. -- @return #ZONE_POLYGON_BASE self function ZONE_POLYGON_BASE:New( ZoneName, PointsArray ) local self = BASE:Inherit( self, ZONE_BASE:New( ZoneName ) ) @@ -10399,7 +6423,7 @@ end --- Smokes the zone boundaries in a color. -- @param #ZONE_POLYGON_BASE self --- @param #POINT_VEC3.SmokeColor SmokeColor The smoke color. +-- @param Utilities.Utils#SMOKECOLOR SmokeColor The smoke color. -- @return #ZONE_POLYGON_BASE self function ZONE_POLYGON_BASE:SmokeZone( SmokeColor ) self:F2( SmokeColor ) @@ -10435,7 +6459,7 @@ end --- Returns if a location is within the zone. -- Source learned and taken from: https://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html -- @param #ZONE_POLYGON_BASE self --- @param DCSTypes#Vec2 Vec2 The location to test. +-- @param Dcs.DCSTypes#Vec2 Vec2 The location to test. -- @return #boolean true if the location is within the zone. function ZONE_POLYGON_BASE:IsPointVec2InZone( Vec2 ) self:F2( Vec2 ) @@ -10463,9 +6487,9 @@ function ZONE_POLYGON_BASE:IsPointVec2InZone( Vec2 ) return InPolygon end ---- Define a random @{DCSTypes#Vec2} within the zone. +--- Define a random @{Dcs.DCSTypes#Vec2} within the zone. -- @param #ZONE_POLYGON_BASE self --- @return DCSTypes#Vec2 The Vec2 coordinate. +-- @return Dcs.DCSTypes#Vec2 The Vec2 coordinate. function ZONE_POLYGON_BASE:GetRandomVec2() self:F2() @@ -10515,18 +6539,18 @@ end ---- The ZONE_POLYGON class defined by a sequence of @{Group#GROUP} waypoints within the Mission Editor, forming a polygon. +--- The ZONE_POLYGON class defined by a sequence of @{Wrapper.Group#GROUP} waypoints within the Mission Editor, forming a polygon. -- @type ZONE_POLYGON --- @extends Zone#ZONE_POLYGON_BASE +-- @extends Core.Zone#ZONE_POLYGON_BASE ZONE_POLYGON = { ClassName="ZONE_POLYGON", } ---- Constructor to create a ZONE_POLYGON instance, taking the zone name and the name of the @{Group#GROUP} defined within the Mission Editor. --- The @{Group#GROUP} waypoints define the polygon corners. The first and the last point are automatically connected by ZONE_POLYGON. +--- Constructor to create a ZONE_POLYGON instance, taking the zone name and the name of the @{Wrapper.Group#GROUP} defined within the Mission Editor. +-- The @{Wrapper.Group#GROUP} waypoints define the polygon corners. The first and the last point are automatically connected by ZONE_POLYGON. -- @param #ZONE_POLYGON self -- @param #string ZoneName Name of the zone. --- @param Group#GROUP ZoneGroup The GROUP waypoints as defined within the Mission Editor define the polygon shape. +-- @param Wrapper.Group#GROUP ZoneGroup The GROUP waypoints as defined within the Mission Editor define the polygon shape. -- @return #ZONE_POLYGON self function ZONE_POLYGON:New( ZoneName, ZoneGroup ) @@ -10538,672 +6562,11 @@ function ZONE_POLYGON:New( ZoneName, ZoneGroup ) return self end ---- This module contains the CLIENT class. --- --- 1) @{Client#CLIENT} class, extends @{Unit#UNIT} --- =============================================== --- Clients are those **Units** defined within the Mission Editor that have the skillset defined as __Client__ or __Player__. --- Note that clients are NOT the same as Units, they are NOT necessarily alive. --- The @{Client#CLIENT} class is a wrapper class to handle the DCS Unit objects that have the skillset defined as __Client__ or __Player__: --- --- * Wraps the DCS Unit objects with skill level set to Player or Client. --- * Support all DCS Unit APIs. --- * Enhance with Unit specific APIs not in the DCS Group API set. --- * When player joins Unit, execute alive init logic. --- * Handles messages to players. --- * Manage the "state" of the DCS Unit. --- --- Clients are being used by the @{MISSION} class to follow players and register their successes. --- --- 1.1) CLIENT reference methods --- ----------------------------- --- For each DCS Unit having skill level Player or Client, a CLIENT wrapper object (instance) will be created within the _@{DATABASE} object. --- This is done at the beginning of the mission (when the mission starts). --- --- The CLIENT class does not contain a :New() method, rather it provides :Find() methods to retrieve the object reference --- using the DCS Unit or the DCS UnitName. --- --- Another thing to know is that CLIENT objects do not "contain" the DCS Unit object. --- The CLIENT methods will reference the DCS Unit object by name when it is needed during API execution. --- If the DCS Unit object does not exist or is nil, the CLIENT methods will return nil and log an exception in the DCS.log file. --- --- The CLIENT class provides the following functions to retrieve quickly the relevant CLIENT instance: --- --- * @{#CLIENT.Find}(): Find a CLIENT instance from the _DATABASE object using a DCS Unit object. --- * @{#CLIENT.FindByName}(): Find a CLIENT instance from the _DATABASE object using a DCS Unit name. --- --- IMPORTANT: ONE SHOULD NEVER SANATIZE these CLIENT OBJECT REFERENCES! (make the CLIENT object references nil). --- --- @module Client --- @author FlightControl - ---- The CLIENT class --- @type CLIENT --- @extends Unit#UNIT -CLIENT = { - ONBOARDSIDE = { - NONE = 0, - LEFT = 1, - RIGHT = 2, - BACK = 3, - FRONT = 4 - }, - ClassName = "CLIENT", - ClientName = nil, - ClientAlive = false, - ClientTransport = false, - ClientBriefingShown = false, - _Menus = {}, - _Tasks = {}, - Messages = { - } -} - - ---- Finds a CLIENT from the _DATABASE using the relevant DCS Unit. --- @param #CLIENT self --- @param #string ClientName Name of the DCS **Unit** as defined within the Mission Editor. --- @param #string ClientBriefing Text that describes the briefing of the mission when a Player logs into the Client. --- @return #CLIENT --- @usage --- -- Create new Clients. --- local Mission = MISSIONSCHEDULER.AddMission( 'Russia Transport Troops SA-6', 'Operational', 'Transport troops from the control center to one of the SA-6 SAM sites to activate their operation.', 'Russia' ) --- Mission:AddGoal( DeploySA6TroopsGoal ) --- --- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*HOT-Deploy Troops 1' ):Transport() ) --- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*RAMP-Deploy Troops 3' ):Transport() ) --- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*HOT-Deploy Troops 2' ):Transport() ) --- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*RAMP-Deploy Troops 4' ):Transport() ) -function CLIENT:Find( DCSUnit ) - local ClientName = DCSUnit:getName() - local ClientFound = _DATABASE:FindClient( ClientName ) - - if ClientFound then - ClientFound:F( ClientName ) - return ClientFound - end - - error( "CLIENT not found for: " .. ClientName ) -end - - ---- Finds a CLIENT from the _DATABASE using the relevant Client Unit Name. --- As an optional parameter, a briefing text can be given also. --- @param #CLIENT self --- @param #string ClientName Name of the DCS **Unit** as defined within the Mission Editor. --- @param #string ClientBriefing Text that describes the briefing of the mission when a Player logs into the Client. --- @return #CLIENT --- @usage --- -- Create new Clients. --- local Mission = MISSIONSCHEDULER.AddMission( 'Russia Transport Troops SA-6', 'Operational', 'Transport troops from the control center to one of the SA-6 SAM sites to activate their operation.', 'Russia' ) --- Mission:AddGoal( DeploySA6TroopsGoal ) --- --- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*HOT-Deploy Troops 1' ):Transport() ) --- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*RAMP-Deploy Troops 3' ):Transport() ) --- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*HOT-Deploy Troops 2' ):Transport() ) --- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*RAMP-Deploy Troops 4' ):Transport() ) -function CLIENT:FindByName( ClientName, ClientBriefing ) - local ClientFound = _DATABASE:FindClient( ClientName ) - - if ClientFound then - ClientFound:F( { ClientName, ClientBriefing } ) - ClientFound:AddBriefing( ClientBriefing ) - ClientFound.MessageSwitch = true - - return ClientFound - end - - error( "CLIENT not found for: " .. ClientName ) -end - -function CLIENT:Register( ClientName ) - local self = BASE:Inherit( self, UNIT:Register( ClientName ) ) - - self:F( ClientName ) - self.ClientName = ClientName - self.MessageSwitch = true - self.ClientAlive2 = false - - --self.AliveCheckScheduler = routines.scheduleFunction( self._AliveCheckScheduler, { self }, timer.getTime() + 1, 5 ) - self.AliveCheckScheduler = SCHEDULER:New( self, self._AliveCheckScheduler, { "Client Alive " .. ClientName }, 1, 5 ) - - self:E( self ) - return self -end - - ---- Transport defines that the Client is a Transport. Transports show cargo. --- @param #CLIENT self --- @return #CLIENT -function CLIENT:Transport() - self:F() - - self.ClientTransport = true - return self -end - ---- AddBriefing adds a briefing to a CLIENT when a player joins a mission. --- @param #CLIENT self --- @param #string ClientBriefing is the text defining the Mission briefing. --- @return #CLIENT self -function CLIENT:AddBriefing( ClientBriefing ) - self:F( ClientBriefing ) - self.ClientBriefing = ClientBriefing - self.ClientBriefingShown = false - - return self -end - ---- Show the briefing of a CLIENT. --- @param #CLIENT self --- @return #CLIENT self -function CLIENT:ShowBriefing() - self:F( { self.ClientName, self.ClientBriefingShown } ) - - if not self.ClientBriefingShown then - self.ClientBriefingShown = true - local Briefing = "" - if self.ClientBriefing then - Briefing = Briefing .. self.ClientBriefing - end - Briefing = Briefing .. " Press [LEFT ALT]+[B] to view the complete mission briefing." - self:Message( Briefing, 60, "Briefing" ) - end - - return self -end - ---- Show the mission briefing of a MISSION to the CLIENT. --- @param #CLIENT self --- @param #string MissionBriefing --- @return #CLIENT self -function CLIENT:ShowMissionBriefing( MissionBriefing ) - self:F( { self.ClientName } ) - - if MissionBriefing then - self:Message( MissionBriefing, 60, "Mission Briefing" ) - end - - return self -end - - - ---- Resets a CLIENT. --- @param #CLIENT self --- @param #string ClientName Name of the Group as defined within the Mission Editor. The Group must have a Unit with the type Client. -function CLIENT:Reset( ClientName ) - self:F() - self._Menus = {} -end - --- Is Functions - ---- Checks if the CLIENT is a multi-seated UNIT. --- @param #CLIENT self --- @return #boolean true if multi-seated. -function CLIENT:IsMultiSeated() - self:F( self.ClientName ) - - local ClientMultiSeatedTypes = { - ["Mi-8MT"] = "Mi-8MT", - ["UH-1H"] = "UH-1H", - ["P-51B"] = "P-51B" - } - - if self:IsAlive() then - local ClientTypeName = self:GetClientGroupUnit():GetTypeName() - if ClientMultiSeatedTypes[ClientTypeName] then - return true - end - end - - return false -end - ---- Checks for a client alive event and calls a function on a continuous basis. --- @param #CLIENT self --- @param #function CallBack Function. --- @return #CLIENT -function CLIENT:Alive( CallBackFunction, ... ) - self:F() - - self.ClientCallBack = CallBackFunction - self.ClientParameters = arg - - return self -end - ---- @param #CLIENT self -function CLIENT:_AliveCheckScheduler( SchedulerName ) - self:F( { SchedulerName, self.ClientName, self.ClientAlive2, self.ClientBriefingShown, self.ClientCallBack } ) - - if self:IsAlive() then - if self.ClientAlive2 == false then - self:ShowBriefing() - if self.ClientCallBack then - self:T("Calling Callback function") - self.ClientCallBack( self, unpack( self.ClientParameters ) ) - end - self.ClientAlive2 = true - end - else - if self.ClientAlive2 == true then - self.ClientAlive2 = false - end - end - - return true -end - ---- Return the DCSGroup of a Client. --- This function is modified to deal with a couple of bugs in DCS 1.5.3 --- @param #CLIENT self --- @return DCSGroup#Group -function CLIENT:GetDCSGroup() - self:F3() - --- local ClientData = Group.getByName( self.ClientName ) --- if ClientData and ClientData:isExist() then --- self:T( self.ClientName .. " : group found!" ) --- return ClientData --- else --- return nil --- end - - local ClientUnit = Unit.getByName( self.ClientName ) - - local CoalitionsData = { AlivePlayersRed = coalition.getPlayers( coalition.side.RED ), AlivePlayersBlue = coalition.getPlayers( coalition.side.BLUE ) } - for CoalitionId, CoalitionData in pairs( CoalitionsData ) do - self:T3( { "CoalitionData:", CoalitionData } ) - for UnitId, UnitData in pairs( CoalitionData ) do - self:T3( { "UnitData:", UnitData } ) - if UnitData and UnitData:isExist() then - - --self:E(self.ClientName) - if ClientUnit then - local ClientGroup = ClientUnit:getGroup() - if ClientGroup then - self:T3( "ClientGroup = " .. self.ClientName ) - if ClientGroup:isExist() and UnitData:getGroup():isExist() then - if ClientGroup:getID() == UnitData:getGroup():getID() then - self:T3( "Normal logic" ) - self:T3( self.ClientName .. " : group found!" ) - self.ClientGroupID = ClientGroup:getID() - self.ClientGroupName = ClientGroup:getName() - return ClientGroup - end - else - -- Now we need to resolve the bugs in DCS 1.5 ... - -- Consult the database for the units of the Client Group. (ClientGroup:getUnits() returns nil) - self:T3( "Bug 1.5 logic" ) - local ClientGroupTemplate = _DATABASE.Templates.Units[self.ClientName].GroupTemplate - self.ClientGroupID = ClientGroupTemplate.groupId - self.ClientGroupName = _DATABASE.Templates.Units[self.ClientName].GroupName - self:T3( self.ClientName .. " : group found in bug 1.5 resolvement logic!" ) - return ClientGroup - end - -- else - -- error( "Client " .. self.ClientName .. " not found!" ) - end - else - --self:E( { "Client not found!", self.ClientName } ) - end - end - end - end - - -- For non player clients - if ClientUnit then - local ClientGroup = ClientUnit:getGroup() - if ClientGroup then - self:T3( "ClientGroup = " .. self.ClientName ) - if ClientGroup:isExist() then - self:T3( "Normal logic" ) - self:T3( self.ClientName .. " : group found!" ) - return ClientGroup - end - end - end - - self.ClientGroupID = nil - self.ClientGroupUnit = nil - - return nil -end - - --- TODO: Check DCSTypes#Group.ID ---- Get the group ID of the client. --- @param #CLIENT self --- @return DCSTypes#Group.ID -function CLIENT:GetClientGroupID() - - local ClientGroup = self:GetDCSGroup() - - --self:E( self.ClientGroupID ) -- Determined in GetDCSGroup() - return self.ClientGroupID -end - - ---- Get the name of the group of the client. --- @param #CLIENT self --- @return #string -function CLIENT:GetClientGroupName() - - local ClientGroup = self:GetDCSGroup() - - self:T( self.ClientGroupName ) -- Determined in GetDCSGroup() - return self.ClientGroupName -end - ---- Returns the UNIT of the CLIENT. --- @param #CLIENT self --- @return Unit#UNIT -function CLIENT:GetClientGroupUnit() - self:F2() - - local ClientDCSUnit = Unit.getByName( self.ClientName ) - - self:T( self.ClientDCSUnit ) - if ClientDCSUnit and ClientDCSUnit:isExist() then - local ClientUnit = _DATABASE:FindUnit( self.ClientName ) - self:T2( ClientUnit ) - return ClientUnit - end -end - ---- Returns the DCSUnit of the CLIENT. --- @param #CLIENT self --- @return DCSTypes#Unit -function CLIENT:GetClientGroupDCSUnit() - self:F2() - - local ClientDCSUnit = Unit.getByName( self.ClientName ) - - if ClientDCSUnit and ClientDCSUnit:isExist() then - self:T2( ClientDCSUnit ) - return ClientDCSUnit - end -end - - ---- Evaluates if the CLIENT is a transport. --- @param #CLIENT self --- @return #boolean true is a transport. -function CLIENT:IsTransport() - self:F() - return self.ClientTransport -end - ---- Shows the @{Cargo#CARGO} contained within the CLIENT to the player as a message. --- The @{Cargo#CARGO} is shown using the @{Message#MESSAGE} distribution system. --- @param #CLIENT self -function CLIENT:ShowCargo() - self:F() - - local CargoMsg = "" - - for CargoName, Cargo in pairs( CARGOS ) do - if self == Cargo:IsLoadedInClient() then - CargoMsg = CargoMsg .. Cargo.CargoName .. " Type:" .. Cargo.CargoType .. " Weight: " .. Cargo.CargoWeight .. "\n" - end - end - - if CargoMsg == "" then - CargoMsg = "empty" - end - - self:Message( CargoMsg, 15, "Co-Pilot: Cargo Status", 30 ) - -end - --- TODO (1) I urgently need to revise this. ---- A local function called by the DCS World Menu system to switch off messages. -function CLIENT.SwitchMessages( PrmTable ) - PrmTable[1].MessageSwitch = PrmTable[2] -end - ---- The main message driver for the CLIENT. --- This function displays various messages to the Player logged into the CLIENT through the DCS World Messaging system. --- @param #CLIENT self --- @param #string Message is the text describing the message. --- @param #number MessageDuration is the duration in seconds that the Message should be displayed. --- @param #string MessageCategory is the category of the message (the title). --- @param #number MessageInterval is the interval in seconds between the display of the @{Message#MESSAGE} when the CLIENT is in the air. --- @param #string MessageID is the identifier of the message when displayed with intervals. -function CLIENT:Message( Message, MessageDuration, MessageCategory, MessageInterval, MessageID ) - self:F( { Message, MessageDuration, MessageCategory, MessageInterval } ) - - if self.MessageSwitch == true then - if MessageCategory == nil then - MessageCategory = "Messages" - end - if MessageID ~= nil then - if self.Messages[MessageID] == nil then - self.Messages[MessageID] = {} - self.Messages[MessageID].MessageId = MessageID - self.Messages[MessageID].MessageTime = timer.getTime() - self.Messages[MessageID].MessageDuration = MessageDuration - if MessageInterval == nil then - self.Messages[MessageID].MessageInterval = 600 - else - self.Messages[MessageID].MessageInterval = MessageInterval - end - MESSAGE:New( Message, MessageDuration, MessageCategory ):ToClient( self ) - else - if self:GetClientGroupDCSUnit() and not self:GetClientGroupDCSUnit():inAir() then - if timer.getTime() - self.Messages[MessageID].MessageTime >= self.Messages[MessageID].MessageDuration + 10 then - MESSAGE:New( Message, MessageDuration , MessageCategory):ToClient( self ) - self.Messages[MessageID].MessageTime = timer.getTime() - end - else - if timer.getTime() - self.Messages[MessageID].MessageTime >= self.Messages[MessageID].MessageDuration + self.Messages[MessageID].MessageInterval then - MESSAGE:New( Message, MessageDuration, MessageCategory ):ToClient( self ) - self.Messages[MessageID].MessageTime = timer.getTime() - end - end - end - else - MESSAGE:New( Message, MessageDuration, MessageCategory ):ToClient( self ) - end - end -end ---- This module contains the STATIC class. --- --- 1) @{Static#STATIC} class, extends @{Positionable#POSITIONABLE} --- =============================================================== --- Statics are **Static Units** defined within the Mission Editor. --- Note that Statics are almost the same as Units, but they don't have a controller. --- The @{Static#STATIC} class is a wrapper class to handle the DCS Static objects: --- --- * Wraps the DCS Static objects. --- * Support all DCS Static APIs. --- * Enhance with Static specific APIs not in the DCS API set. --- --- 1.1) STATIC reference methods --- ----------------------------- --- For each DCS Static will have a STATIC wrapper object (instance) within the _@{DATABASE} object. --- This is done at the beginning of the mission (when the mission starts). --- --- The STATIC class does not contain a :New() method, rather it provides :Find() methods to retrieve the object reference --- using the Static Name. --- --- Another thing to know is that STATIC objects do not "contain" the DCS Static object. --- The STATIc methods will reference the DCS Static object by name when it is needed during API execution. --- If the DCS Static object does not exist or is nil, the STATIC methods will return nil and log an exception in the DCS.log file. --- --- The STATIc class provides the following functions to retrieve quickly the relevant STATIC instance: --- --- * @{#STATIC.FindByName}(): Find a STATIC instance from the _DATABASE object using a DCS Static name. --- --- IMPORTANT: ONE SHOULD NEVER SANATIZE these STATIC OBJECT REFERENCES! (make the STATIC object references nil). --- --- @module Static --- @author FlightControl - - - - - - ---- The STATIC class --- @type STATIC --- @extends Positionable#POSITIONABLE -STATIC = { - ClassName = "STATIC", -} - - ---- Finds a STATIC from the _DATABASE using the relevant Static Name. --- As an optional parameter, a briefing text can be given also. --- @param #STATIC self --- @param #string StaticName Name of the DCS **Static** as defined within the Mission Editor. --- @return #STATIC -function STATIC:FindByName( StaticName ) - local StaticFound = _DATABASE:FindStatic( StaticName ) - - self.StaticName = StaticName - - if StaticFound then - StaticFound:F( { StaticName } ) - - return StaticFound - end - - error( "STATIC not found for: " .. StaticName ) -end - -function STATIC:Register( StaticName ) - local self = BASE:Inherit( self, POSITIONABLE:New( StaticName ) ) - self.StaticName = StaticName - return self -end - - -function STATIC:GetDCSObject() - local DCSStatic = StaticObject.getByName( self.StaticName ) - - if DCSStatic then - return DCSStatic - end - - return nil -end ---- This module contains the AIRBASE classes. --- --- === --- --- 1) @{Airbase#AIRBASE} class, extends @{Positionable#POSITIONABLE} --- ================================================================= --- The @{AIRBASE} class is a wrapper class to handle the DCS Airbase objects: --- --- * Support all DCS Airbase APIs. --- * Enhance with Airbase specific APIs not in the DCS Airbase API set. --- --- --- 1.1) AIRBASE reference methods --- ------------------------------ --- For each DCS Airbase object alive within a running mission, a AIRBASE wrapper object (instance) will be created within the _@{DATABASE} object. --- This is done at the beginning of the mission (when the mission starts). --- --- The AIRBASE class **does not contain a :New()** method, rather it provides **:Find()** methods to retrieve the object reference --- using the DCS Airbase or the DCS AirbaseName. --- --- Another thing to know is that AIRBASE objects do not "contain" the DCS Airbase object. --- The AIRBASE methods will reference the DCS Airbase object by name when it is needed during API execution. --- If the DCS Airbase object does not exist or is nil, the AIRBASE methods will return nil and log an exception in the DCS.log file. --- --- The AIRBASE class provides the following functions to retrieve quickly the relevant AIRBASE instance: --- --- * @{#AIRBASE.Find}(): Find a AIRBASE instance from the _DATABASE object using a DCS Airbase object. --- * @{#AIRBASE.FindByName}(): Find a AIRBASE instance from the _DATABASE object using a DCS Airbase name. --- --- IMPORTANT: ONE SHOULD NEVER SANATIZE these AIRBASE OBJECT REFERENCES! (make the AIRBASE object references nil). --- --- 1.2) DCS AIRBASE APIs --- --------------------- --- The DCS Airbase APIs are used extensively within MOOSE. The AIRBASE class has for each DCS Airbase API a corresponding method. --- To be able to distinguish easily in your code the difference between a AIRBASE API call and a DCS Airbase API call, --- the first letter of the method is also capitalized. So, by example, the DCS Airbase method @{DCSAirbase#Airbase.getName}() --- is implemented in the AIRBASE class as @{#AIRBASE.GetName}(). --- --- More functions will be added --- ---------------------------- --- During the MOOSE development, more functions will be added. --- --- @module Airbase --- @author FlightControl - - - - - ---- The AIRBASE class --- @type AIRBASE --- @extends Positionable#POSITIONABLE -AIRBASE = { - ClassName="AIRBASE", - CategoryName = { - [Airbase.Category.AIRDROME] = "Airdrome", - [Airbase.Category.HELIPAD] = "Helipad", - [Airbase.Category.SHIP] = "Ship", - }, - } - --- Registration. - ---- Create a new AIRBASE from DCSAirbase. --- @param #AIRBASE self --- @param #string AirbaseName The name of the airbase. --- @return Airbase#AIRBASE -function AIRBASE:Register( AirbaseName ) - - local self = BASE:Inherit( self, POSITIONABLE:New( AirbaseName ) ) - self.AirbaseName = AirbaseName - return self -end - --- Reference methods. - ---- Finds a AIRBASE from the _DATABASE using a DCSAirbase object. --- @param #AIRBASE self --- @param DCSAirbase#Airbase DCSAirbase An existing DCS Airbase object reference. --- @return Airbase#AIRBASE self -function AIRBASE:Find( DCSAirbase ) - - local AirbaseName = DCSAirbase:getName() - local AirbaseFound = _DATABASE:FindAirbase( AirbaseName ) - return AirbaseFound -end - ---- Find a AIRBASE in the _DATABASE using the name of an existing DCS Airbase. --- @param #AIRBASE self --- @param #string AirbaseName The Airbase Name. --- @return Airbase#AIRBASE self -function AIRBASE:FindByName( AirbaseName ) - - local AirbaseFound = _DATABASE:FindAirbase( AirbaseName ) - return AirbaseFound -end - -function AIRBASE:GetDCSObject() - local DCSAirbase = Airbase.getByName( self.AirbaseName ) - - if DCSAirbase then - return DCSAirbase - end - - return nil -end - - - --- This module contains the DATABASE class, managing the database of mission objects. -- -- ==== -- --- 1) @{Database#DATABASE} class, extends @{Base#BASE} +-- 1) @{Core.Database#DATABASE} class, extends @{Core.Base#BASE} -- =================================================== -- Mission designers can use the DATABASE class to refer to: -- @@ -11239,7 +6602,7 @@ end --- DATABASE class -- @type DATABASE --- @extends Base#BASE +-- @extends Core.Base#BASE DATABASE = { ClassName = "DATABASE", Templates = { @@ -11307,7 +6670,7 @@ end --- Finds a Unit based on the Unit Name. -- @param #DATABASE self -- @param #string UnitName --- @return Unit#UNIT The found Unit. +-- @return Wrapper.Unit#UNIT The found Unit. function DATABASE:FindUnit( UnitName ) local UnitFound = self.UNITS[UnitName] @@ -11355,7 +6718,7 @@ end --- Finds a STATIC based on the StaticName. -- @param #DATABASE self -- @param #string StaticName --- @return Static#STATIC The found STATIC. +-- @return Wrapper.Static#STATIC The found STATIC. function DATABASE:FindStatic( StaticName ) local StaticFound = self.STATICS[StaticName] @@ -11382,7 +6745,7 @@ end --- Finds a AIRBASE based on the AirbaseName. -- @param #DATABASE self -- @param #string AirbaseName --- @return Airbase#AIRBASE The found AIRBASE. +-- @return Wrapper.Airbase#AIRBASE The found AIRBASE. function DATABASE:FindAirbase( AirbaseName ) local AirbaseFound = self.AIRBASES[AirbaseName] @@ -11393,7 +6756,7 @@ end --- Finds a CLIENT based on the ClientName. -- @param #DATABASE self -- @param #string ClientName --- @return Client#CLIENT The found CLIENT. +-- @return Wrapper.Client#CLIENT The found CLIENT. function DATABASE:FindClient( ClientName ) local ClientFound = self.CLIENTS[ClientName] @@ -11416,7 +6779,7 @@ end --- Finds a GROUP based on the GroupName. -- @param #DATABASE self -- @param #string GroupName --- @return Group#GROUP The found GROUP. +-- @return Wrapper.Group#GROUP The found GROUP. function DATABASE:FindGroup( GroupName ) local GroupFound = self.GROUPS[GroupName] @@ -11736,7 +7099,7 @@ end --- Handles the OnBirth event for the alive units set. -- @param #DATABASE self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event function DATABASE:_EventOnBirth( Event ) self:F2( { Event } ) @@ -11750,7 +7113,7 @@ end --- Handles the OnDead or OnCrash event for alive units set. -- @param #DATABASE self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event function DATABASE:_EventOnDeadOrCrash( Event ) self:F2( { Event } ) @@ -11765,7 +7128,7 @@ end --- Handles the OnPlayerEnterUnit event to fill the active players table (with the unit filter applied). -- @param #DATABASE self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event function DATABASE:_EventOnPlayerEnterUnit( Event ) self:F2( { Event } ) @@ -11780,7 +7143,7 @@ end --- Handles the OnPlayerLeaveUnit event to clean the active players table. -- @param #DATABASE self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event function DATABASE:_EventOnPlayerLeaveUnit( Event ) self:F2( { Event } ) @@ -11982,9 +7345,9 @@ end -- -- === -- --- 1) @{Set#SET_BASE} class, extends @{Base#BASE} +-- 1) @{Core.Set#SET_BASE} class, extends @{Core.Base#BASE} -- ============================================== --- The @{Set#SET_BASE} class defines the core functions that define a collection of objects. +-- The @{Core.Set#SET_BASE} class defines the core functions that define a collection of objects. -- A SET provides iterators to iterate the SET, but will **temporarily** yield the ForEach interator loop at defined **"intervals"** to the mail simulator loop. -- In this way, large loops can be done while not blocking the simulator main processing loop. -- The default **"yield interval"** is after 10 objects processed. @@ -11992,18 +7355,18 @@ end -- -- 1.1) Add or remove objects from the SET -- --------------------------------------- --- Some key core functions are @{Set#SET_BASE.Add} and @{Set#SET_BASE.Remove} to add or remove objects from the SET in your logic. +-- Some key core functions are @{Core.Set#SET_BASE.Add} and @{Core.Set#SET_BASE.Remove} to add or remove objects from the SET in your logic. -- -- 1.2) Define the SET iterator **"yield interval"** and the **"time interval"** -- ----------------------------------------------------------------------------- --- Modify the iterator intervals with the @{Set#SET_BASE.SetInteratorIntervals} method. +-- Modify the iterator intervals with the @{Core.Set#SET_BASE.SetInteratorIntervals} method. -- You can set the **"yield interval"**, and the **"time interval"**. (See above). -- -- === -- --- 2) @{Set#SET_GROUP} class, extends @{Set#SET_BASE} +-- 2) @{Core.Set#SET_GROUP} class, extends @{Core.Set#SET_BASE} -- ================================================== --- Mission designers can use the @{Set#SET_GROUP} class to build sets of groups belonging to certain: +-- Mission designers can use the @{Core.Set#SET_GROUP} class to build sets of groups belonging to certain: -- -- * Coalitions -- * Categories @@ -12018,7 +7381,7 @@ end -- -- 2.2) Add or Remove GROUP(s) from SET_GROUP: -- ------------------------------------------- --- GROUPS can be added and removed using the @{Set#SET_GROUP.AddGroupsByName} and @{Set#SET_GROUP.RemoveGroupsByName} respectively. +-- GROUPS can be added and removed using the @{Core.Set#SET_GROUP.AddGroupsByName} and @{Core.Set#SET_GROUP.RemoveGroupsByName} respectively. -- These methods take a single GROUP name or an array of GROUP names to be added or removed from SET_GROUP. -- -- 2.3) SET_GROUP filter criteria: @@ -12037,7 +7400,7 @@ end -- -- Planned filter criteria within development are (so these are not yet available): -- --- * @{#SET_GROUP.FilterZones}: Builds the SET_GROUP with the groups within a @{Zone#ZONE}. +-- * @{#SET_GROUP.FilterZones}: Builds the SET_GROUP with the groups within a @{Core.Zone#ZONE}. -- -- 2.4) SET_GROUP iterators: -- ------------------------- @@ -12052,9 +7415,9 @@ end -- -- ==== -- --- 3) @{Set#SET_UNIT} class, extends @{Set#SET_BASE} +-- 3) @{Core.Set#SET_UNIT} class, extends @{Core.Set#SET_BASE} -- =================================================== --- Mission designers can use the @{Set#SET_UNIT} class to build sets of units belonging to certain: +-- Mission designers can use the @{Core.Set#SET_UNIT} class to build sets of units belonging to certain: -- -- * Coalitions -- * Categories @@ -12070,7 +7433,7 @@ end -- -- 3.2) Add or Remove UNIT(s) from SET_UNIT: -- ----------------------------------------- --- UNITs can be added and removed using the @{Set#SET_UNIT.AddUnitsByName} and @{Set#SET_UNIT.RemoveUnitsByName} respectively. +-- UNITs can be added and removed using the @{Core.Set#SET_UNIT.AddUnitsByName} and @{Core.Set#SET_UNIT.RemoveUnitsByName} respectively. -- These methods take a single UNIT name or an array of UNIT names to be added or removed from SET_UNIT. -- -- 3.3) SET_UNIT filter criteria: @@ -12090,7 +7453,7 @@ end -- -- Planned filter criteria within development are (so these are not yet available): -- --- * @{#SET_UNIT.FilterZones}: Builds the SET_UNIT with the units within a @{Zone#ZONE}. +-- * @{#SET_UNIT.FilterZones}: Builds the SET_UNIT with the units within a @{Core.Zone#ZONE}. -- -- 3.4) SET_UNIT iterators: -- ------------------------ @@ -12110,9 +7473,9 @@ end -- -- === -- --- 4) @{Set#SET_CLIENT} class, extends @{Set#SET_BASE} +-- 4) @{Core.Set#SET_CLIENT} class, extends @{Core.Set#SET_BASE} -- =================================================== --- Mission designers can use the @{Set#SET_CLIENT} class to build sets of units belonging to certain: +-- Mission designers can use the @{Core.Set#SET_CLIENT} class to build sets of units belonging to certain: -- -- * Coalitions -- * Categories @@ -12128,7 +7491,7 @@ end -- -- 4.2) Add or Remove CLIENT(s) from SET_CLIENT: -- ----------------------------------------- --- CLIENTs can be added and removed using the @{Set#SET_CLIENT.AddClientsByName} and @{Set#SET_CLIENT.RemoveClientsByName} respectively. +-- CLIENTs can be added and removed using the @{Core.Set#SET_CLIENT.AddClientsByName} and @{Core.Set#SET_CLIENT.RemoveClientsByName} respectively. -- These methods take a single CLIENT name or an array of CLIENT names to be added or removed from SET_CLIENT. -- -- 4.3) SET_CLIENT filter criteria: @@ -12148,7 +7511,7 @@ end -- -- Planned filter criteria within development are (so these are not yet available): -- --- * @{#SET_CLIENT.FilterZones}: Builds the SET_CLIENT with the clients within a @{Zone#ZONE}. +-- * @{#SET_CLIENT.FilterZones}: Builds the SET_CLIENT with the clients within a @{Core.Zone#ZONE}. -- -- 4.4) SET_CLIENT iterators: -- ------------------------ @@ -12160,9 +7523,9 @@ end -- -- ==== -- --- 5) @{Set#SET_AIRBASE} class, extends @{Set#SET_BASE} +-- 5) @{Core.Set#SET_AIRBASE} class, extends @{Core.Set#SET_BASE} -- ==================================================== --- Mission designers can use the @{Set#SET_AIRBASE} class to build sets of airbases optionally belonging to certain: +-- Mission designers can use the @{Core.Set#SET_AIRBASE} class to build sets of airbases optionally belonging to certain: -- -- * Coalitions -- @@ -12174,7 +7537,7 @@ end -- -- 5.2) Add or Remove AIRBASEs from SET_AIRBASE -- -------------------------------------------- --- AIRBASEs can be added and removed using the @{Set#SET_AIRBASE.AddAirbasesByName} and @{Set#SET_AIRBASE.RemoveAirbasesByName} respectively. +-- AIRBASEs can be added and removed using the @{Core.Set#SET_AIRBASE.AddAirbasesByName} and @{Core.Set#SET_AIRBASE.RemoveAirbasesByName} respectively. -- These methods take a single AIRBASE name or an array of AIRBASE names to be added or removed from SET_AIRBASE. -- -- 5.3) SET_AIRBASE filter criteria @@ -12198,13 +7561,12 @@ end -- -- ==== -- --- ### Contributions: --- --- * Mechanist : Concept & Testing --- -- ### Authors: -- -- * FlightControl : Design & Programming +-- +-- ### Contributions: +-- -- -- @module Set @@ -12214,7 +7576,7 @@ end -- @field #table Filter -- @field #table Set -- @field #table List --- @extends Base#BASE +-- @extends Core.Base#BASE SET_BASE = { ClassName = "SET_BASE", Filter = {}, @@ -12245,10 +7607,10 @@ function SET_BASE:New( Database ) return self end ---- Finds an @{Base#BASE} object based on the object Name. +--- Finds an @{Core.Base#BASE} object based on the object Name. -- @param #SET_BASE self -- @param #string ObjectName --- @return Base#BASE The Object found. +-- @return Core.Base#BASE The Object found. function SET_BASE:_Find( ObjectName ) local ObjectFound = self.Set[ObjectName] @@ -12265,11 +7627,11 @@ function SET_BASE:GetSet() return self.Set end ---- Adds a @{Base#BASE} object in the @{Set#SET_BASE}, using the Object Name as the index. +--- Adds a @{Core.Base#BASE} object in the @{Core.Set#SET_BASE}, using a given ObjectName as the index. -- @param #SET_BASE self -- @param #string ObjectName --- @param Base#BASE Object --- @return Base#BASE The added BASE Object. +-- @param Core.Base#BASE Object +-- @return Core.Base#BASE The added BASE Object. function SET_BASE:Add( ObjectName, Object ) self:F2( ObjectName ) @@ -12291,7 +7653,22 @@ function SET_BASE:Add( ObjectName, Object ) end ---- Removes a @{Base#BASE} object from the @{Set#SET_BASE} and derived classes, based on the Object Name. +--- Adds a @{Core.Base#BASE} object in the @{Core.Set#SET_BASE}, using the Object Name as the index. +-- @param #SET_BASE self +-- @param Wrapper.Object#OBJECT Object +-- @return Core.Base#BASE The added BASE Object. +function SET_BASE:AddObject( Object ) + self:F2( Object.ObjectName ) + + self:T( Object.UnitName ) + self:T( Object.ObjectName ) + self:Add( Object.ObjectName, Object ) + +end + + + +--- Removes a @{Core.Base#BASE} object from the @{Core.Set#SET_BASE} and derived classes, based on the Object Name. -- @param #SET_BASE self -- @param #string ObjectName function SET_BASE:Remove( ObjectName ) @@ -12330,7 +7707,22 @@ function SET_BASE:Remove( ObjectName ) end ---- Retrieves the amount of objects in the @{Set#SET_BASE} and derived classes. +--- Gets a @{Core.Base#BASE} object from the @{Core.Set#SET_BASE} and derived classes, based on the Object Name. +-- @param #SET_BASE self +-- @param #string ObjectName +-- @return Core.Base#BASE +function SET_BASE:Get( ObjectName ) + self:F( ObjectName ) + + local t = self.Set[ObjectName] + + self:T3( { ObjectName, t } ) + + return t + +end + +--- Retrieves the amount of objects in the @{Core.Set#SET_BASE} and derived classes. -- @param #SET_BASE self -- @return #number Count function SET_BASE:Count() @@ -12423,10 +7815,10 @@ function SET_BASE:FilterStop() return self end ---- Iterate the SET_BASE while identifying the nearest object from a @{Point#POINT_VEC2}. +--- Iterate the SET_BASE while identifying the nearest object from a @{Core.Point#POINT_VEC2}. -- @param #SET_BASE self --- @param Point#POINT_VEC2 PointVec2 A @{Point#POINT_VEC2} object from where to evaluate the closest object in the set. --- @return Base#BASE The closest object. +-- @param Core.Point#POINT_VEC2 PointVec2 A @{Core.Point#POINT_VEC2} object from where to evaluate the closest object in the set. +-- @return Core.Base#BASE The closest object. function SET_BASE:FindNearestObjectFromPointVec2( PointVec2 ) self:F2( PointVec2 ) @@ -12477,7 +7869,7 @@ end --- Handles the OnBirth event for the Set. -- @param #SET_BASE self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event function SET_BASE:_EventOnBirth( Event ) self:F3( { Event } ) @@ -12493,7 +7885,7 @@ end --- Handles the OnDead or OnCrash event for alive units set. -- @param #SET_BASE self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event function SET_BASE:_EventOnDeadOrCrash( Event ) self:F3( { Event } ) @@ -12507,7 +7899,7 @@ end --- Handles the OnPlayerEnterUnit event to fill the active players table (with the unit filter applied). -- @param #SET_BASE self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event function SET_BASE:_EventOnPlayerEnterUnit( Event ) self:F3( { Event } ) @@ -12523,7 +7915,7 @@ end --- Handles the OnPlayerLeaveUnit event to clean the active players table. -- @param #SET_BASE self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event function SET_BASE:_EventOnPlayerLeaveUnit( Event ) self:F3( { Event } ) @@ -12556,6 +7948,9 @@ end function SET_BASE:ForEach( IteratorFunction, arg, Set, Function, FunctionArguments ) self:F3( arg ) + Set = Set or self:GetSet() + arg = arg or {} + local function CoRoutine() local Count = 0 for ObjectID, ObjectData in pairs( Set ) do @@ -12668,7 +8063,7 @@ end --- SET_GROUP class -- @type SET_GROUP --- @extends Set#SET_BASE +-- @extends #SET_BASE SET_GROUP = { ClassName = "SET_GROUP", Filter = { @@ -12709,7 +8104,7 @@ function SET_GROUP:New() end --- Add GROUP(s) to SET_GROUP. --- @param Set#SET_GROUP self +-- @param Core.Set#SET_GROUP self -- @param #string AddGroupNames A single name or an array of GROUP names. -- @return self function SET_GROUP:AddGroupsByName( AddGroupNames ) @@ -12724,8 +8119,8 @@ function SET_GROUP:AddGroupsByName( AddGroupNames ) end --- Remove GROUP(s) from SET_GROUP. --- @param Set#SET_GROUP self --- @param Group#GROUP RemoveGroupNames A single name or an array of GROUP names. +-- @param Core.Set#SET_GROUP self +-- @param Wrapper.Group#GROUP RemoveGroupNames A single name or an array of GROUP names. -- @return self function SET_GROUP:RemoveGroupsByName( RemoveGroupNames ) @@ -12744,7 +8139,7 @@ end --- Finds a Group based on the Group Name. -- @param #SET_GROUP self -- @param #string GroupName --- @return Group#GROUP The found Group. +-- @return Wrapper.Group#GROUP The found Group. function SET_GROUP:FindGroup( GroupName ) local GroupFound = self.Set[GroupName] @@ -12845,7 +8240,7 @@ end --- Handles the Database to check on an event (birth) that the Object was added in the Database. -- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! -- @param #SET_GROUP self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event -- @return #string The name of the GROUP -- @return #table The GROUP function SET_GROUP:AddInDatabase( Event ) @@ -12862,7 +8257,7 @@ end --- Handles the Database to check on any event that Object exists in the Database. -- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa! -- @param #SET_GROUP self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event -- @return #string The name of the GROUP -- @return #table The GROUP function SET_GROUP:FindInDatabase( Event ) @@ -12885,15 +8280,15 @@ end --- Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence completely in a @{Zone}, providing the GROUP and optional parameters to the called function. -- @param #SET_GROUP self --- @param Zone#ZONE ZoneObject The Zone to be tested for. +-- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. -- @param #function IteratorFunction The function that will be called when there is an alive GROUP in the SET_GROUP. The function needs to accept a GROUP parameter. -- @return #SET_GROUP self function SET_GROUP:ForEachGroupCompletelyInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) self:ForEach( IteratorFunction, arg, self.Set, - --- @param Zone#ZONE_BASE ZoneObject - -- @param Group#GROUP GroupObject + --- @param Core.Zone#ZONE_BASE ZoneObject + -- @param Wrapper.Group#GROUP GroupObject function( ZoneObject, GroupObject ) if GroupObject:IsCompletelyInZone( ZoneObject ) then return true @@ -12907,15 +8302,15 @@ end --- Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence partly in a @{Zone}, providing the GROUP and optional parameters to the called function. -- @param #SET_GROUP self --- @param Zone#ZONE ZoneObject The Zone to be tested for. +-- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. -- @param #function IteratorFunction The function that will be called when there is an alive GROUP in the SET_GROUP. The function needs to accept a GROUP parameter. -- @return #SET_GROUP self function SET_GROUP:ForEachGroupPartlyInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) self:ForEach( IteratorFunction, arg, self.Set, - --- @param Zone#ZONE_BASE ZoneObject - -- @param Group#GROUP GroupObject + --- @param Core.Zone#ZONE_BASE ZoneObject + -- @param Wrapper.Group#GROUP GroupObject function( ZoneObject, GroupObject ) if GroupObject:IsPartlyInZone( ZoneObject ) then return true @@ -12929,15 +8324,15 @@ end --- Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence not in a @{Zone}, providing the GROUP and optional parameters to the called function. -- @param #SET_GROUP self --- @param Zone#ZONE ZoneObject The Zone to be tested for. +-- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. -- @param #function IteratorFunction The function that will be called when there is an alive GROUP in the SET_GROUP. The function needs to accept a GROUP parameter. -- @return #SET_GROUP self function SET_GROUP:ForEachGroupNotInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) self:ForEach( IteratorFunction, arg, self.Set, - --- @param Zone#ZONE_BASE ZoneObject - -- @param Group#GROUP GroupObject + --- @param Core.Zone#ZONE_BASE ZoneObject + -- @param Wrapper.Group#GROUP GroupObject function( ZoneObject, GroupObject ) if GroupObject:IsNotInZone( ZoneObject ) then return true @@ -12978,7 +8373,7 @@ end --- -- @param #SET_GROUP self --- @param Group#GROUP MooseGroup +-- @param Wrapper.Group#GROUP MooseGroup -- @return #SET_GROUP self function SET_GROUP:IsIncludeObject( MooseGroup ) self:F2( MooseGroup ) @@ -13034,7 +8429,7 @@ end --- SET_UNIT class -- @type SET_UNIT --- @extends Set#SET_BASE +-- @extends Core.Set#SET_BASE SET_UNIT = { ClassName = "SET_UNIT", Units = {}, @@ -13106,8 +8501,8 @@ function SET_UNIT:AddUnitsByName( AddUnitNames ) end --- Remove UNIT(s) from SET_UNIT. --- @param Set#SET_UNIT self --- @param Unit#UNIT RemoveUnitNames A single name or an array of UNIT names. +-- @param Core.Set#SET_UNIT self +-- @param Wrapper.Unit#UNIT RemoveUnitNames A single name or an array of UNIT names. -- @return self function SET_UNIT:RemoveUnitsByName( RemoveUnitNames ) @@ -13124,7 +8519,7 @@ end --- Finds a Unit based on the Unit Name. -- @param #SET_UNIT self -- @param #string UnitName --- @return Unit#UNIT The found Unit. +-- @return Wrapper.Unit#UNIT The found Unit. function SET_UNIT:FindUnit( UnitName ) local UnitFound = self.Set[UnitName] @@ -13270,7 +8665,7 @@ end --- Handles the Database to check on an event (birth) that the Object was added in the Database. -- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! -- @param #SET_UNIT self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event -- @return #string The name of the UNIT -- @return #table The UNIT function SET_UNIT:AddInDatabase( Event ) @@ -13287,7 +8682,7 @@ end --- Handles the Database to check on any event that Object exists in the Database. -- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa! -- @param #SET_UNIT self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event -- @return #string The name of the UNIT -- @return #table The UNIT function SET_UNIT:FindInDatabase( Event ) @@ -13311,15 +8706,15 @@ end --- Iterate the SET_UNIT and call an iterator function for each **alive** UNIT presence completely in a @{Zone}, providing the UNIT and optional parameters to the called function. -- @param #SET_UNIT self --- @param Zone#ZONE ZoneObject The Zone to be tested for. +-- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. -- @param #function IteratorFunction The function that will be called when there is an alive UNIT in the SET_UNIT. The function needs to accept a UNIT parameter. -- @return #SET_UNIT self function SET_UNIT:ForEachUnitCompletelyInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) self:ForEach( IteratorFunction, arg, self.Set, - --- @param Zone#ZONE_BASE ZoneObject - -- @param Unit#UNIT UnitObject + --- @param Core.Zone#ZONE_BASE ZoneObject + -- @param Wrapper.Unit#UNIT UnitObject function( ZoneObject, UnitObject ) if UnitObject:IsCompletelyInZone( ZoneObject ) then return true @@ -13333,15 +8728,15 @@ end --- Iterate the SET_UNIT and call an iterator function for each **alive** UNIT presence not in a @{Zone}, providing the UNIT and optional parameters to the called function. -- @param #SET_UNIT self --- @param Zone#ZONE ZoneObject The Zone to be tested for. +-- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. -- @param #function IteratorFunction The function that will be called when there is an alive UNIT in the SET_UNIT. The function needs to accept a UNIT parameter. -- @return #SET_UNIT self function SET_UNIT:ForEachUnitNotInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) self:ForEach( IteratorFunction, arg, self.Set, - --- @param Zone#ZONE_BASE ZoneObject - -- @param Unit#UNIT UnitObject + --- @param Core.Zone#ZONE_BASE ZoneObject + -- @param Wrapper.Unit#UNIT UnitObject function( ZoneObject, UnitObject ) if UnitObject:IsNotInZone( ZoneObject ) then return true @@ -13363,7 +8758,7 @@ function SET_UNIT:GetUnitTypes() local UnitTypes = {} for UnitID, UnitData in pairs( self:GetSet() ) do - local TextUnit = UnitData -- Unit#UNIT + local TextUnit = UnitData -- Wrapper.Unit#UNIT if TextUnit:IsAlive() then local UnitType = TextUnit:GetTypeName() @@ -13408,7 +8803,7 @@ function SET_UNIT:GetUnitThreatLevels() local UnitThreatLevels = {} for UnitID, UnitData in pairs( self:GetSet() ) do - local ThreatUnit = UnitData -- Unit#UNIT + local ThreatUnit = UnitData -- Wrapper.Unit#UNIT if ThreatUnit:IsAlive() then local UnitThreatLevel, UnitThreatLevelText = ThreatUnit:GetThreatLevel() local ThreatUnitName = ThreatUnit:GetName() @@ -13425,14 +8820,14 @@ end --- Returns if the @{Set} has targets having a radar (of a given type). -- @param #SET_UNIT self --- @param DCSUnit#Unit.RadarType RadarType +-- @param Dcs.DCSWrapper.Unit#Unit.RadarType RadarType -- @return #number The amount of radars in the Set with the given type function SET_UNIT:HasRadar( RadarType ) self:F2( RadarType ) local RadarCount = 0 for UnitID, UnitData in pairs( self:GetSet()) do - local UnitSensorTest = UnitData -- Unit#UNIT + local UnitSensorTest = UnitData -- Wrapper.Unit#UNIT local HasSensors if RadarType then HasSensors = UnitSensorTest:HasSensors( Unit.SensorType.RADAR, RadarType ) @@ -13456,7 +8851,7 @@ function SET_UNIT:HasSEAD() local SEADCount = 0 for UnitID, UnitData in pairs( self:GetSet()) do - local UnitSEAD = UnitData -- Unit#UNIT + local UnitSEAD = UnitData -- Wrapper.Unit#UNIT if UnitSEAD:IsAlive() then local UnitSEADAttributes = UnitSEAD:GetDesc().attributes @@ -13480,7 +8875,7 @@ function SET_UNIT:HasGroundUnits() local GroundUnitCount = 0 for UnitID, UnitData in pairs( self:GetSet()) do - local UnitTest = UnitData -- Unit#UNIT + local UnitTest = UnitData -- Wrapper.Unit#UNIT if UnitTest:IsGround() then GroundUnitCount = GroundUnitCount + 1 end @@ -13497,7 +8892,7 @@ function SET_UNIT:HasFriendlyUnits( FriendlyCoalition ) local FriendlyUnitCount = 0 for UnitID, UnitData in pairs( self:GetSet()) do - local UnitTest = UnitData -- Unit#UNIT + local UnitTest = UnitData -- Wrapper.Unit#UNIT if UnitTest:IsFriendly( FriendlyCoalition ) then FriendlyUnitCount = FriendlyUnitCount + 1 end @@ -13536,7 +8931,7 @@ end --- -- @param #SET_UNIT self --- @param Unit#UNIT MUnit +-- @param Wrapper.Unit#UNIT MUnit -- @return #SET_UNIT self function SET_UNIT:IsIncludeObject( MUnit ) self:F2( MUnit ) @@ -13629,7 +9024,7 @@ end --- SET_CLIENT class -- @type SET_CLIENT --- @extends Set#SET_BASE +-- @extends Core.Set#SET_BASE SET_CLIENT = { ClassName = "SET_CLIENT", Clients = {}, @@ -13671,7 +9066,7 @@ function SET_CLIENT:New() end --- Add CLIENT(s) to SET_CLIENT. --- @param Set#SET_CLIENT self +-- @param Core.Set#SET_CLIENT self -- @param #string AddClientNames A single name or an array of CLIENT names. -- @return self function SET_CLIENT:AddClientsByName( AddClientNames ) @@ -13686,8 +9081,8 @@ function SET_CLIENT:AddClientsByName( AddClientNames ) end --- Remove CLIENT(s) from SET_CLIENT. --- @param Set#SET_CLIENT self --- @param Client#CLIENT RemoveClientNames A single name or an array of CLIENT names. +-- @param Core.Set#SET_CLIENT self +-- @param Wrapper.Client#CLIENT RemoveClientNames A single name or an array of CLIENT names. -- @return self function SET_CLIENT:RemoveClientsByName( RemoveClientNames ) @@ -13704,7 +9099,7 @@ end --- Finds a Client based on the Client Name. -- @param #SET_CLIENT self -- @param #string ClientName --- @return Client#CLIENT The found Client. +-- @return Wrapper.Client#CLIENT The found Client. function SET_CLIENT:FindClient( ClientName ) local ClientFound = self.Set[ClientName] @@ -13825,7 +9220,7 @@ end --- Handles the Database to check on an event (birth) that the Object was added in the Database. -- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! -- @param #SET_CLIENT self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event -- @return #string The name of the CLIENT -- @return #table The CLIENT function SET_CLIENT:AddInDatabase( Event ) @@ -13837,7 +9232,7 @@ end --- Handles the Database to check on any event that Object exists in the Database. -- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa! -- @param #SET_CLIENT self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event -- @return #string The name of the CLIENT -- @return #table The CLIENT function SET_CLIENT:FindInDatabase( Event ) @@ -13860,15 +9255,15 @@ end --- Iterate the SET_CLIENT and call an iterator function for each **alive** CLIENT presence completely in a @{Zone}, providing the CLIENT and optional parameters to the called function. -- @param #SET_CLIENT self --- @param Zone#ZONE ZoneObject The Zone to be tested for. +-- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. -- @param #function IteratorFunction The function that will be called when there is an alive CLIENT in the SET_CLIENT. The function needs to accept a CLIENT parameter. -- @return #SET_CLIENT self function SET_CLIENT:ForEachClientInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) self:ForEach( IteratorFunction, arg, self.Set, - --- @param Zone#ZONE_BASE ZoneObject - -- @param Client#CLIENT ClientObject + --- @param Core.Zone#ZONE_BASE ZoneObject + -- @param Wrapper.Client#CLIENT ClientObject function( ZoneObject, ClientObject ) if ClientObject:IsInZone( ZoneObject ) then return true @@ -13882,15 +9277,15 @@ end --- Iterate the SET_CLIENT and call an iterator function for each **alive** CLIENT presence not in a @{Zone}, providing the CLIENT and optional parameters to the called function. -- @param #SET_CLIENT self --- @param Zone#ZONE ZoneObject The Zone to be tested for. +-- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. -- @param #function IteratorFunction The function that will be called when there is an alive CLIENT in the SET_CLIENT. The function needs to accept a CLIENT parameter. -- @return #SET_CLIENT self function SET_CLIENT:ForEachClientNotInZone( ZoneObject, IteratorFunction, ... ) self:F2( arg ) self:ForEach( IteratorFunction, arg, self.Set, - --- @param Zone#ZONE_BASE ZoneObject - -- @param Client#CLIENT ClientObject + --- @param Core.Zone#ZONE_BASE ZoneObject + -- @param Wrapper.Client#CLIENT ClientObject function( ZoneObject, ClientObject ) if ClientObject:IsNotInZone( ZoneObject ) then return true @@ -13904,7 +9299,7 @@ end --- -- @param #SET_CLIENT self --- @param Client#CLIENT MClient +-- @param Wrapper.Client#CLIENT MClient -- @return #SET_CLIENT self function SET_CLIENT:IsIncludeObject( MClient ) self:F2( MClient ) @@ -13986,7 +9381,7 @@ end --- SET_AIRBASE class -- @type SET_AIRBASE --- @extends Set#SET_BASE +-- @extends Core.Set#SET_BASE SET_AIRBASE = { ClassName = "SET_AIRBASE", Airbases = {}, @@ -14022,7 +9417,7 @@ function SET_AIRBASE:New() end --- Add AIRBASEs to SET_AIRBASE. --- @param Set#SET_AIRBASE self +-- @param Core.Set#SET_AIRBASE self -- @param #string AddAirbaseNames A single name or an array of AIRBASE names. -- @return self function SET_AIRBASE:AddAirbasesByName( AddAirbaseNames ) @@ -14037,8 +9432,8 @@ function SET_AIRBASE:AddAirbasesByName( AddAirbaseNames ) end --- Remove AIRBASEs from SET_AIRBASE. --- @param Set#SET_AIRBASE self --- @param Airbase#AIRBASE RemoveAirbaseNames A single name or an array of AIRBASE names. +-- @param Core.Set#SET_AIRBASE self +-- @param Wrapper.Airbase#AIRBASE RemoveAirbaseNames A single name or an array of AIRBASE names. -- @return self function SET_AIRBASE:RemoveAirbasesByName( RemoveAirbaseNames ) @@ -14055,7 +9450,7 @@ end --- Finds a Airbase based on the Airbase Name. -- @param #SET_AIRBASE self -- @param #string AirbaseName --- @return Airbase#AIRBASE The found Airbase. +-- @return Wrapper.Airbase#AIRBASE The found Airbase. function SET_AIRBASE:FindAirbase( AirbaseName ) local AirbaseFound = self.Set[AirbaseName] @@ -14117,7 +9512,7 @@ end --- Handles the Database to check on an event (birth) that the Object was added in the Database. -- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! -- @param #SET_AIRBASE self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event -- @return #string The name of the AIRBASE -- @return #table The AIRBASE function SET_AIRBASE:AddInDatabase( Event ) @@ -14129,7 +9524,7 @@ end --- Handles the Database to check on any event that Object exists in the Database. -- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa! -- @param #SET_AIRBASE self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event -- @return #string The name of the AIRBASE -- @return #table The AIRBASE function SET_AIRBASE:FindInDatabase( Event ) @@ -14150,10 +9545,10 @@ function SET_AIRBASE:ForEachAirbase( IteratorFunction, ... ) return self end ---- Iterate the SET_AIRBASE while identifying the nearest @{Airbase#AIRBASE} from a @{Point#POINT_VEC2}. +--- Iterate the SET_AIRBASE while identifying the nearest @{Wrapper.Airbase#AIRBASE} from a @{Core.Point#POINT_VEC2}. -- @param #SET_AIRBASE self --- @param Point#POINT_VEC2 PointVec2 A @{Point#POINT_VEC2} object from where to evaluate the closest @{Airbase#AIRBASE}. --- @return Airbase#AIRBASE The closest @{Airbase#AIRBASE}. +-- @param Core.Point#POINT_VEC2 PointVec2 A @{Core.Point#POINT_VEC2} object from where to evaluate the closest @{Wrapper.Airbase#AIRBASE}. +-- @return Wrapper.Airbase#AIRBASE The closest @{Wrapper.Airbase#AIRBASE}. function SET_AIRBASE:FindNearestAirbaseFromPointVec2( PointVec2 ) self:F2( PointVec2 ) @@ -14165,7 +9560,7 @@ end --- -- @param #SET_AIRBASE self --- @param Airbase#AIRBASE MAirbase +-- @param Wrapper.Airbase#AIRBASE MAirbase -- @return #SET_AIRBASE self function SET_AIRBASE:IsIncludeObject( MAirbase ) self:F2( MAirbase ) @@ -14207,60 +9602,75 @@ function SET_AIRBASE:IsIncludeObject( MAirbase ) end --- This module contains the POINT classes. -- --- 1) @{Point#POINT_VEC3} class, extends @{Base#BASE} --- =============================================== --- The @{Point#POINT_VEC3} class defines a 3D point in the simulator. +-- 1) @{Core.Point#POINT_VEC3} class, extends @{Core.Base#BASE} +-- ================================================== +-- The @{Core.Point#POINT_VEC3} class defines a 3D point in the simulator. -- -- **Important Note:** Most of the functions in this section were taken from MIST, and reworked to OO concepts. -- In order to keep the credibility of the the author, I want to emphasize that the of the MIST framework was created by Grimes, who you can find on the Eagle Dynamics Forums. -- -- 1.1) POINT_VEC3 constructor -- --------------------------- --- --- A new POINT instance can be created with: +-- A new POINT_VEC3 instance can be created with: -- -- * @{#POINT_VEC3.New}(): a 3D point. +-- * @{#POINT_VEC3.NewFromVec3}(): a 3D point created from a @{Dcs.DCSTypes#Vec3}. +-- -- --- 2) @{Point#POINT_VEC2} class, extends @{Point#POINT_VEC3} +-- 2) @{Core.Point#POINT_VEC2} class, extends @{Core.Point#POINT_VEC3} -- ========================================================= --- The @{Point#POINT_VEC2} class defines a 2D point in the simulator. The height coordinate (if needed) will be the land height + an optional added height specified. +-- The @{Core.Point#POINT_VEC2} class defines a 2D point in the simulator. The height coordinate (if needed) will be the land height + an optional added height specified. -- -- 2.1) POINT_VEC2 constructor -- --------------------------- --- --- A new POINT instance can be created with: +-- A new POINT_VEC2 instance can be created with: -- --- * @{#POINT_VEC2.New}(): a 2D point. +-- * @{#POINT_VEC2.New}(): a 2D point, taking an additional height parameter. +-- * @{#POINT_VEC2.NewFromVec2}(): a 2D point created from a @{Dcs.DCSTypes#Vec2}. +-- +-- === +-- +-- **API CHANGE HISTORY** +-- ====================== +-- +-- The underlying change log documents the API changes. Please read this carefully. The following notation is used: +-- +-- * **Added** parts are expressed in bold type face. +-- * _Removed_ parts are expressed in italic type face. +-- +-- Hereby the change log: +-- +-- 2016-08-12: POINT_VEC3:**Translate( Distance, Angle )** added. +-- +-- 2016-08-06: Made PointVec3 and Vec3, PointVec2 and Vec2 terminology used in the code consistent. +-- +-- * Replaced method _Point_Vec3() to **Vec3**() where the code manages a Vec3. Replaced all references to the method. +-- * Replaced method _Point_Vec2() to **Vec2**() where the code manages a Vec2. Replaced all references to the method. +-- * Replaced method Random_Point_Vec3() to **RandomVec3**() where the code manages a Vec3. Replaced all references to the method. +-- . +-- === +-- +-- ### Authors: +-- +-- * FlightControl : Design & Programming +-- +-- ### Contributions: -- -- @module Point --- @author FlightControl --- The POINT_VEC3 class -- @type POINT_VEC3 --- @extends Base#BASE +-- @extends Core.Base#BASE -- @field #number x The x coordinate in 3D space. -- @field #number y The y coordinate in 3D space. -- @field #number z The z coordiante in 3D space. --- @field #POINT_VEC3.SmokeColor SmokeColor --- @field #POINT_VEC3.FlareColor FlareColor +-- @field Utilities.Utils#SMOKECOLOR SmokeColor +-- @field Utilities.Utils#FLARECOLOR FlareColor -- @field #POINT_VEC3.RoutePointAltType RoutePointAltType -- @field #POINT_VEC3.RoutePointType RoutePointType -- @field #POINT_VEC3.RoutePointAction RoutePointAction POINT_VEC3 = { ClassName = "POINT_VEC3", - SmokeColor = { - Green = trigger.smokeColor.Green, - Red = trigger.smokeColor.Red, - White = trigger.smokeColor.White, - Orange = trigger.smokeColor.Orange, - Blue = trigger.smokeColor.Blue - }, - FlareColor = { - Green = trigger.flareColor.Green, - Red = trigger.flareColor.Red, - White = trigger.flareColor.White, - Yellow = trigger.flareColor.Yellow - }, Metric = true, RoutePointAltType = { BARO = "BARO", @@ -14273,81 +9683,70 @@ POINT_VEC3 = { }, } - ---- SmokeColor --- @type POINT_VEC3.SmokeColor --- @field Green --- @field Red --- @field White --- @field Orange --- @field Blue - - - ---- FlareColor --- @type POINT_VEC3.FlareColor --- @field Green --- @field Red --- @field White --- @field Yellow +--- The POINT_VEC2 class +-- @type POINT_VEC2 +-- @extends #POINT_VEC3 +-- @field Dcs.DCSTypes#Distance x The x coordinate in meters. +-- @field Dcs.DCSTypes#Distance y the y coordinate in meters. +POINT_VEC2 = { + ClassName = "POINT_VEC2", +} +do -- POINT_VEC3 --- RoutePoint AltTypes -- @type POINT_VEC3.RoutePointAltType -- @field BARO "BARO" - - --- RoutePoint Types -- @type POINT_VEC3.RoutePointType -- @field TurningPoint "Turning Point" - - --- RoutePoint Actions -- @type POINT_VEC3.RoutePointAction -- @field TurningPoint "Turning Point" - - -- Constructor. --- Create a new POINT_VEC3 object. -- @param #POINT_VEC3 self --- @param DCSTypes#Distance x The x coordinate of the Vec3 point, pointing to the North. --- @param DCSTypes#Distance y The y coordinate of the Vec3 point, pointing Upwards. --- @param DCSTypes#Distance z The z coordinate of the Vec3 point, pointing to the Right. --- @return Point#POINT_VEC3 self +-- @param Dcs.DCSTypes#Distance x The x coordinate of the Vec3 point, pointing to the North. +-- @param Dcs.DCSTypes#Distance y The y coordinate of the Vec3 point, pointing Upwards. +-- @param Dcs.DCSTypes#Distance z The z coordinate of the Vec3 point, pointing to the Right. +-- @return Core.Point#POINT_VEC3 self function POINT_VEC3:New( x, y, z ) local self = BASE:Inherit( self, BASE:New() ) self.x = x self.y = y self.z = z + return self end --- Create a new POINT_VEC3 object from Vec3 coordinates. -- @param #POINT_VEC3 self --- @param DCSTypes#Vec3 Vec3 The Vec3 point. --- @return Point#POINT_VEC3 self +-- @param Dcs.DCSTypes#Vec3 Vec3 The Vec3 point. +-- @return Core.Point#POINT_VEC3 self function POINT_VEC3:NewFromVec3( Vec3 ) - return self:New( Vec3.x, Vec3.y, Vec3.z ) + self = self:New( Vec3.x, Vec3.y, Vec3.z ) + self:F2( self ) + return self end --- Return the coordinates of the POINT_VEC3 in Vec3 format. -- @param #POINT_VEC3 self --- @return DCSTypes#Vec3 The Vec3 coodinate. +-- @return Dcs.DCSTypes#Vec3 The Vec3 coodinate. function POINT_VEC3:GetVec3() return { x = self.x, y = self.y, z = self.z } end --- Return the coordinates of the POINT_VEC3 in Vec2 format. -- @param #POINT_VEC3 self --- @return DCSTypes#Vec2 The Vec2 coodinate. +-- @return Dcs.DCSTypes#Vec2 The Vec2 coodinate. function POINT_VEC3:GetVec2() return { x = self.x, y = self.z } end @@ -14394,9 +9793,9 @@ end --- Return a random Vec2 within an Outer Radius and optionally NOT within an Inner Radius of the POINT_VEC3. -- @param #POINT_VEC3 self --- @param DCSTypes#Distance OuterRadius --- @param DCSTypes#Distance InnerRadius --- @return DCSTypes#Vec2 Vec2 +-- @param Dcs.DCSTypes#Distance OuterRadius +-- @param Dcs.DCSTypes#Distance InnerRadius +-- @return Dcs.DCSTypes#Vec2 Vec2 function POINT_VEC3:GetRandomVec2InRadius( OuterRadius, InnerRadius ) self:F2( { OuterRadius, InnerRadius } ) @@ -14425,8 +9824,8 @@ end --- Return a random POINT_VEC2 within an Outer Radius and optionally NOT within an Inner Radius of the POINT_VEC3. -- @param #POINT_VEC3 self --- @param DCSTypes#Distance OuterRadius --- @param DCSTypes#Distance InnerRadius +-- @param Dcs.DCSTypes#Distance OuterRadius +-- @param Dcs.DCSTypes#Distance InnerRadius -- @return #POINT_VEC2 function POINT_VEC3:GetRandomPointVec2InRadius( OuterRadius, InnerRadius ) self:F2( { OuterRadius, InnerRadius } ) @@ -14436,9 +9835,9 @@ end --- Return a random Vec3 within an Outer Radius and optionally NOT within an Inner Radius of the POINT_VEC3. -- @param #POINT_VEC3 self --- @param DCSTypes#Distance OuterRadius --- @param DCSTypes#Distance InnerRadius --- @return DCSTypes#Vec3 Vec3 +-- @param Dcs.DCSTypes#Distance OuterRadius +-- @param Dcs.DCSTypes#Distance InnerRadius +-- @return Dcs.DCSTypes#Vec3 Vec3 function POINT_VEC3:GetRandomVec3InRadius( OuterRadius, InnerRadius ) local RandomVec2 = self:GetRandomVec2InRadius( OuterRadius, InnerRadius ) @@ -14450,8 +9849,8 @@ end --- Return a random POINT_VEC3 within an Outer Radius and optionally NOT within an Inner Radius of the POINT_VEC3. -- @param #POINT_VEC3 self --- @param DCSTypes#Distance OuterRadius --- @param DCSTypes#Distance InnerRadius +-- @param Dcs.DCSTypes#Distance OuterRadius +-- @param Dcs.DCSTypes#Distance InnerRadius -- @return #POINT_VEC3 function POINT_VEC3:GetRandomPointVec3InRadius( OuterRadius, InnerRadius ) @@ -14462,7 +9861,7 @@ end --- Return a direction vector Vec3 from POINT_VEC3 to the POINT_VEC3. -- @param #POINT_VEC3 self -- @param #POINT_VEC3 TargetPointVec3 The target POINT_VEC3. --- @return DCSTypes#Vec3 DirectionVec3 The direction vector in Vec3 format. +-- @return Dcs.DCSTypes#Vec3 DirectionVec3 The direction vector in Vec3 format. function POINT_VEC3:GetDirectionVec3( TargetPointVec3 ) return { x = TargetPointVec3:GetX() - self:GetX(), y = TargetPointVec3:GetY() - self:GetY(), z = TargetPointVec3:GetZ() - self:GetZ() } end @@ -14480,7 +9879,7 @@ end --- Return a direction in radians from the POINT_VEC3 using a direction vector in Vec3 format. -- @param #POINT_VEC3 self --- @param DCSTypes#Vec3 DirectionVec3 The direction vector in Vec3 format. +-- @param Dcs.DCSTypes#Vec3 DirectionVec3 The direction vector in Vec3 format. -- @return #number DirectionRadians The direction in radians. function POINT_VEC3:GetDirectionRadians( DirectionVec3 ) local DirectionRadians = math.atan2( DirectionVec3.z, DirectionVec3.x ) @@ -14494,7 +9893,7 @@ end --- Return the 2D distance in meters between the target POINT_VEC3 and the POINT_VEC3. -- @param #POINT_VEC3 self -- @param #POINT_VEC3 TargetPointVec3 The target POINT_VEC3. --- @return DCSTypes#Distance Distance The distance in meters. +-- @return Dcs.DCSTypes#Distance Distance The distance in meters. function POINT_VEC3:Get2DDistance( TargetPointVec3 ) local TargetVec3 = TargetPointVec3:GetVec3() local SourceVec3 = self:GetVec3() @@ -14504,7 +9903,7 @@ end --- Return the 3D distance in meters between the target POINT_VEC3 and the POINT_VEC3. -- @param #POINT_VEC3 self -- @param #POINT_VEC3 TargetPointVec3 The target POINT_VEC3. --- @return DCSTypes#Distance Distance The distance in meters. +-- @return Dcs.DCSTypes#Distance Distance The distance in meters. function POINT_VEC3:Get3DDistance( TargetPointVec3 ) local TargetVec3 = TargetPointVec3:GetVec3() local SourceVec3 = self:GetVec3() @@ -14580,6 +9979,20 @@ function POINT_VEC3:IsMetric() return self.Metric end +--- Add a Distance in meters from the POINT_VEC3 horizontal plane, with the given angle, and calculate the new POINT_VEC3. +-- @param #POINT_VEC3 self +-- @param Dcs.DCSTypes#Distance Distance The Distance to be added in meters. +-- @param Dcs.DCSTypes#Angle Angle The Angle in degrees. +-- @return #POINT_VEC3 The new calculated POINT_VEC3. +function POINT_VEC3:Translate( Distance, Angle ) + local SX = self:GetX() + local SZ = self:GetZ() + local Radians = Angle / 180 * math.pi + local TX = Distance * math.cos( Radians ) + SX + local TZ = Distance * math.sin( Radians ) + SZ + + return POINT_VEC3:New( TX, self:GetY(), TZ ) +end @@ -14588,7 +10001,7 @@ end -- @param #POINT_VEC3.RoutePointAltType AltType The altitude type. -- @param #POINT_VEC3.RoutePointType Type The route point type. -- @param #POINT_VEC3.RoutePointAction Action The route point action. --- @param DCSTypes#Speed Speed Airspeed in km/h. +-- @param Dcs.DCSTypes#Speed Speed Airspeed in km/h. -- @param #boolean SpeedLocked true means the speed is locked. -- @return #table The route point. function POINT_VEC3:RoutePointAir( AltType, Type, Action, Speed, SpeedLocked ) @@ -14629,7 +10042,7 @@ end --- Build an ground type route point. -- @param #POINT_VEC3 self --- @param DCSTypes#Speed Speed Speed in km/h. +-- @param Dcs.DCSTypes#Speed Speed Speed in km/h. -- @param #POINT_VEC3.RoutePointAction Formation The route point Formation. -- @return #table The route point. function POINT_VEC3:RoutePointGround( Speed, Formation ) @@ -14669,7 +10082,7 @@ end --- Smokes the point in a color. -- @param #POINT_VEC3 self --- @param Point#POINT_VEC3.SmokeColor SmokeColor +-- @param Utilities.Utils#SMOKECOLOR SmokeColor function POINT_VEC3:Smoke( SmokeColor ) self:F2( { SmokeColor } ) trigger.action.smoke( self:GetVec3(), SmokeColor ) @@ -14679,41 +10092,41 @@ end -- @param #POINT_VEC3 self function POINT_VEC3:SmokeGreen() self:F2() - self:Smoke( POINT_VEC3.SmokeColor.Green ) + self:Smoke( SMOKECOLOR.Green ) end --- Smoke the POINT_VEC3 Red. -- @param #POINT_VEC3 self function POINT_VEC3:SmokeRed() self:F2() - self:Smoke( POINT_VEC3.SmokeColor.Red ) + self:Smoke( SMOKECOLOR.Red ) end --- Smoke the POINT_VEC3 White. -- @param #POINT_VEC3 self function POINT_VEC3:SmokeWhite() self:F2() - self:Smoke( POINT_VEC3.SmokeColor.White ) + self:Smoke( SMOKECOLOR.White ) end --- Smoke the POINT_VEC3 Orange. -- @param #POINT_VEC3 self function POINT_VEC3:SmokeOrange() self:F2() - self:Smoke( POINT_VEC3.SmokeColor.Orange ) + self:Smoke( SMOKECOLOR.Orange ) end --- Smoke the POINT_VEC3 Blue. -- @param #POINT_VEC3 self function POINT_VEC3:SmokeBlue() self:F2() - self:Smoke( POINT_VEC3.SmokeColor.Blue ) + self:Smoke( SMOKECOLOR.Blue ) end --- Flares the point in a color. -- @param #POINT_VEC3 self --- @param Point#POINT_VEC3.FlareColor --- @param DCSTypes#Azimuth (optional) Azimuth The azimuth of the flare direction. The default azimuth is 0. +-- @param Utilities.Utils#FLARECOLOR FlareColor +-- @param Dcs.DCSTypes#Azimuth (optional) Azimuth The azimuth of the flare direction. The default azimuth is 0. function POINT_VEC3:Flare( FlareColor, Azimuth ) self:F2( { FlareColor } ) trigger.action.signalFlare( self:GetVec3(), FlareColor, Azimuth and Azimuth or 0 ) @@ -14721,51 +10134,47 @@ end --- Flare the POINT_VEC3 White. -- @param #POINT_VEC3 self --- @param DCSTypes#Azimuth (optional) Azimuth The azimuth of the flare direction. The default azimuth is 0. +-- @param Dcs.DCSTypes#Azimuth (optional) Azimuth The azimuth of the flare direction. The default azimuth is 0. function POINT_VEC3:FlareWhite( Azimuth ) self:F2( Azimuth ) - self:Flare( POINT_VEC3.FlareColor.White, Azimuth ) + self:Flare( FLARECOLOR.White, Azimuth ) end --- Flare the POINT_VEC3 Yellow. -- @param #POINT_VEC3 self --- @param DCSTypes#Azimuth (optional) Azimuth The azimuth of the flare direction. The default azimuth is 0. +-- @param Dcs.DCSTypes#Azimuth (optional) Azimuth The azimuth of the flare direction. The default azimuth is 0. function POINT_VEC3:FlareYellow( Azimuth ) self:F2( Azimuth ) - self:Flare( POINT_VEC3.FlareColor.Yellow, Azimuth ) + self:Flare( FLARECOLOR.Yellow, Azimuth ) end --- Flare the POINT_VEC3 Green. -- @param #POINT_VEC3 self --- @param DCSTypes#Azimuth (optional) Azimuth The azimuth of the flare direction. The default azimuth is 0. +-- @param Dcs.DCSTypes#Azimuth (optional) Azimuth The azimuth of the flare direction. The default azimuth is 0. function POINT_VEC3:FlareGreen( Azimuth ) self:F2( Azimuth ) - self:Flare( POINT_VEC3.FlareColor.Green, Azimuth ) + self:Flare( FLARECOLOR.Green, Azimuth ) end --- Flare the POINT_VEC3 Red. -- @param #POINT_VEC3 self function POINT_VEC3:FlareRed( Azimuth ) self:F2( Azimuth ) - self:Flare( POINT_VEC3.FlareColor.Red, Azimuth ) + self:Flare( FLARECOLOR.Red, Azimuth ) end +end ---- The POINT_VEC2 class --- @type POINT_VEC2 --- @extends Point#POINT_VEC3 --- @field #number x The x coordinate in 2D space. --- @field #number y the y coordinate in 2D space. -POINT_VEC2 = { - ClassName = "POINT_VEC2", - } +do -- POINT_VEC2 ---- Create a new POINT_VEC2 object. + + +--- POINT_VEC2 constructor. -- @param #POINT_VEC2 self --- @param DCSTypes#Distance x The x coordinate of the Vec3 point, pointing to the North. --- @param DCSTypes#Distance y The y coordinate of the Vec3 point, pointing to the Right. --- @param DCSTypes#Distance LandHeightAdd (optional) The default height if required to be evaluated will be the land height of the x, y coordinate. You can specify an extra height to be added to the land height. --- @return Point#POINT_VEC2 +-- @param Dcs.DCSTypes#Distance x The x coordinate of the Vec3 point, pointing to the North. +-- @param Dcs.DCSTypes#Distance y The y coordinate of the Vec3 point, pointing to the Right. +-- @param Dcs.DCSTypes#Distance LandHeightAdd (optional) The default height if required to be evaluated will be the land height of the x, y coordinate. You can specify an extra height to be added to the land height. +-- @return Core.Point#POINT_VEC2 function POINT_VEC2:New( x, y, LandHeightAdd ) local LandHeight = land.getHeight( { ["x"] = x, ["y"] = y } ) @@ -14773,15 +10182,16 @@ function POINT_VEC2:New( x, y, LandHeightAdd ) LandHeightAdd = LandHeightAdd or 0 LandHeight = LandHeight + LandHeightAdd - local self = BASE:Inherit( self, POINT_VEC3:New( x, LandHeight, y ) ) + self = BASE:Inherit( self, POINT_VEC3:New( x, LandHeight, y ) ) + self:F2( self ) return self end --- Create a new POINT_VEC2 object from Vec2 coordinates. -- @param #POINT_VEC2 self --- @param DCSTypes#Vec2 Vec2 The Vec2 point. --- @return Point#POINT_VEC2 self +-- @param Dcs.DCSTypes#Vec2 Vec2 The Vec2 point. +-- @return Core.Point#POINT_VEC2 self function POINT_VEC2:NewFromVec2( Vec2, LandHeightAdd ) local LandHeight = land.getHeight( Vec2 ) @@ -14789,16 +10199,16 @@ function POINT_VEC2:NewFromVec2( Vec2, LandHeightAdd ) LandHeightAdd = LandHeightAdd or 0 LandHeight = LandHeight + LandHeightAdd - local self = BASE:Inherit( self, POINT_VEC3:New( Vec2.x, LandHeight, Vec2.y ) ) - self:F2( { Vec2.x, Vec2.y, LandHeightAdd } ) + self = BASE:Inherit( self, POINT_VEC3:New( Vec2.x, LandHeight, Vec2.y ) ) + self:F2( self ) return self end --- Create a new POINT_VEC2 object from Vec3 coordinates. -- @param #POINT_VEC2 self --- @param DCSTypes#Vec3 Vec3 The Vec3 point. --- @return Point#POINT_VEC2 self +-- @param Dcs.DCSTypes#Vec3 Vec3 The Vec3 point. +-- @return Core.Point#POINT_VEC2 self function POINT_VEC2:NewFromVec3( Vec3 ) local self = BASE:Inherit( self, BASE:New() ) @@ -14806,8 +10216,8 @@ function POINT_VEC2:NewFromVec3( Vec3 ) local LandHeight = land.getHeight( Vec2 ) - local self = BASE:Inherit( self, POINT_VEC3:New( Vec2.x, LandHeight, Vec2.y ) ) - self:F2( { Vec2.x, LandHeight, Vec2.y } ) + self = BASE:Inherit( self, POINT_VEC3:New( Vec2.x, LandHeight, Vec2.y ) ) + self:F2( self ) return self end @@ -14850,7 +10260,7 @@ end --- Calculate the distance from a reference @{#POINT_VEC2}. -- @param #POINT_VEC2 self -- @param #POINT_VEC2 PointVec2Reference The reference @{#POINT_VEC2}. --- @return DCSTypes#Distance The distance from the reference @{#POINT_VEC2} in meters. +-- @return Dcs.DCSTypes#Distance The distance from the reference @{#POINT_VEC2} in meters. function POINT_VEC2:DistanceFromPointVec2( PointVec2Reference ) self:F2( PointVec2Reference ) @@ -14860,10 +10270,10 @@ function POINT_VEC2:DistanceFromPointVec2( PointVec2Reference ) return Distance end ---- Calculate the distance from a reference @{DCSTypes#Vec2}. +--- Calculate the distance from a reference @{Dcs.DCSTypes#Vec2}. -- @param #POINT_VEC2 self --- @param DCSTypes#Vec2 Vec2Reference The reference @{DCSTypes#Vec2}. --- @return DCSTypes#Distance The distance from the reference @{DCSTypes#Vec2} in meters. +-- @param Dcs.DCSTypes#Vec2 Vec2Reference The reference @{Dcs.DCSTypes#Vec2}. +-- @return Dcs.DCSTypes#Distance The distance from the reference @{Dcs.DCSTypes#Vec2} in meters. function POINT_VEC2:DistanceFromVec2( Vec2Reference ) self:F2( Vec2Reference ) @@ -14883,8 +10293,8 @@ end --- Add a Distance in meters from the POINT_VEC2 orthonormal plane, with the given angle, and calculate the new POINT_VEC2. -- @param #POINT_VEC2 self --- @param DCSTypes#Distance Distance The Distance to be added in meters. --- @param DCSTypes#Angle Angle The Angle in degrees. +-- @param Dcs.DCSTypes#Distance Distance The Distance to be added in meters. +-- @param Dcs.DCSTypes#Angle Angle The Angle in degrees. -- @return #POINT_VEC2 The new calculated POINT_VEC2. function POINT_VEC2:Translate( Distance, Angle ) local SX = self:GetX() @@ -14896,78 +10306,6279 @@ function POINT_VEC2:Translate( Distance, Angle ) return POINT_VEC2:New( TX, TY ) end +end ---- The main include file for the MOOSE system. +--- This module contains the MESSAGE class. +-- +-- 1) @{Core.Message#MESSAGE} class, extends @{Core.Base#BASE} +-- ================================================= +-- Message System to display Messages to Clients, Coalitions or All. +-- Messages are shown on the display panel for an amount of seconds, and will then disappear. +-- Messages can contain a category which is indicating the category of the message. +-- +-- 1.1) MESSAGE construction methods +-- --------------------------------- +-- Messages are created with @{Core.Message#MESSAGE.New}. Note that when the MESSAGE object is created, no message is sent yet. +-- To send messages, you need to use the To functions. +-- +-- 1.2) Send messages with MESSAGE To methods +-- ------------------------------------------ +-- Messages are sent to: +-- +-- * Clients with @{Core.Message#MESSAGE.ToClient}. +-- * Coalitions with @{Core.Message#MESSAGE.ToCoalition}. +-- * All Players with @{Core.Message#MESSAGE.ToAll}. +-- +-- @module Message +-- @author FlightControl -Include.File( "Routines" ) -Include.File( "Utils" ) -Include.File( "Base" ) -Include.File( "Object" ) -Include.File( "Identifiable" ) -Include.File( "Positionable" ) -Include.File( "Controllable" ) -Include.File( "Scheduler" ) -Include.File( "Event" ) -Include.File( "Menu" ) -Include.File( "Group" ) -Include.File( "Unit" ) -Include.File( "Zone" ) -Include.File( "Client" ) -Include.File( "Static" ) -Include.File( "Airbase" ) -Include.File( "Database" ) -Include.File( "Set" ) -Include.File( "Point" ) -Include.File( "Moose" ) -Include.File( "Scoring" ) -Include.File( "Cargo" ) -Include.File( "Message" ) -Include.File( "Stage" ) -Include.File( "Task" ) -Include.File( "GoHomeTask" ) -Include.File( "DestroyBaseTask" ) -Include.File( "DestroyGroupsTask" ) -Include.File( "DestroyRadarsTask" ) -Include.File( "DestroyUnitTypesTask" ) -Include.File( "PickupTask" ) -Include.File( "DeployTask" ) -Include.File( "NoTask" ) -Include.File( "RouteTask" ) -Include.File( "Mission" ) -Include.File( "CleanUp" ) -Include.File( "Spawn" ) -Include.File( "Movement" ) -Include.File( "Sead" ) -Include.File( "Escort" ) -Include.File( "MissileTrainer" ) -Include.File( "PatrolZone" ) -Include.File( "AIBalancer" ) -Include.File( "AirbasePolice" ) +--- The MESSAGE class +-- @type MESSAGE +-- @extends Core.Base#BASE +MESSAGE = { + ClassName = "MESSAGE", + MessageCategory = 0, + MessageID = 0, +} -Include.File( "Detection" ) -Include.File( "DetectionManager" ) -Include.File( "StateMachine" ) +--- Creates a new MESSAGE object. Note that these MESSAGE objects are not yet displayed on the display panel. You must use the functions @{ToClient} or @{ToCoalition} or @{ToAll} to send these Messages to the respective recipients. +-- @param self +-- @param #string MessageText is the text of the Message. +-- @param #number MessageDuration is a number in seconds of how long the MESSAGE should be shown on the display panel. +-- @param #string MessageCategory (optional) is a string expressing the "category" of the Message. The category will be shown as the first text in the message followed by a ": ". +-- @return #MESSAGE +-- @usage +-- -- Create a series of new Messages. +-- -- MessageAll is meant to be sent to all players, for 25 seconds, and is classified as "Score". +-- -- MessageRED is meant to be sent to the RED players only, for 10 seconds, and is classified as "End of Mission", with ID "Win". +-- -- MessageClient1 is meant to be sent to a Client, for 25 seconds, and is classified as "Score", with ID "Score". +-- -- MessageClient1 is meant to be sent to a Client, for 25 seconds, and is classified as "Score", with ID "Score". +-- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", 25, "End of Mission" ) +-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", 25, "Penalty" ) +-- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", 25, "Score" ) +-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", 25, "Score") +function MESSAGE:New( MessageText, MessageDuration, MessageCategory ) + local self = BASE:Inherit( self, BASE:New() ) + self:F( { MessageText, MessageDuration, MessageCategory } ) -Include.File( "Process" ) -Include.File( "Process_Assign" ) -Include.File( "Process_Route" ) -Include.File( "Process_Smoke" ) -Include.File( "Process_Destroy" ) -Include.File( "Process_JTAC" ) + -- When no MessageCategory is given, we don't show it as a title... + if MessageCategory and MessageCategory ~= "" then + if MessageCategory:sub(-1) ~= "\n" then + self.MessageCategory = MessageCategory .. ": " + else + self.MessageCategory = MessageCategory:sub( 1, -2 ) .. ":\n" + end + else + self.MessageCategory = "" + end -Include.File( "Task" ) -Include.File( "Task_SEAD" ) -Include.File( "Task_A2G" ) + self.MessageDuration = MessageDuration or 5 + self.MessageTime = timer.getTime() + self.MessageText = MessageText + + self.MessageSent = false + self.MessageGroup = false + self.MessageCoalition = false --- The order of the declarations is important here. Don't touch it. + return self +end + +--- Sends a MESSAGE to a Client Group. Note that the Group needs to be defined within the ME with the skillset "Client" or "Player". +-- @param #MESSAGE self +-- @param Wrapper.Client#CLIENT Client is the Group of the Client. +-- @return #MESSAGE +-- @usage +-- -- Send the 2 messages created with the @{New} method to the Client Group. +-- -- Note that the Message of MessageClient2 is overwriting the Message of MessageClient1. +-- ClientGroup = Group.getByName( "ClientGroup" ) +-- +-- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ):ToClient( ClientGroup ) +-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ):ToClient( ClientGroup ) +-- or +-- MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ):ToClient( ClientGroup ) +-- MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ):ToClient( ClientGroup ) +-- or +-- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ) +-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ) +-- MessageClient1:ToClient( ClientGroup ) +-- MessageClient2:ToClient( ClientGroup ) +function MESSAGE:ToClient( Client ) + self:F( Client ) + + if Client and Client:GetClientGroupID() then + + local ClientGroupID = Client:GetClientGroupID() + self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration ) + trigger.action.outTextForGroup( ClientGroupID, self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration ) + end + + return self +end + +--- Sends a MESSAGE to a Group. +-- @param #MESSAGE self +-- @param Wrapper.Group#GROUP Group is the Group. +-- @return #MESSAGE +function MESSAGE:ToGroup( Group ) + self:F( Group.GroupName ) + + if Group then + + self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration ) + trigger.action.outTextForGroup( Group:GetID(), self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration ) + end + + return self +end +--- Sends a MESSAGE to the Blue coalition. +-- @param #MESSAGE self +-- @return #MESSAGE +-- @usage +-- -- Send a message created with the @{New} method to the BLUE coalition. +-- MessageBLUE = MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToBlue() +-- or +-- MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToBlue() +-- or +-- MessageBLUE = MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ) +-- MessageBLUE:ToBlue() +function MESSAGE:ToBlue() + self:F() + + self:ToCoalition( coalition.side.BLUE ) + + return self +end + +--- Sends a MESSAGE to the Red Coalition. +-- @param #MESSAGE self +-- @return #MESSAGE +-- @usage +-- -- Send a message created with the @{New} method to the RED coalition. +-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToRed() +-- or +-- MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToRed() +-- or +-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ) +-- MessageRED:ToRed() +function MESSAGE:ToRed( ) + self:F() + + self:ToCoalition( coalition.side.RED ) + + return self +end + +--- Sends a MESSAGE to a Coalition. +-- @param #MESSAGE self +-- @param CoalitionSide needs to be filled out by the defined structure of the standard scripting engine @{coalition.side}. +-- @return #MESSAGE +-- @usage +-- -- Send a message created with the @{New} method to the RED coalition. +-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToCoalition( coalition.side.RED ) +-- or +-- MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToCoalition( coalition.side.RED ) +-- or +-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ) +-- MessageRED:ToCoalition( coalition.side.RED ) +function MESSAGE:ToCoalition( CoalitionSide ) + self:F( CoalitionSide ) + + if CoalitionSide then + self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration ) + trigger.action.outTextForCoalition( CoalitionSide, self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration ) + end + + return self +end + +--- Sends a MESSAGE to all players. +-- @param #MESSAGE self +-- @return #MESSAGE +-- @usage +-- -- Send a message created to all players. +-- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ):ToAll() +-- or +-- MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ):ToAll() +-- or +-- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ) +-- MessageAll:ToAll() +function MESSAGE:ToAll() + self:F() + + self:ToCoalition( coalition.side.RED ) + self:ToCoalition( coalition.side.BLUE ) + + return self +end + + + +----- The MESSAGEQUEUE class +---- @type MESSAGEQUEUE +--MESSAGEQUEUE = { +-- ClientGroups = {}, +-- CoalitionSides = {} +--} +-- +--function MESSAGEQUEUE:New( RefreshInterval ) +-- local self = BASE:Inherit( self, BASE:New() ) +-- self:F( { RefreshInterval } ) +-- +-- self.RefreshInterval = RefreshInterval +-- +-- --self.DisplayFunction = routines.scheduleFunction( self._DisplayMessages, { self }, 0, RefreshInterval ) +-- self.DisplayFunction = SCHEDULER:New( self, self._DisplayMessages, {}, 0, RefreshInterval ) +-- +-- return self +--end +-- +----- This function is called automatically by the MESSAGEQUEUE scheduler. +--function MESSAGEQUEUE:_DisplayMessages() +-- +-- -- First we display all messages that a coalition needs to receive... Also those who are not in a client (CA module clients...). +-- for CoalitionSideID, CoalitionSideData in pairs( self.CoalitionSides ) do +-- for MessageID, MessageData in pairs( CoalitionSideData.Messages ) do +-- if MessageData.MessageSent == false then +-- --trigger.action.outTextForCoalition( CoalitionSideID, MessageData.MessageCategory .. '\n' .. MessageData.MessageText:gsub("\n$",""):gsub("\n$",""), MessageData.MessageDuration ) +-- MessageData.MessageSent = true +-- end +-- local MessageTimeLeft = ( MessageData.MessageTime + MessageData.MessageDuration ) - timer.getTime() +-- if MessageTimeLeft <= 0 then +-- MessageData = nil +-- end +-- end +-- end +-- +-- -- Then we send the messages for each individual client, but also to be included are those Coalition messages for the Clients who belong to a coalition. +-- -- Because the Client messages will overwrite the Coalition messages (for that Client). +-- for ClientGroupName, ClientGroupData in pairs( self.ClientGroups ) do +-- for MessageID, MessageData in pairs( ClientGroupData.Messages ) do +-- if MessageData.MessageGroup == false then +-- trigger.action.outTextForGroup( Group.getByName(ClientGroupName):getID(), MessageData.MessageCategory .. '\n' .. MessageData.MessageText:gsub("\n$",""):gsub("\n$",""), MessageData.MessageDuration ) +-- MessageData.MessageGroup = true +-- end +-- local MessageTimeLeft = ( MessageData.MessageTime + MessageData.MessageDuration ) - timer.getTime() +-- if MessageTimeLeft <= 0 then +-- MessageData = nil +-- end +-- end +-- +-- -- Now check if the Client also has messages that belong to the Coalition of the Client... +-- for CoalitionSideID, CoalitionSideData in pairs( self.CoalitionSides ) do +-- for MessageID, MessageData in pairs( CoalitionSideData.Messages ) do +-- local CoalitionGroup = Group.getByName( ClientGroupName ) +-- if CoalitionGroup and CoalitionGroup:getCoalition() == CoalitionSideID then +-- if MessageData.MessageCoalition == false then +-- trigger.action.outTextForGroup( Group.getByName(ClientGroupName):getID(), MessageData.MessageCategory .. '\n' .. MessageData.MessageText:gsub("\n$",""):gsub("\n$",""), MessageData.MessageDuration ) +-- MessageData.MessageCoalition = true +-- end +-- end +-- local MessageTimeLeft = ( MessageData.MessageTime + MessageData.MessageDuration ) - timer.getTime() +-- if MessageTimeLeft <= 0 then +-- MessageData = nil +-- end +-- end +-- end +-- end +-- +-- return true +--end +-- +----- The _MessageQueue object is created when the MESSAGE class module is loaded. +----_MessageQueue = MESSAGEQUEUE:New( 0.5 ) +-- +--- This module contains the FSM class. +-- This development is based on a state machine implementation made by Conroy Kyle. +-- The state machine can be found here: https://github.com/kyleconroy/lua-state-machine +-- +-- I've taken the development and enhanced it to make the state machine hierarchical... +-- It is a fantastic development, this module. +-- +-- === +-- +-- 1) @{Workflow#FSM} class, extends @{Core.Base#BASE} +-- ============================================== +-- +-- 1.1) Add or remove objects from the FSM +-- -------------------------------------------- +-- @module Fsm +-- @author FlightControl + +do -- FSM + + --- FSM class + -- @type FSM + -- @extends Core.Base#BASE + FSM = { + ClassName = "FSM", + } + + --- Creates a new FSM object. + -- @param #FSM self + -- @return #FSM + function FSM:New( FsmT ) + + -- Inherits from BASE + local self = BASE:Inherit( self, BASE:New() ) + + self.options = options or {} + self.options.subs = self.options.subs or {} + self.current = self.options.initial or 'none' + self.Events = {} + self.subs = {} + self.endstates = {} + + self.Scores = {} + + self._StartState = "none" + self._Transitions = {} + self._Processes = {} + self._EndStates = {} + self._Scores = {} + + self.CallScheduler = SCHEDULER:New( self ) + + + return self + end + + + function FSM:SetStartState( State ) + + self._StartState = State + self.current = State + end + + + function FSM:GetStartState() + + return self._StartState or {} + end + + function FSM:AddTransition( From, Event, To ) + + local Transition = {} + Transition.From = From + Transition.Event = Event + Transition.To = To + + self:E( Transition ) + + self._Transitions[Transition] = Transition + self:_eventmap( self.Events, Transition ) + end + + function FSM:GetTransitions() + + return self._Transitions or {} + end + + --- Set the default @{Process} template with key ProcessName providing the ProcessClass and the process object when it is assigned to a @{Controllable} by the task. + -- @return Core.Fsm#FSM_PROCESS + function FSM:AddProcess( From, Event, Process, ReturnEvents ) + self:E( { From, Event, Process, ReturnEvents } ) + + local Sub = {} + Sub.From = From + Sub.Event = Event + Sub.fsm = Process + Sub.StartEvent = "Start" + Sub.ReturnEvents = ReturnEvents + + self._Processes[Sub] = Sub + + self:_submap( self.subs, Sub, nil ) + + self:AddTransition( From, Event, From ) + + return Process + end + + function FSM:GetProcesses() + + return self._Processes or {} + end + + function FSM:GetProcess( From, Event ) + + for ProcessID, Process in pairs( self:GetProcesses() ) do + if Process.From == From and Process.Event == Event then + self:E( Process ) + return Process.fsm + end + end + + error( "Sub-Process from state " .. From .. " with event " .. Event .. " not found!" ) + end + + function FSM:AddEndState( State ) + + self._EndStates[State] = State + self.endstates[State] = State + end + + function FSM:GetEndStates() + + return self._EndStates or {} + end + + + --- Adds a score for the FSM to be achieved. + -- @param #FSM self + -- @param #string State is the state of the process when the score needs to be given. (See the relevant state descriptions of the process). + -- @param #string ScoreText is a text describing the score that is given according the status. + -- @param #number Score is a number providing the score of the status. + -- @return #FSM self + function FSM:AddScore( State, ScoreText, Score ) + self:F2( { State, ScoreText, Score } ) + + self._Scores[State] = self._Scores[State] or {} + self._Scores[State].ScoreText = ScoreText + self._Scores[State].Score = Score + + return self + end + + --- Adds a score for the FSM_PROCESS to be achieved. + -- @param #FSM self + -- @param #string From is the From State of the main process. + -- @param #string Event is the Event of the main process. + -- @param #string State is the state of the process when the score needs to be given. (See the relevant state descriptions of the process). + -- @param #string ScoreText is a text describing the score that is given according the status. + -- @param #number Score is a number providing the score of the status. + -- @return #FSM self + function FSM:AddScoreProcess( From, Event, State, ScoreText, Score ) + self:F2( { Event, State, ScoreText, Score } ) + + local Process = self:GetProcess( From, Event ) + + self:E( { Process = Process._Name, Scores = Process._Scores, State = State, ScoreText = ScoreText, Score = Score } ) + Process._Scores[State] = Process._Scores[State] or {} + Process._Scores[State].ScoreText = ScoreText + Process._Scores[State].Score = Score + + return Process + end + + function FSM:GetScores() + + return self._Scores or {} + end + + + function FSM:GetSubs() + + return self.options.subs + end + + + function FSM:LoadCallBacks( CallBackTable ) + + for name, callback in pairs( CallBackTable or {} ) do + self[name] = callback + end + + end + + function FSM:_eventmap( Events, EventStructure ) + + local Event = EventStructure.Event + local __Event = "__" .. EventStructure.Event + self[Event] = self[Event] or self:_create_transition(Event) + self[__Event] = self[__Event] or self:_delayed_transition(Event) + self:T( "Added methods: " .. Event .. ", " .. __Event ) + Events[Event] = self.Events[Event] or { map = {} } + self:_add_to_map( Events[Event].map, EventStructure ) + + end + + function FSM:_submap( subs, sub, name ) + self:F( { sub = sub, name = name } ) + subs[sub.From] = subs[sub.From] or {} + subs[sub.From][sub.Event] = subs[sub.From][sub.Event] or {} + + -- Make the reference table weak. + -- setmetatable( subs[sub.From][sub.Event], { __mode = "k" } ) + + subs[sub.From][sub.Event][sub] = {} + subs[sub.From][sub.Event][sub].fsm = sub.fsm + subs[sub.From][sub.Event][sub].StartEvent = sub.StartEvent + subs[sub.From][sub.Event][sub].ReturnEvents = sub.ReturnEvents or {} -- these events need to be given to find the correct continue event ... if none given, the processing will stop. + subs[sub.From][sub.Event][sub].name = name + subs[sub.From][sub.Event][sub].fsmparent = self + end + + + function FSM:_call_handler(handler, params) + if self[handler] then + self:E( "Calling " .. handler ) + return self[handler]( self, unpack(params) ) + end + end + + function FSM._handler( self, EventName, ... ) + + self:E( { EventName, ... } ) + + local can, to = self:can( EventName ) + self:E( { EventName, self.current, can, to } ) + + local ReturnValues = nil + + if can then + local from = self.current + local params = { EventName, from, to, ... } + + if self:_call_handler("onbefore" .. EventName, params) == false + or self:_call_handler("onleave" .. from, params) == false then + return false + end + + self.current = to + + local execute = true + + local subtable = self:_gosub( from, EventName ) + for _, sub in pairs( subtable ) do + --if sub.nextevent then + -- self:F2( "nextevent = " .. sub.nextevent ) + -- self[sub.nextevent]( self ) + --end + self:E( "calling sub start event: " .. sub.StartEvent ) + sub.fsm.fsmparent = self + sub.fsm.ReturnEvents = sub.ReturnEvents + sub.fsm[sub.StartEvent]( sub.fsm ) + execute = true + end + + local fsmparent, Event = self:_isendstate( to ) + if fsmparent and Event then + self:F2( { "end state: ", fsmparent, Event } ) + self:_call_handler("onenter" .. to, params) + self:_call_handler("onafter" .. EventName, params) + self:_call_handler("onstatechange", params) + fsmparent[Event]( fsmparent ) + execute = false + end + + if execute then + -- only execute the call if the From state is not equal to the To state! Otherwise this function should never execute! + if from ~= to then + self:T3( { onenter = "onenter" .. to, callback = self["onenter" .. to] } ) + self:_call_handler("onenter" .. to, params) + end + + self:T3( { On = "OnBefore" .. to, callback = self["OnBefore" .. to] } ) + if ( self:_call_handler("OnBefore" .. to, params ) ~= false ) then + + self:T3( { onafter = "onafter" .. EventName, callback = self["onafter" .. EventName] } ) + self:_call_handler("onafter" .. EventName, params) + + self:T3( { On = "OnAfter" .. to, callback = self["OnAfter" .. to] } ) + ReturnValues = self:_call_handler("OnAfter" .. to, params ) + end + + self:_call_handler("onstatechange", params) + end + + return ReturnValues + end + + return nil + end + + function FSM:_delayed_transition( EventName ) + self:E( { EventName = EventName } ) + return function( self, DelaySeconds, ... ) + self:T( "Delayed Event: " .. EventName ) + local CallID = self.CallScheduler:Schedule( self, self._handler, { EventName, ... }, DelaySeconds or 1 ) + self:T( { CallID = CallID } ) + end + end + + function FSM:_create_transition( EventName ) + self:E( { Event = EventName } ) + return function( self, ... ) return self._handler( self, EventName , ... ) end + end + + function FSM:_gosub( ParentFrom, ParentEvent ) + local fsmtable = {} + if self.subs[ParentFrom] and self.subs[ParentFrom][ParentEvent] then + self:E( { ParentFrom, ParentEvent, self.subs[ParentFrom], self.subs[ParentFrom][ParentEvent] } ) + return self.subs[ParentFrom][ParentEvent] + else + return {} + end + end + + function FSM:_isendstate( Current ) + local FSMParent = self.fsmparent + if FSMParent and self.endstates[Current] then + self:E( { state = Current, endstates = self.endstates, endstate = self.endstates[Current] } ) + FSMParent.current = Current + local ParentFrom = FSMParent.current + self:E( ParentFrom ) + self:E( self.ReturnEvents ) + local Event = self.ReturnEvents[Current] + self:E( { ParentFrom, Event, self.ReturnEvents } ) + if Event then + return FSMParent, Event + else + self:E( { "Could not find parent event name for state ", ParentFrom } ) + end + end + + return nil + end + + function FSM:_add_to_map( Map, Event ) + self:F3( { Map, Event } ) + if type(Event.From) == 'string' then + Map[Event.From] = Event.To + else + for _, From in ipairs(Event.From) do + Map[From] = Event.To + end + end + self:T3( { Map, Event } ) + end + + function FSM:GetState() + return self.current + end + + + function FSM:Is( State ) + return self.current == State + end + + function FSM:is(state) + return self.current == state + end + + function FSM:can(e) + self:E( { e, self.Events, self.Events[e] } ) + local Event = self.Events[e] + self:F3( { self.current, Event } ) + local To = Event and Event.map[self.current] or Event.map['*'] + return To ~= nil, To + end + + function FSM:cannot(e) + return not self:can(e) + end + +end + +do -- FSM_CONTROLLABLE + + --- FSM_CONTROLLABLE class + -- @type FSM_CONTROLLABLE + -- @field Wrapper.Controllable#CONTROLLABLE Controllable + -- @extends Core.Fsm#FSM + FSM_CONTROLLABLE = { + ClassName = "FSM_CONTROLLABLE", + } + + --- Creates a new FSM_CONTROLLABLE object. + -- @param #FSM_CONTROLLABLE self + -- @param #table FSMT Finite State Machine Table + -- @param Wrapper.Controllable#CONTROLLABLE Controllable (optional) The CONTROLLABLE object that the FSM_CONTROLLABLE governs. + -- @return #FSM_CONTROLLABLE + function FSM_CONTROLLABLE:New( FSMT, Controllable ) + + -- Inherits from BASE + local self = BASE:Inherit( self, FSM:New( FSMT ) ) -- Core.Fsm#FSM_CONTROLLABLE + + if Controllable then + self:SetControllable( Controllable ) + end + + return self + end + + --- Sets the CONTROLLABLE object that the FSM_CONTROLLABLE governs. + -- @param #FSM_CONTROLLABLE self + -- @param Wrapper.Controllable#CONTROLLABLE FSMControllable + -- @return #FSM_CONTROLLABLE + function FSM_CONTROLLABLE:SetControllable( FSMControllable ) + self:F( FSMControllable ) + self.Controllable = FSMControllable + end + + --- Gets the CONTROLLABLE object that the FSM_CONTROLLABLE governs. + -- @param #FSM_CONTROLLABLE self + -- @return Wrapper.Controllable#CONTROLLABLE + function FSM_CONTROLLABLE:GetControllable() + return self.Controllable + end + + function FSM_CONTROLLABLE:_call_handler( handler, params ) + + local ErrorHandler = function( errmsg ) + + env.info( "Error in SCHEDULER function:" .. errmsg ) + if debug ~= nil then + env.info( debug.traceback() ) + end + + return errmsg + end + + if self[handler] then + self:E( "Calling " .. handler ) + return xpcall( function() return self[handler]( self, self.Controllable, unpack( params ) ) end, ErrorHandler ) + --return self[handler]( self, self.Controllable, unpack( params ) ) + end + end + +end + +do -- FSM_PROCESS + + --- FSM_PROCESS class + -- @type FSM_PROCESS + -- @field Tasking.Task#TASK Task + -- @extends Core.Fsm#FSM_CONTROLLABLE + FSM_PROCESS = { + ClassName = "FSM_PROCESS", + } + + --- Creates a new FSM_PROCESS object. + -- @param #FSM_PROCESS self + -- @return #FSM_PROCESS + function FSM_PROCESS:New( Controllable, Task ) + + local self = BASE:Inherit( self, FSM_CONTROLLABLE:New() ) -- Core.Fsm#FSM_PROCESS + + self:F( Controllable, Task ) + + self:Assign( Controllable, Task ) + + return self + end + + function FSM_PROCESS:Init( FsmProcess ) + self:E( "No Initialisation" ) + end + + --- Creates a new FSM_PROCESS object based on this FSM_PROCESS. + -- @param #FSM_PROCESS self + -- @return #FSM_PROCESS + function FSM_PROCESS:Copy( Controllable, Task ) + self:E( { self:GetClassNameAndID() } ) + + local NewFsm = self:New( Controllable, Task ) -- Core.Fsm#FSM_PROCESS + + NewFsm:Assign( Controllable, Task ) + + -- Polymorphic call to initialize the new FSM_PROCESS based on self FSM_PROCESS + NewFsm:Init( self ) + + -- Set Start State + NewFsm:SetStartState( self:GetStartState() ) + + -- Copy Transitions + for TransitionID, Transition in pairs( self:GetTransitions() ) do + NewFsm:AddTransition( Transition.From, Transition.Event, Transition.To ) + end + + -- Copy Processes + for ProcessID, Process in pairs( self:GetProcesses() ) do + self:E( { Process} ) + local FsmProcess = NewFsm:AddProcess( Process.From, Process.Event, Process.fsm:Copy( Controllable, Task ), Process.ReturnEvents ) + end + + -- Copy End States + for EndStateID, EndState in pairs( self:GetEndStates() ) do + self:E( EndState ) + NewFsm:AddEndState( EndState ) + end + + -- Copy the score tables + for ScoreID, Score in pairs( self:GetScores() ) do + self:E( Score ) + NewFsm:AddScore( ScoreID, Score.ScoreText, Score.Score ) + end + + return NewFsm + end + + --- Sets the task of the process. + -- @param #FSM_PROCESS self + -- @param Tasking.Task#TASK Task + -- @return #FSM_PROCESS + function FSM_PROCESS:SetTask( Task ) + + self.Task = Task + + return self + end + + --- Gets the task of the process. + -- @param #FSM_PROCESS self + -- @return Tasking.Task#TASK + function FSM_PROCESS:GetTask() + + return self.Task + end + + --- Gets the mission of the process. + -- @param #FSM_PROCESS self + -- @return Tasking.Mission#MISSION + function FSM_PROCESS:GetMission() + + return self.Task.Mission + end + + --- Gets the mission of the process. + -- @param #FSM_PROCESS self + -- @return Tasking.CommandCenter#COMMANDCENTER + function FSM_PROCESS:GetCommandCenter() + + return self:GetTask():GetMission():GetCommandCenter() + end + + --- Send a message of the @{Task} to the Group of the Unit. +-- @param #FSM_PROCESS self +function FSM_PROCESS:Message( Message ) + self:F( { Message = Message } ) + + local CC = self:GetCommandCenter() + local TaskGroup = self.Controllable:GetGroup() + + CC:MessageToGroup( Message, TaskGroup ) +end + + + + + --- Assign the process to a @{Unit} and activate the process. + -- @param #FSM_PROCESS self + -- @param Task.Tasking#TASK Task + -- @param Wrapper.Unit#UNIT ProcessUnit + -- @return #FSM_PROCESS self + function FSM_PROCESS:Assign( ProcessUnit, Task ) + self:E( { Task, ProcessUnit } ) + + self:SetControllable( ProcessUnit ) + self:SetTask( Task ) + + --self.ProcessGroup = ProcessUnit:GetGroup() + + return self + end + + --- Adds a score for the FSM_PROCESS to be achieved. + -- @param #FSM_PROCESS self + -- @param #string State is the state of the process when the score needs to be given. (See the relevant state descriptions of the process). + -- @param #string ScoreText is a text describing the score that is given according the status. + -- @param #number Score is a number providing the score of the status. + -- @return #FSM_PROCESS self + function FSM_PROCESS:AddScore( State, ScoreText, Score ) + self:F2( { State, ScoreText, Score } ) + + self.Scores[State] = self.Scores[State] or {} + self.Scores[State].ScoreText = ScoreText + self.Scores[State].Score = Score + + return self + end + + function FSM_PROCESS:onenterAssigned( ProcessUnit ) + self:E( "Assign" ) + + self.Task:Assign() + end + + function FSM_PROCESS:onenterFailed( ProcessUnit ) + self:E( "Failed" ) + + self.Task:Fail() + end + + function FSM_PROCESS:onenterSuccess( ProcessUnit ) + self:E( "Success" ) + + self.Task:Success() + end + + --- StateMachine callback function for a FSM_PROCESS + -- @param #FSM_PROCESS self + -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit + -- @param #string Event + -- @param #string From + -- @param #string To + function FSM_PROCESS:onstatechange( ProcessUnit, Event, From, To, Dummy ) + self:E( { ProcessUnit, Event, From, To, Dummy, self:IsTrace() } ) + + if self:IsTrace() then + MESSAGE:New( "@ Process " .. self:GetClassNameAndID() .. " : " .. Event .. " changed to state " .. To, 2 ):ToAll() + end + + self:E( self.Scores[To] ) + -- TODO: This needs to be reworked with a callback functions allocated within Task, and set within the mission script from the Task Objects... + if self.Scores[To] then + + local Task = self.Task + local Scoring = Task:GetScoring() + if Scoring then + Scoring:_AddMissionTaskScore( Task.Mission, ProcessUnit, self.Scores[To].ScoreText, self.Scores[To].Score ) + end + end + end + +end + +do -- FSM_TASK + + --- FSM_TASK class + -- @type FSM_TASK + -- @field Tasking.Task#TASK Task + -- @extends Core.Fsm#FSM + FSM_TASK = { + ClassName = "FSM_TASK", + } + + --- Creates a new FSM_TASK object. + -- @param #FSM_TASK self + -- @param #table FSMT + -- @param Tasking.Task#TASK Task + -- @param Wrapper.Unit#UNIT TaskUnit + -- @return #FSM_TASK + function FSM_TASK:New( FSMT ) + + local self = BASE:Inherit( self, FSM_CONTROLLABLE:New( FSMT ) ) -- Core.Fsm#FSM_TASK + + self["onstatechange"] = self.OnStateChange + + return self + end + + function FSM_TASK:_call_handler( handler, params ) + if self[handler] then + self:E( "Calling " .. handler ) + return self[handler]( self, unpack( params ) ) + end + end + +end -- FSM_TASK + +do -- FSM_SET + + --- FSM_SET class + -- @type FSM_SET + -- @field Core.Set#SET_BASE Set + -- @extends Core.Fsm#FSM + FSM_SET = { + ClassName = "FSM_SET", + } + + --- Creates a new FSM_SET object. + -- @param #FSM_SET self + -- @param #table FSMT Finite State Machine Table + -- @param Set_SET_BASE FSMSet (optional) The Set object that the FSM_SET governs. + -- @return #FSM_SET + function FSM_SET:New( FSMSet ) + + -- Inherits from BASE + local self = BASE:Inherit( self, FSM:New() ) -- Core.Fsm#FSM_SET + + if FSMSet then + self:Set( FSMSet ) + end + + return self + end + + --- Sets the SET_BASE object that the FSM_SET governs. + -- @param #FSM_SET self + -- @param Core.Set#SET_BASE FSMSet + -- @return #FSM_SET + function FSM_SET:Set( FSMSet ) + self:F( FSMSet ) + self.Set = FSMSet + end + + --- Gets the SET_BASE object that the FSM_SET governs. + -- @param #FSM_SET self + -- @return Core.Set#SET_BASE + function FSM_SET:Get() + return self.Controllable + end + + function FSM_SET:_call_handler( handler, params ) + if self[handler] then + self:E( "Calling " .. handler ) + return self[handler]( self, self.Set, unpack( params ) ) + end + end + +end -- FSM_SET + +--- This module contains the OBJECT class. +-- +-- 1) @{Wrapper.Object#OBJECT} class, extends @{Core.Base#BASE} +-- =========================================================== +-- The @{Wrapper.Object#OBJECT} class is a wrapper class to handle the DCS Object objects: +-- +-- * Support all DCS Object APIs. +-- * Enhance with Object specific APIs not in the DCS Object API set. +-- * Manage the "state" of the DCS Object. +-- +-- 1.1) OBJECT constructor: +-- ------------------------------ +-- The OBJECT class provides the following functions to construct a OBJECT instance: +-- +-- * @{Wrapper.Object#OBJECT.New}(): Create a OBJECT instance. +-- +-- 1.2) OBJECT methods: +-- -------------------------- +-- The following methods can be used to identify an Object object: +-- +-- * @{Wrapper.Object#OBJECT.GetID}(): Returns the ID of the Object object. +-- +-- === +-- +-- @module Object +-- @author FlightControl + +--- The OBJECT class +-- @type OBJECT +-- @extends Core.Base#BASE +-- @field #string ObjectName The name of the Object. +OBJECT = { + ClassName = "OBJECT", + ObjectName = "", +} + + +--- A DCSObject +-- @type DCSObject +-- @field id_ The ID of the controllable in DCS + +--- Create a new OBJECT from a DCSObject +-- @param #OBJECT self +-- @param Dcs.DCSWrapper.Object#Object ObjectName The Object name +-- @return #OBJECT self +function OBJECT:New( ObjectName ) + local self = BASE:Inherit( self, BASE:New() ) + self:F2( ObjectName ) + self.ObjectName = ObjectName + return self +end + + +--- Returns the unit's unique identifier. +-- @param Wrapper.Object#OBJECT self +-- @return Dcs.DCSWrapper.Object#Object.ID ObjectID +-- @return #nil The DCS Object is not existing or alive. +function OBJECT:GetID() + self:F2( self.ObjectName ) + + local DCSObject = self:GetDCSObject() + + if DCSObject then + local ObjectID = DCSObject:getID() + return ObjectID + end + + return nil +end + +--- Destroys the OBJECT. +-- @param #OBJECT self +-- @return #nil The DCS Unit is not existing or alive. +function OBJECT:Destroy() + self:F2( self.ObjectName ) + + local DCSObject = self:GetDCSObject() + + if DCSObject then + + DCSObject:destroy() + end + + return nil +end + + + + +--- This module contains the IDENTIFIABLE class. +-- +-- 1) @{#IDENTIFIABLE} class, extends @{Wrapper.Object#OBJECT} +-- =============================================================== +-- The @{#IDENTIFIABLE} class is a wrapper class to handle the DCS Identifiable objects: +-- +-- * Support all DCS Identifiable APIs. +-- * Enhance with Identifiable specific APIs not in the DCS Identifiable API set. +-- * Manage the "state" of the DCS Identifiable. +-- +-- 1.1) IDENTIFIABLE constructor: +-- ------------------------------ +-- The IDENTIFIABLE class provides the following functions to construct a IDENTIFIABLE instance: +-- +-- * @{#IDENTIFIABLE.New}(): Create a IDENTIFIABLE instance. +-- +-- 1.2) IDENTIFIABLE methods: +-- -------------------------- +-- The following methods can be used to identify an identifiable object: +-- +-- * @{#IDENTIFIABLE.GetName}(): Returns the name of the Identifiable. +-- * @{#IDENTIFIABLE.IsAlive}(): Returns if the Identifiable is alive. +-- * @{#IDENTIFIABLE.GetTypeName}(): Returns the type name of the Identifiable. +-- * @{#IDENTIFIABLE.GetCoalition}(): Returns the coalition of the Identifiable. +-- * @{#IDENTIFIABLE.GetCountry}(): Returns the country of the Identifiable. +-- * @{#IDENTIFIABLE.GetDesc}(): Returns the descriptor structure of the Identifiable. +-- +-- +-- === +-- +-- @module Identifiable +-- @author FlightControl + +--- The IDENTIFIABLE class +-- @type IDENTIFIABLE +-- @extends Wrapper.Object#OBJECT +-- @field #string IdentifiableName The name of the identifiable. +IDENTIFIABLE = { + ClassName = "IDENTIFIABLE", + IdentifiableName = "", +} + +local _CategoryName = { + [Unit.Category.AIRPLANE] = "Airplane", + [Unit.Category.HELICOPTER] = "Helicoper", + [Unit.Category.GROUND_UNIT] = "Ground Identifiable", + [Unit.Category.SHIP] = "Ship", + [Unit.Category.STRUCTURE] = "Structure", + } + +--- Create a new IDENTIFIABLE from a DCSIdentifiable +-- @param #IDENTIFIABLE self +-- @param Dcs.DCSWrapper.Identifiable#Identifiable IdentifiableName The DCS Identifiable name +-- @return #IDENTIFIABLE self +function IDENTIFIABLE:New( IdentifiableName ) + local self = BASE:Inherit( self, OBJECT:New( IdentifiableName ) ) + self:F2( IdentifiableName ) + self.IdentifiableName = IdentifiableName + return self +end + +--- Returns if the Identifiable is alive. +-- @param #IDENTIFIABLE self +-- @return #boolean true if Identifiable is alive. +-- @return #nil The DCS Identifiable is not existing or alive. +function IDENTIFIABLE:IsAlive() + self:F3( self.IdentifiableName ) + + local DCSIdentifiable = self:GetDCSObject() + + if DCSIdentifiable then + local IdentifiableIsAlive = DCSIdentifiable:isExist() + return IdentifiableIsAlive + end + + return false +end + + + + +--- Returns DCS Identifiable object name. +-- The function provides access to non-activated objects too. +-- @param #IDENTIFIABLE self +-- @return #string The name of the DCS Identifiable. +-- @return #nil The DCS Identifiable is not existing or alive. +function IDENTIFIABLE:GetName() + self:F2( self.IdentifiableName ) + + local DCSIdentifiable = self:GetDCSObject() + + if DCSIdentifiable then + local IdentifiableName = self.IdentifiableName + return IdentifiableName + end + + self:E( self.ClassName .. " " .. self.IdentifiableName .. " not found!" ) + return nil +end + + +--- Returns the type name of the DCS Identifiable. +-- @param #IDENTIFIABLE self +-- @return #string The type name of the DCS Identifiable. +-- @return #nil The DCS Identifiable is not existing or alive. +function IDENTIFIABLE:GetTypeName() + self:F2( self.IdentifiableName ) + + local DCSIdentifiable = self:GetDCSObject() + + if DCSIdentifiable then + local IdentifiableTypeName = DCSIdentifiable:getTypeName() + self:T3( IdentifiableTypeName ) + return IdentifiableTypeName + end + + self:E( self.ClassName .. " " .. self.IdentifiableName .. " not found!" ) + return nil +end + + +--- Returns category of the DCS Identifiable. +-- @param #IDENTIFIABLE self +-- @return Dcs.DCSWrapper.Object#Object.Category The category ID +function IDENTIFIABLE:GetCategory() + self:F2( self.ObjectName ) + + local DCSObject = self:GetDCSObject() + if DCSObject then + local ObjectCategory = DCSObject:getCategory() + self:T3( ObjectCategory ) + return ObjectCategory + end + + return nil +end + + +--- Returns the DCS Identifiable category name as defined within the DCS Identifiable Descriptor. +-- @param #IDENTIFIABLE self +-- @return #string The DCS Identifiable Category Name +function IDENTIFIABLE:GetCategoryName() + local DCSIdentifiable = self:GetDCSObject() + + if DCSIdentifiable then + local IdentifiableCategoryName = _CategoryName[ self:GetDesc().category ] + return IdentifiableCategoryName + end + + self:E( self.ClassName .. " " .. self.IdentifiableName .. " not found!" ) + return nil +end + +--- Returns coalition of the Identifiable. +-- @param #IDENTIFIABLE self +-- @return Dcs.DCSCoalitionWrapper.Object#coalition.side The side of the coalition. +-- @return #nil The DCS Identifiable is not existing or alive. +function IDENTIFIABLE:GetCoalition() + self:F2( self.IdentifiableName ) + + local DCSIdentifiable = self:GetDCSObject() + + if DCSIdentifiable then + local IdentifiableCoalition = DCSIdentifiable:getCoalition() + self:T3( IdentifiableCoalition ) + return IdentifiableCoalition + end + + self:E( self.ClassName .. " " .. self.IdentifiableName .. " not found!" ) + return nil +end + +--- Returns country of the Identifiable. +-- @param #IDENTIFIABLE self +-- @return Dcs.DCScountry#country.id The country identifier. +-- @return #nil The DCS Identifiable is not existing or alive. +function IDENTIFIABLE:GetCountry() + self:F2( self.IdentifiableName ) + + local DCSIdentifiable = self:GetDCSObject() + + if DCSIdentifiable then + local IdentifiableCountry = DCSIdentifiable:getCountry() + self:T3( IdentifiableCountry ) + return IdentifiableCountry + end + + self:E( self.ClassName .. " " .. self.IdentifiableName .. " not found!" ) + return nil +end + + + +--- Returns Identifiable descriptor. Descriptor type depends on Identifiable category. +-- @param #IDENTIFIABLE self +-- @return Dcs.DCSWrapper.Identifiable#Identifiable.Desc The Identifiable descriptor. +-- @return #nil The DCS Identifiable is not existing or alive. +function IDENTIFIABLE:GetDesc() + self:F2( self.IdentifiableName ) + + local DCSIdentifiable = self:GetDCSObject() + + if DCSIdentifiable then + local IdentifiableDesc = DCSIdentifiable:getDesc() + self:T2( IdentifiableDesc ) + return IdentifiableDesc + end + + self:E( self.ClassName .. " " .. self.IdentifiableName .. " not found!" ) + return nil +end + + + + + + + + + +--- This module contains the POSITIONABLE class. +-- +-- 1) @{Wrapper.Positionable#POSITIONABLE} class, extends @{Wrapper.Identifiable#IDENTIFIABLE} +-- =========================================================== +-- The @{Wrapper.Positionable#POSITIONABLE} class is a wrapper class to handle the POSITIONABLE objects: +-- +-- * Support all DCS APIs. +-- * Enhance with POSITIONABLE specific APIs not in the DCS API set. +-- * Manage the "state" of the POSITIONABLE. +-- +-- 1.1) POSITIONABLE constructor: +-- ------------------------------ +-- The POSITIONABLE class provides the following functions to construct a POSITIONABLE instance: +-- +-- * @{Wrapper.Positionable#POSITIONABLE.New}(): Create a POSITIONABLE instance. +-- +-- 1.2) POSITIONABLE methods: +-- -------------------------- +-- The following methods can be used to identify an measurable object: +-- +-- * @{Wrapper.Positionable#POSITIONABLE.GetID}(): Returns the ID of the measurable object. +-- * @{Wrapper.Positionable#POSITIONABLE.GetName}(): Returns the name of the measurable object. +-- +-- === +-- +-- @module Positionable +-- @author FlightControl + +--- The POSITIONABLE class +-- @type POSITIONABLE +-- @extends Wrapper.Identifiable#IDENTIFIABLE +-- @field #string PositionableName The name of the measurable. +POSITIONABLE = { + ClassName = "POSITIONABLE", + PositionableName = "", +} + +--- A DCSPositionable +-- @type DCSPositionable +-- @field id_ The ID of the controllable in DCS + +--- Create a new POSITIONABLE from a DCSPositionable +-- @param #POSITIONABLE self +-- @param Dcs.DCSWrapper.Positionable#Positionable PositionableName The POSITIONABLE name +-- @return #POSITIONABLE self +function POSITIONABLE:New( PositionableName ) + local self = BASE:Inherit( self, IDENTIFIABLE:New( PositionableName ) ) + + return self +end + +--- Returns the @{Dcs.DCSTypes#Position3} position vectors indicating the point and direction vectors in 3D of the POSITIONABLE within the mission. +-- @param Wrapper.Positionable#POSITIONABLE self +-- @return Dcs.DCSTypes#Position The 3D position vectors of the POSITIONABLE. +-- @return #nil The POSITIONABLE is not existing or alive. +function POSITIONABLE:GetPositionVec3() + self:F2( self.PositionableName ) + + local DCSPositionable = self:GetDCSObject() + + if DCSPositionable then + local PositionablePosition = DCSPositionable:getPosition() + self:T3( PositionablePosition ) + return PositionablePosition + end + + return nil +end + +--- Returns the @{Dcs.DCSTypes#Vec2} vector indicating the point in 2D of the POSITIONABLE within the mission. +-- @param Wrapper.Positionable#POSITIONABLE self +-- @return Dcs.DCSTypes#Vec2 The 2D point vector of the POSITIONABLE. +-- @return #nil The POSITIONABLE is not existing or alive. +function POSITIONABLE:GetVec2() + self:F2( self.PositionableName ) + + local DCSPositionable = self:GetDCSObject() + + if DCSPositionable then + local PositionableVec3 = DCSPositionable:getPosition().p + + local PositionableVec2 = {} + PositionableVec2.x = PositionableVec3.x + PositionableVec2.y = PositionableVec3.z + + self:T2( PositionableVec2 ) + return PositionableVec2 + end + + return nil +end + +--- Returns a POINT_VEC2 object indicating the point in 2D of the POSITIONABLE within the mission. +-- @param Wrapper.Positionable#POSITIONABLE self +-- @return Core.Point#POINT_VEC2 The 2D point vector of the POSITIONABLE. +-- @return #nil The POSITIONABLE is not existing or alive. +function POSITIONABLE:GetPointVec2() + self:F2( self.PositionableName ) + + local DCSPositionable = self:GetDCSObject() + + if DCSPositionable then + local PositionableVec3 = DCSPositionable:getPosition().p + + local PositionablePointVec2 = POINT_VEC2:NewFromVec3( PositionableVec3 ) + + self:T2( PositionablePointVec2 ) + return PositionablePointVec2 + end + + return nil +end + + +--- Returns a random @{Dcs.DCSTypes#Vec3} vector within a range, indicating the point in 3D of the POSITIONABLE within the mission. +-- @param Wrapper.Positionable#POSITIONABLE self +-- @return Dcs.DCSTypes#Vec3 The 3D point vector of the POSITIONABLE. +-- @return #nil The POSITIONABLE is not existing or alive. +function POSITIONABLE:GetRandomVec3( Radius ) + self:F2( self.PositionableName ) + + local DCSPositionable = self:GetDCSObject() + + if DCSPositionable then + local PositionablePointVec3 = DCSPositionable:getPosition().p + local PositionableRandomVec3 = {} + local angle = math.random() * math.pi*2; + PositionableRandomVec3.x = PositionablePointVec3.x + math.cos( angle ) * math.random() * Radius; + PositionableRandomVec3.y = PositionablePointVec3.y + PositionableRandomVec3.z = PositionablePointVec3.z + math.sin( angle ) * math.random() * Radius; + + self:T3( PositionableRandomVec3 ) + return PositionableRandomVec3 + end + + return nil +end + +--- Returns the @{Dcs.DCSTypes#Vec3} vector indicating the 3D vector of the POSITIONABLE within the mission. +-- @param Wrapper.Positionable#POSITIONABLE self +-- @return Dcs.DCSTypes#Vec3 The 3D point vector of the POSITIONABLE. +-- @return #nil The POSITIONABLE is not existing or alive. +function POSITIONABLE:GetVec3() + self:F2( self.PositionableName ) + + local DCSPositionable = self:GetDCSObject() + + if DCSPositionable then + local PositionableVec3 = DCSPositionable:getPosition().p + self:T3( PositionableVec3 ) + return PositionableVec3 + end + + return nil +end + +--- Returns the altitude of the POSITIONABLE. +-- @param Wrapper.Positionable#POSITIONABLE self +-- @return Dcs.DCSTypes#Distance The altitude of the POSITIONABLE. +-- @return #nil The POSITIONABLE is not existing or alive. +function POSITIONABLE:GetAltitude() + self:F2() + + local DCSPositionable = self:GetDCSObject() + + if DCSPositionable then + local PositionablePointVec3 = DCSPositionable:getPoint() --Dcs.DCSTypes#Vec3 + return PositionablePointVec3.y + end + + return nil +end + +--- Returns if the Positionable is located above a runway. +-- @param Wrapper.Positionable#POSITIONABLE self +-- @return #boolean true if Positionable is above a runway. +-- @return #nil The POSITIONABLE is not existing or alive. +function POSITIONABLE:IsAboveRunway() + self:F2( self.PositionableName ) + + local DCSPositionable = self:GetDCSObject() + + if DCSPositionable then + + local Vec2 = self:GetVec2() + local SurfaceType = land.getSurfaceType( Vec2 ) + local IsAboveRunway = SurfaceType == land.SurfaceType.RUNWAY + + self:T2( IsAboveRunway ) + return IsAboveRunway + end + + return nil +end + + + +--- Returns the POSITIONABLE heading in degrees. +-- @param Wrapper.Positionable#POSITIONABLE self +-- @return #number The POSTIONABLE heading +function POSITIONABLE:GetHeading() + local DCSPositionable = self:GetDCSObject() + + if DCSPositionable then + + local PositionablePosition = DCSPositionable:getPosition() + if PositionablePosition then + local PositionableHeading = math.atan2( PositionablePosition.x.z, PositionablePosition.x.x ) + if PositionableHeading < 0 then + PositionableHeading = PositionableHeading + 2 * math.pi + end + PositionableHeading = PositionableHeading * 180 / math.pi + self:T2( PositionableHeading ) + return PositionableHeading + end + end + + return nil +end + + +--- Returns true if the POSITIONABLE is in the air. +-- @param Wrapper.Positionable#POSITIONABLE self +-- @return #boolean true if in the air. +-- @return #nil The POSITIONABLE is not existing or alive. +function POSITIONABLE:InAir() + self:F2( self.PositionableName ) + + local DCSPositionable = self:GetDCSObject() + + if DCSPositionable then + local PositionableInAir = DCSPositionable:inAir() + self:T3( PositionableInAir ) + return PositionableInAir + end + + return nil +end + + +--- Returns the POSITIONABLE velocity vector. +-- @param Wrapper.Positionable#POSITIONABLE self +-- @return Dcs.DCSTypes#Vec3 The velocity vector +-- @return #nil The POSITIONABLE is not existing or alive. +function POSITIONABLE:GetVelocity() + self:F2( self.PositionableName ) + + local DCSPositionable = self:GetDCSObject() + + if DCSPositionable then + local PositionableVelocityVec3 = DCSPositionable:getVelocity() + self:T3( PositionableVelocityVec3 ) + return PositionableVelocityVec3 + end + + return nil +end + +--- Returns the POSITIONABLE velocity in km/h. +-- @param Wrapper.Positionable#POSITIONABLE self +-- @return #number The velocity in km/h +-- @return #nil The POSITIONABLE is not existing or alive. +function POSITIONABLE:GetVelocityKMH() + self:F2( self.PositionableName ) + + local DCSPositionable = self:GetDCSObject() + + if DCSPositionable then + local VelocityVec3 = self:GetVelocity() + local Velocity = ( VelocityVec3.x ^ 2 + VelocityVec3.y ^ 2 + VelocityVec3.z ^ 2 ) ^ 0.5 -- in meters / sec + local Velocity = Velocity * 3.6 -- now it is in km/h. + self:T3( Velocity ) + return Velocity + end + + return nil +end + +--- Returns a message with the callsign embedded (if there is one). +-- @param #POSITIONABLE self +-- @param #string Message The message text +-- @param Dcs.DCSTypes#Duration Duration The duration of the message. +-- @return Core.Message#MESSAGE +function POSITIONABLE:GetMessage( Message, Duration ) + + local DCSObject = self:GetDCSObject() + if DCSObject then + return MESSAGE:New( Message, Duration, self:GetCallsign() .. " (" .. self:GetTypeName() .. ")" ) + end + + return nil +end + +--- Send a message to all coalitions. +-- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. +-- @param #POSITIONABLE self +-- @param #string Message The message text +-- @param Dcs.DCSTypes#Duration Duration The duration of the message. +function POSITIONABLE:MessageToAll( Message, Duration ) + self:F2( { Message, Duration } ) + + local DCSObject = self:GetDCSObject() + if DCSObject then + self:GetMessage( Message, Duration ):ToAll() + end + + return nil +end + +--- Send a message to a coalition. +-- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. +-- @param #POSITIONABLE self +-- @param #string Message The message text +-- @param Dcs.DCSTYpes#Duration Duration The duration of the message. +function POSITIONABLE:MessageToCoalition( Message, Duration, MessageCoalition ) + self:F2( { Message, Duration } ) + + local DCSObject = self:GetDCSObject() + if DCSObject then + self:GetMessage( Message, Duration ):ToCoalition( MessageCoalition ) + end + + return nil +end + + +--- Send a message to the red coalition. +-- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. +-- @param #POSITIONABLE self +-- @param #string Message The message text +-- @param Dcs.DCSTYpes#Duration Duration The duration of the message. +function POSITIONABLE:MessageToRed( Message, Duration ) + self:F2( { Message, Duration } ) + + local DCSObject = self:GetDCSObject() + if DCSObject then + self:GetMessage( Message, Duration ):ToRed() + end + + return nil +end + +--- Send a message to the blue coalition. +-- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. +-- @param #POSITIONABLE self +-- @param #string Message The message text +-- @param Dcs.DCSTypes#Duration Duration The duration of the message. +function POSITIONABLE:MessageToBlue( Message, Duration ) + self:F2( { Message, Duration } ) + + local DCSObject = self:GetDCSObject() + if DCSObject then + self:GetMessage( Message, Duration ):ToBlue() + end + + return nil +end + +--- Send a message to a client. +-- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. +-- @param #POSITIONABLE self +-- @param #string Message The message text +-- @param Dcs.DCSTypes#Duration Duration The duration of the message. +-- @param Wrapper.Client#CLIENT Client The client object receiving the message. +function POSITIONABLE:MessageToClient( Message, Duration, Client ) + self:F2( { Message, Duration } ) + + local DCSObject = self:GetDCSObject() + if DCSObject then + self:GetMessage( Message, Duration ):ToClient( Client ) + end + + return nil +end + +--- Send a message to a @{Group}. +-- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. +-- @param #POSITIONABLE self +-- @param #string Message The message text +-- @param Dcs.DCSTypes#Duration Duration The duration of the message. +-- @param Wrapper.Group#GROUP MessageGroup The GROUP object receiving the message. +function POSITIONABLE:MessageToGroup( Message, Duration, MessageGroup ) + self:F2( { Message, Duration } ) + + local DCSObject = self:GetDCSObject() + if DCSObject then + if DCSObject:isExist() then + self:GetMessage( Message, Duration ):ToGroup( MessageGroup ) + end + end + + return nil +end + +--- Send a message to the players in the @{Group}. +-- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. +-- @param #POSITIONABLE self +-- @param #string Message The message text +-- @param Dcs.DCSTypes#Duration Duration The duration of the message. +function POSITIONABLE:Message( Message, Duration ) + self:F2( { Message, Duration } ) + + local DCSObject = self:GetDCSObject() + if DCSObject then + self:GetMessage( Message, Duration ):ToGroup( self ) + end + + return nil +end + + + + + +--- This module contains the CONTROLLABLE class. +-- +-- 1) @{Wrapper.Controllable#CONTROLLABLE} class, extends @{Wrapper.Positionable#POSITIONABLE} +-- =========================================================== +-- The @{Wrapper.Controllable#CONTROLLABLE} class is a wrapper class to handle the DCS Controllable objects: +-- +-- * Support all DCS Controllable APIs. +-- * Enhance with Controllable specific APIs not in the DCS Controllable API set. +-- * Handle local Controllable Controller. +-- * Manage the "state" of the DCS Controllable. +-- +-- 1.1) CONTROLLABLE constructor +-- ----------------------------- +-- The CONTROLLABLE class provides the following functions to construct a CONTROLLABLE instance: +-- +-- * @{#CONTROLLABLE.New}(): Create a CONTROLLABLE instance. +-- +-- 1.2) CONTROLLABLE task methods +-- ------------------------------ +-- Several controllable task methods are available that help you to prepare tasks. +-- These methods return a string consisting of the task description, which can then be given to either a @{Wrapper.Controllable#CONTROLLABLE.PushTask} or @{Wrapper.Controllable#SetTask} method to assign the task to the CONTROLLABLE. +-- Tasks are specific for the category of the CONTROLLABLE, more specific, for AIR, GROUND or AIR and GROUND. +-- Each task description where applicable indicates for which controllable category the task is valid. +-- There are 2 main subdivisions of tasks: Assigned tasks and EnRoute tasks. +-- +-- ### 1.2.1) Assigned task methods +-- +-- Assigned task methods make the controllable execute the task where the location of the (possible) targets of the task are known before being detected. +-- This is different from the EnRoute tasks, where the targets of the task need to be detected before the task can be executed. +-- +-- Find below a list of the **assigned task** methods: +-- +-- * @{#CONTROLLABLE.TaskAttackControllable}: (AIR) Attack a Controllable. +-- * @{#CONTROLLABLE.TaskAttackMapObject}: (AIR) Attacking the map object (building, structure, e.t.c). +-- * @{#CONTROLLABLE.TaskAttackUnit}: (AIR) Attack the Unit. +-- * @{#CONTROLLABLE.TaskBombing}: (AIR) Delivering weapon at the point on the ground. +-- * @{#CONTROLLABLE.TaskBombingRunway}: (AIR) Delivering weapon on the runway. +-- * @{#CONTROLLABLE.TaskEmbarking}: (AIR) Move the controllable to a Vec2 Point, wait for a defined duration and embark a controllable. +-- * @{#CONTROLLABLE.TaskEmbarkToTransport}: (GROUND) Embark to a Transport landed at a location. +-- * @{#CONTROLLABLE.TaskEscort}: (AIR) Escort another airborne controllable. +-- * @{#CONTROLLABLE.TaskFAC_AttackControllable}: (AIR + GROUND) The task makes the controllable/unit a FAC and orders the FAC to control the target (enemy ground controllable) destruction. +-- * @{#CONTROLLABLE.TaskFireAtPoint}: (GROUND) Fire at a VEC2 point until ammunition is finished. +-- * @{#CONTROLLABLE.TaskFollow}: (AIR) Following another airborne controllable. +-- * @{#CONTROLLABLE.TaskHold}: (GROUND) Hold ground controllable from moving. +-- * @{#CONTROLLABLE.TaskHoldPosition}: (AIR) Hold position at the current position of the first unit of the controllable. +-- * @{#CONTROLLABLE.TaskLand}: (AIR HELICOPTER) Landing at the ground. For helicopters only. +-- * @{#CONTROLLABLE.TaskLandAtZone}: (AIR) Land the controllable at a @{Core.Zone#ZONE_RADIUS). +-- * @{#CONTROLLABLE.TaskOrbitCircle}: (AIR) Orbit at the current position of the first unit of the controllable at a specified alititude. +-- * @{#CONTROLLABLE.TaskOrbitCircleAtVec2}: (AIR) Orbit at a specified position at a specified alititude during a specified duration with a specified speed. +-- * @{#CONTROLLABLE.TaskRefueling}: (AIR) Refueling from the nearest tanker. No parameters. +-- * @{#CONTROLLABLE.TaskRoute}: (AIR + GROUND) Return a Misson task to follow a given route defined by Points. +-- * @{#CONTROLLABLE.TaskRouteToVec2}: (AIR + GROUND) Make the Controllable move to a given point. +-- * @{#CONTROLLABLE.TaskRouteToVec3}: (AIR + GROUND) Make the Controllable move to a given point. +-- * @{#CONTROLLABLE.TaskRouteToZone}: (AIR + GROUND) Route the controllable to a given zone. +-- * @{#CONTROLLABLE.TaskReturnToBase}: (AIR) Route the controllable to an airbase. +-- +-- ### 1.2.2) EnRoute task methods +-- +-- EnRoute tasks require the targets of the task need to be detected by the controllable (using its sensors) before the task can be executed: +-- +-- * @{#CONTROLLABLE.EnRouteTaskAWACS}: (AIR) Aircraft will act as an AWACS for friendly units (will provide them with information about contacts). No parameters. +-- * @{#CONTROLLABLE.EnRouteTaskEngageControllable}: (AIR) Engaging a controllable. The task does not assign the target controllable to the unit/controllable to attack now; it just allows the unit/controllable to engage the target controllable as well as other assigned targets. +-- * @{#CONTROLLABLE.EnRouteTaskEngageTargets}: (AIR) Engaging targets of defined types. +-- * @{#CONTROLLABLE.EnRouteTaskEWR}: (AIR) Attack the Unit. +-- * @{#CONTROLLABLE.EnRouteTaskFAC}: (AIR + GROUND) The task makes the controllable/unit a FAC and lets the FAC to choose a targets (enemy ground controllable) around as well as other assigned targets. +-- * @{#CONTROLLABLE.EnRouteTaskFAC_EngageControllable}: (AIR + GROUND) The task makes the controllable/unit a FAC and lets the FAC to choose the target (enemy ground controllable) as well as other assigned targets. +-- * @{#CONTROLLABLE.EnRouteTaskTanker}: (AIR) Aircraft will act as a tanker for friendly units. No parameters. +-- +-- ### 1.2.3) Preparation task methods +-- +-- There are certain task methods that allow to tailor the task behaviour: +-- +-- * @{#CONTROLLABLE.TaskWrappedAction}: Return a WrappedAction Task taking a Command. +-- * @{#CONTROLLABLE.TaskCombo}: Return a Combo Task taking an array of Tasks. +-- * @{#CONTROLLABLE.TaskCondition}: Return a condition section for a controlled task. +-- * @{#CONTROLLABLE.TaskControlled}: Return a Controlled Task taking a Task and a TaskCondition. +-- +-- ### 1.2.4) Obtain the mission from controllable templates +-- +-- Controllable templates contain complete mission descriptions. Sometimes you want to copy a complete mission from a controllable and assign it to another: +-- +-- * @{#CONTROLLABLE.TaskMission}: (AIR + GROUND) Return a mission task from a mission template. +-- +-- 1.3) CONTROLLABLE Command methods +-- -------------------------- +-- Controllable **command methods** prepare the execution of commands using the @{#CONTROLLABLE.SetCommand} method: +-- +-- * @{#CONTROLLABLE.CommandDoScript}: Do Script command. +-- * @{#CONTROLLABLE.CommandSwitchWayPoint}: Perform a switch waypoint command. +-- +-- 1.4) CONTROLLABLE Option methods +-- ------------------------- +-- Controllable **Option methods** change the behaviour of the Controllable while being alive. +-- +-- ### 1.4.1) Rule of Engagement: +-- +-- * @{#CONTROLLABLE.OptionROEWeaponFree} +-- * @{#CONTROLLABLE.OptionROEOpenFire} +-- * @{#CONTROLLABLE.OptionROEReturnFire} +-- * @{#CONTROLLABLE.OptionROEEvadeFire} +-- +-- To check whether an ROE option is valid for a specific controllable, use: +-- +-- * @{#CONTROLLABLE.OptionROEWeaponFreePossible} +-- * @{#CONTROLLABLE.OptionROEOpenFirePossible} +-- * @{#CONTROLLABLE.OptionROEReturnFirePossible} +-- * @{#CONTROLLABLE.OptionROEEvadeFirePossible} +-- +-- ### 1.4.2) Rule on thread: +-- +-- * @{#CONTROLLABLE.OptionROTNoReaction} +-- * @{#CONTROLLABLE.OptionROTPassiveDefense} +-- * @{#CONTROLLABLE.OptionROTEvadeFire} +-- * @{#CONTROLLABLE.OptionROTVertical} +-- +-- To test whether an ROT option is valid for a specific controllable, use: +-- +-- * @{#CONTROLLABLE.OptionROTNoReactionPossible} +-- * @{#CONTROLLABLE.OptionROTPassiveDefensePossible} +-- * @{#CONTROLLABLE.OptionROTEvadeFirePossible} +-- * @{#CONTROLLABLE.OptionROTVerticalPossible} +-- +-- === +-- +-- @module Controllable +-- @author FlightControl + +--- The CONTROLLABLE class +-- @type CONTROLLABLE +-- @extends Wrapper.Positionable#POSITIONABLE +-- @field Dcs.DCSWrapper.Controllable#Controllable DCSControllable The DCS controllable class. +-- @field #string ControllableName The name of the controllable. +CONTROLLABLE = { + ClassName = "CONTROLLABLE", + ControllableName = "", + WayPointFunctions = {}, +} + +--- Create a new CONTROLLABLE from a DCSControllable +-- @param #CONTROLLABLE self +-- @param Dcs.DCSWrapper.Controllable#Controllable ControllableName The DCS Controllable name +-- @return #CONTROLLABLE self +function CONTROLLABLE:New( ControllableName ) + local self = BASE:Inherit( self, POSITIONABLE:New( ControllableName ) ) + self:F2( ControllableName ) + self.ControllableName = ControllableName + return self +end + +-- DCS Controllable methods support. + +--- Get the controller for the CONTROLLABLE. +-- @param #CONTROLLABLE self +-- @return Dcs.DCSController#Controller +function CONTROLLABLE:_GetController() + self:F2( { self.ControllableName } ) + local DCSControllable = self:GetDCSObject() + + if DCSControllable then + local ControllableController = DCSControllable:getController() + self:T3( ControllableController ) + return ControllableController + end + + return nil +end + + + +-- Tasks + +--- Popping current Task from the controllable. +-- @param #CONTROLLABLE self +-- @return Wrapper.Controllable#CONTROLLABLE self +function CONTROLLABLE:PopCurrentTask() + self:F2() + + local DCSControllable = self:GetDCSObject() + + if DCSControllable then + local Controller = self:_GetController() + Controller:popTask() + return self + end + + return nil +end + +--- Pushing Task on the queue from the controllable. +-- @param #CONTROLLABLE self +-- @return Wrapper.Controllable#CONTROLLABLE self +function CONTROLLABLE:PushTask( DCSTask, WaitTime ) + self:F2() + + local DCSControllable = self:GetDCSObject() + + if DCSControllable then + local Controller = self:_GetController() + + -- When a controllable SPAWNs, it takes about a second to get the controllable in the simulator. Setting tasks to unspawned controllables provides unexpected results. + -- Therefore we schedule the functions to set the mission and options for the Controllable. + -- Controller:pushTask( DCSTask ) + + if WaitTime then + SCHEDULER:New( Controller, Controller.pushTask, { DCSTask }, WaitTime ) + else + Controller:pushTask( DCSTask ) + end + + return self + end + + return nil +end + +--- Clearing the Task Queue and Setting the Task on the queue from the controllable. +-- @param #CONTROLLABLE self +-- @return Wrapper.Controllable#CONTROLLABLE self +function CONTROLLABLE:SetTask( DCSTask, WaitTime ) + self:F2( { DCSTask } ) + + local DCSControllable = self:GetDCSObject() + + if DCSControllable then + + local Controller = self:_GetController() + self:E(Controller) + + -- When a controllable SPAWNs, it takes about a second to get the controllable in the simulator. Setting tasks to unspawned controllables provides unexpected results. + -- Therefore we schedule the functions to set the mission and options for the Controllable. + -- Controller.setTask( Controller, DCSTask ) + + if not WaitTime then + Controller:setTask( DCSTask ) + else + SCHEDULER:New( Controller, Controller.setTask, { DCSTask }, WaitTime ) + end + + return self + end + + return nil +end + + +--- Return a condition section for a controlled task. +-- @param #CONTROLLABLE self +-- @param Dcs.DCSTime#Time time +-- @param #string userFlag +-- @param #boolean userFlagValue +-- @param #string condition +-- @param Dcs.DCSTime#Time duration +-- @param #number lastWayPoint +-- return Dcs.DCSTasking.Task#Task +function CONTROLLABLE:TaskCondition( time, userFlag, userFlagValue, condition, duration, lastWayPoint ) + self:F2( { time, userFlag, userFlagValue, condition, duration, lastWayPoint } ) + + local DCSStopCondition = {} + DCSStopCondition.time = time + DCSStopCondition.userFlag = userFlag + DCSStopCondition.userFlagValue = userFlagValue + DCSStopCondition.condition = condition + DCSStopCondition.duration = duration + DCSStopCondition.lastWayPoint = lastWayPoint + + self:T3( { DCSStopCondition } ) + return DCSStopCondition +end + +--- Return a Controlled Task taking a Task and a TaskCondition. +-- @param #CONTROLLABLE self +-- @param Dcs.DCSTasking.Task#Task DCSTask +-- @param #DCSStopCondition DCSStopCondition +-- @return Dcs.DCSTasking.Task#Task +function CONTROLLABLE:TaskControlled( DCSTask, DCSStopCondition ) + self:F2( { DCSTask, DCSStopCondition } ) + + local DCSTaskControlled + + DCSTaskControlled = { + id = 'ControlledTask', + params = { + task = DCSTask, + stopCondition = DCSStopCondition + } + } + + self:T3( { DCSTaskControlled } ) + return DCSTaskControlled +end + +--- Return a Combo Task taking an array of Tasks. +-- @param #CONTROLLABLE self +-- @param Dcs.DCSTasking.Task#TaskArray DCSTasks Array of @{Dcs.DCSTasking.Task#Task} +-- @return Dcs.DCSTasking.Task#Task +function CONTROLLABLE:TaskCombo( DCSTasks ) + self:F2( { DCSTasks } ) + + local DCSTaskCombo + + DCSTaskCombo = { + id = 'ComboTask', + params = { + tasks = DCSTasks + } + } + + self:T3( { DCSTaskCombo } ) + return DCSTaskCombo +end + +--- Return a WrappedAction Task taking a Command. +-- @param #CONTROLLABLE self +-- @param Dcs.DCSCommand#Command DCSCommand +-- @return Dcs.DCSTasking.Task#Task +function CONTROLLABLE:TaskWrappedAction( DCSCommand, Index ) + self:F2( { DCSCommand } ) + + local DCSTaskWrappedAction + + DCSTaskWrappedAction = { + id = "WrappedAction", + enabled = true, + number = Index, + auto = false, + params = { + action = DCSCommand, + }, + } + + self:T3( { DCSTaskWrappedAction } ) + return DCSTaskWrappedAction +end + +--- Executes a command action +-- @param #CONTROLLABLE self +-- @param Dcs.DCSCommand#Command DCSCommand +-- @return #CONTROLLABLE self +function CONTROLLABLE:SetCommand( DCSCommand ) + self:F2( DCSCommand ) + + local DCSControllable = self:GetDCSObject() + + if DCSControllable then + local Controller = self:_GetController() + Controller:setCommand( DCSCommand ) + return self + end + + return nil +end + +--- Perform a switch waypoint command +-- @param #CONTROLLABLE self +-- @param #number FromWayPoint +-- @param #number ToWayPoint +-- @return Dcs.DCSTasking.Task#Task +-- @usage +-- --- This test demonstrates the use(s) of the SwitchWayPoint method of the GROUP class. +-- HeliGroup = GROUP:FindByName( "Helicopter" ) +-- +-- --- Route the helicopter back to the FARP after 60 seconds. +-- -- We use the SCHEDULER class to do this. +-- SCHEDULER:New( nil, +-- function( HeliGroup ) +-- local CommandRTB = HeliGroup:CommandSwitchWayPoint( 2, 8 ) +-- HeliGroup:SetCommand( CommandRTB ) +-- end, { HeliGroup }, 90 +-- ) +function CONTROLLABLE:CommandSwitchWayPoint( FromWayPoint, ToWayPoint ) + self:F2( { FromWayPoint, ToWayPoint } ) + + local CommandSwitchWayPoint = { + id = 'SwitchWaypoint', + params = { + fromWaypointIndex = FromWayPoint, + goToWaypointIndex = ToWayPoint, + }, + } + + self:T3( { CommandSwitchWayPoint } ) + return CommandSwitchWayPoint +end + +--- Perform stop route command +-- @param #CONTROLLABLE self +-- @param #boolean StopRoute +-- @return Dcs.DCSTasking.Task#Task +function CONTROLLABLE:CommandStopRoute( StopRoute, Index ) + self:F2( { StopRoute, Index } ) + + local CommandStopRoute = { + id = 'StopRoute', + params = { + value = StopRoute, + }, + } + + self:T3( { CommandStopRoute } ) + return CommandStopRoute +end + + +-- TASKS FOR AIR CONTROLLABLES + + +--- (AIR) Attack a Controllable. +-- @param #CONTROLLABLE self +-- @param Wrapper.Controllable#CONTROLLABLE AttackGroup The Controllable to be attacked. +-- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. +-- @param Dcs.DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. +-- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. +-- @param Dcs.DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. +-- @param Dcs.DCSTypes#Distance Altitude (optional) Desired attack start altitude. Controllable/aircraft will make its attacks from the altitude. If the altitude is too low or too high to use weapon aircraft/controllable will choose closest altitude to the desired attack start altitude. If the desired altitude is defined controllable/aircraft will not attack from safe altitude. +-- @param #boolean AttackQtyLimit (optional) The flag determines how to interpret attackQty parameter. If the flag is true then attackQty is a limit on maximal attack quantity for "AttackControllable" and "AttackUnit" tasks. If the flag is false then attackQty is a desired attack quantity for "Bombing" and "BombingRunway" tasks. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. +function CONTROLLABLE:TaskAttackGroup( AttackGroup, WeaponType, WeaponExpend, AttackQty, Direction, Altitude, AttackQtyLimit ) + self:F2( { self.ControllableName, AttackGroup, WeaponType, WeaponExpend, AttackQty, Direction, Altitude, AttackQtyLimit } ) + + -- AttackControllable = { + -- id = 'AttackControllable', + -- params = { + -- groupId = Group.ID, + -- weaponType = number, + -- expend = enum AI.Task.WeaponExpend, + -- attackQty = number, + -- directionEnabled = boolean, + -- direction = Azimuth, + -- altitudeEnabled = boolean, + -- altitude = Distance, + -- attackQtyLimit = boolean, + -- } + -- } + + local DirectionEnabled = nil + if Direction then + DirectionEnabled = true + end + + local AltitudeEnabled = nil + if Altitude then + AltitudeEnabled = true + end + + local DCSTask + DCSTask = { id = 'AttackControllable', + params = { + groupId = AttackGroup:GetID(), + weaponType = WeaponType, + expend = WeaponExpend, + attackQty = AttackQty, + directionEnabled = DirectionEnabled, + direction = Direction, + altitudeEnabled = AltitudeEnabled, + altitude = Altitude, + attackQtyLimit = AttackQtyLimit, + }, + }, + + self:T3( { DCSTask } ) + return DCSTask +end + + +--- (AIR) Attack the Unit. +-- @param #CONTROLLABLE self +-- @param Wrapper.Unit#UNIT AttackUnit The unit. +-- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. +-- @param Dcs.DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. +-- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. +-- @param Dcs.DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. +-- @param #boolean AttackQtyLimit (optional) The flag determines how to interpret attackQty parameter. If the flag is true then attackQty is a limit on maximal attack quantity for "AttackControllable" and "AttackUnit" tasks. If the flag is false then attackQty is a desired attack quantity for "Bombing" and "BombingRunway" tasks. +-- @param #boolean ControllableAttack (optional) Flag indicates that the target must be engaged by all aircrafts of the controllable. Has effect only if the task is assigned to a controllable, not to a single aircraft. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. +function CONTROLLABLE:TaskAttackUnit( AttackUnit, WeaponType, WeaponExpend, AttackQty, Direction, AttackQtyLimit, ControllableAttack ) + self:F2( { self.ControllableName, AttackUnit, WeaponType, WeaponExpend, AttackQty, Direction, AttackQtyLimit, ControllableAttack } ) + + -- AttackUnit = { + -- id = 'AttackUnit', + -- params = { + -- unitId = Unit.ID, + -- weaponType = number, + -- expend = enum AI.Task.WeaponExpend + -- attackQty = number, + -- direction = Azimuth, + -- attackQtyLimit = boolean, + -- controllableAttack = boolean, + -- } + -- } + + local DCSTask + DCSTask = { id = 'AttackUnit', + params = { + unitId = AttackUnit:GetID(), + weaponType = WeaponType, + expend = WeaponExpend, + attackQty = AttackQty, + direction = Direction, + attackQtyLimit = AttackQtyLimit, + controllableAttack = ControllableAttack, + }, + }, + + self:T3( { DCSTask } ) + return DCSTask +end + + +--- (AIR) Delivering weapon at the point on the ground. +-- @param #CONTROLLABLE self +-- @param Dcs.DCSTypes#Vec2 Vec2 2D-coordinates of the point to deliver weapon at. +-- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. +-- @param Dcs.DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. +-- @param #number AttackQty (optional) Desired quantity of passes. The parameter is not the same in AttackControllable and AttackUnit tasks. +-- @param Dcs.DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. +-- @param #boolean ControllableAttack (optional) Flag indicates that the target must be engaged by all aircrafts of the controllable. Has effect only if the task is assigned to a controllable, not to a single aircraft. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. +function CONTROLLABLE:TaskBombing( Vec2, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack ) + self:F2( { self.ControllableName, Vec2, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack } ) + +-- Bombing = { +-- id = 'Bombing', +-- params = { +-- point = Vec2, +-- weaponType = number, +-- expend = enum AI.Task.WeaponExpend, +-- attackQty = number, +-- direction = Azimuth, +-- controllableAttack = boolean, +-- } +-- } + + local DCSTask + DCSTask = { id = 'Bombing', + params = { + point = Vec2, + weaponType = WeaponType, + expend = WeaponExpend, + attackQty = AttackQty, + direction = Direction, + controllableAttack = ControllableAttack, + }, + }, + + self:T3( { DCSTask } ) + return DCSTask +end + +--- (AIR) Orbit at a specified position at a specified alititude during a specified duration with a specified speed. +-- @param #CONTROLLABLE self +-- @param Dcs.DCSTypes#Vec2 Point The point to hold the position. +-- @param #number Altitude The altitude to hold the position. +-- @param #number Speed The speed flying when holding the position. +-- @return #CONTROLLABLE self +function CONTROLLABLE:TaskOrbitCircleAtVec2( Point, Altitude, Speed ) + self:F2( { self.ControllableName, Point, Altitude, Speed } ) + + -- pattern = enum AI.Task.OribtPattern, + -- point = Vec2, + -- point2 = Vec2, + -- speed = Distance, + -- altitude = Distance + + local LandHeight = land.getHeight( Point ) + + self:T3( { LandHeight } ) + + local DCSTask = { id = 'Orbit', + params = { pattern = AI.Task.OrbitPattern.CIRCLE, + point = Point, + speed = Speed, + altitude = Altitude + LandHeight + } + } + + + -- local AITask = { id = 'ControlledTask', + -- params = { task = { id = 'Orbit', + -- params = { pattern = AI.Task.OrbitPattern.CIRCLE, + -- point = Point, + -- speed = Speed, + -- altitude = Altitude + LandHeight + -- } + -- }, + -- stopCondition = { duration = Duration + -- } + -- } + -- } + -- ) + + return DCSTask +end + +--- (AIR) Orbit at the current position of the first unit of the controllable at a specified alititude. +-- @param #CONTROLLABLE self +-- @param #number Altitude The altitude to hold the position. +-- @param #number Speed The speed flying when holding the position. +-- @return #CONTROLLABLE self +function CONTROLLABLE:TaskOrbitCircle( Altitude, Speed ) + self:F2( { self.ControllableName, Altitude, Speed } ) + + local DCSControllable = self:GetDCSObject() + + if DCSControllable then + local ControllablePoint = self:GetVec2() + return self:TaskOrbitCircleAtVec2( ControllablePoint, Altitude, Speed ) + end + + return nil +end + + + +--- (AIR) Hold position at the current position of the first unit of the controllable. +-- @param #CONTROLLABLE self +-- @param #number Duration The maximum duration in seconds to hold the position. +-- @return #CONTROLLABLE self +function CONTROLLABLE:TaskHoldPosition() + self:F2( { self.ControllableName } ) + + return self:TaskOrbitCircle( 30, 10 ) +end + + + + +--- (AIR) Attacking the map object (building, structure, e.t.c). +-- @param #CONTROLLABLE self +-- @param Dcs.DCSTypes#Vec2 Vec2 2D-coordinates of the point the map object is closest to. The distance between the point and the map object must not be greater than 2000 meters. Object id is not used here because Mission Editor doesn't support map object identificators. +-- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. +-- @param Dcs.DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. +-- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. +-- @param Dcs.DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. +-- @param #boolean ControllableAttack (optional) Flag indicates that the target must be engaged by all aircrafts of the controllable. Has effect only if the task is assigned to a controllable, not to a single aircraft. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. +function CONTROLLABLE:TaskAttackMapObject( Vec2, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack ) + self:F2( { self.ControllableName, Vec2, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack } ) + +-- AttackMapObject = { +-- id = 'AttackMapObject', +-- params = { +-- point = Vec2, +-- weaponType = number, +-- expend = enum AI.Task.WeaponExpend, +-- attackQty = number, +-- direction = Azimuth, +-- controllableAttack = boolean, +-- } +-- } + + local DCSTask + DCSTask = { id = 'AttackMapObject', + params = { + point = Vec2, + weaponType = WeaponType, + expend = WeaponExpend, + attackQty = AttackQty, + direction = Direction, + controllableAttack = ControllableAttack, + }, + }, + + self:T3( { DCSTask } ) + return DCSTask +end + + +--- (AIR) Delivering weapon on the runway. +-- @param #CONTROLLABLE self +-- @param Wrapper.Airbase#AIRBASE Airbase Airbase to attack. +-- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. +-- @param Dcs.DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. +-- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. +-- @param Dcs.DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. +-- @param #boolean ControllableAttack (optional) Flag indicates that the target must be engaged by all aircrafts of the controllable. Has effect only if the task is assigned to a controllable, not to a single aircraft. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. +function CONTROLLABLE:TaskBombingRunway( Airbase, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack ) + self:F2( { self.ControllableName, Airbase, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack } ) + +-- BombingRunway = { +-- id = 'BombingRunway', +-- params = { +-- runwayId = AirdromeId, +-- weaponType = number, +-- expend = enum AI.Task.WeaponExpend, +-- attackQty = number, +-- direction = Azimuth, +-- controllableAttack = boolean, +-- } +-- } + + local DCSTask + DCSTask = { id = 'BombingRunway', + params = { + point = Airbase:GetID(), + weaponType = WeaponType, + expend = WeaponExpend, + attackQty = AttackQty, + direction = Direction, + controllableAttack = ControllableAttack, + }, + }, + + self:T3( { DCSTask } ) + return DCSTask +end + + +--- (AIR) Refueling from the nearest tanker. No parameters. +-- @param #CONTROLLABLE self +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. +function CONTROLLABLE:TaskRefueling() + self:F2( { self.ControllableName } ) + +-- Refueling = { +-- id = 'Refueling', +-- params = {} +-- } + + local DCSTask + DCSTask = { id = 'Refueling', + params = { + }, + }, + + self:T3( { DCSTask } ) + return DCSTask +end + + +--- (AIR HELICOPTER) Landing at the ground. For helicopters only. +-- @param #CONTROLLABLE self +-- @param Dcs.DCSTypes#Vec2 Point The point where to land. +-- @param #number Duration The duration in seconds to stay on the ground. +-- @return #CONTROLLABLE self +function CONTROLLABLE:TaskLandAtVec2( Point, Duration ) + self:F2( { self.ControllableName, Point, Duration } ) + +-- Land = { +-- id= 'Land', +-- params = { +-- point = Vec2, +-- durationFlag = boolean, +-- duration = Time +-- } +-- } + + local DCSTask + if Duration and Duration > 0 then + DCSTask = { id = 'Land', + params = { + point = Point, + durationFlag = true, + duration = Duration, + }, + } + else + DCSTask = { id = 'Land', + params = { + point = Point, + durationFlag = false, + }, + } + end + + self:T3( DCSTask ) + return DCSTask +end + +--- (AIR) Land the controllable at a @{Core.Zone#ZONE_RADIUS). +-- @param #CONTROLLABLE self +-- @param Core.Zone#ZONE Zone The zone where to land. +-- @param #number Duration The duration in seconds to stay on the ground. +-- @return #CONTROLLABLE self +function CONTROLLABLE:TaskLandAtZone( Zone, Duration, RandomPoint ) + self:F2( { self.ControllableName, Zone, Duration, RandomPoint } ) + + local Point + if RandomPoint then + Point = Zone:GetRandomVec2() + else + Point = Zone:GetVec2() + end + + local DCSTask = self:TaskLandAtVec2( Point, Duration ) + + self:T3( DCSTask ) + return DCSTask +end + + + +--- (AIR) Following another airborne controllable. +-- The unit / controllable will follow lead unit of another controllable, wingmens of both controllables will continue following their leaders. +-- If another controllable is on land the unit / controllable will orbit around. +-- @param #CONTROLLABLE self +-- @param Wrapper.Controllable#CONTROLLABLE FollowControllable The controllable to be followed. +-- @param Dcs.DCSTypes#Vec3 Vec3 Position of the unit / lead unit of the controllable relative lead unit of another controllable in frame reference oriented by course of lead unit of another controllable. If another controllable is on land the unit / controllable will orbit around. +-- @param #number LastWaypointIndex Detach waypoint of another controllable. Once reached the unit / controllable Follow task is finished. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. +function CONTROLLABLE:TaskFollow( FollowControllable, Vec3, LastWaypointIndex ) + self:F2( { self.ControllableName, FollowControllable, Vec3, LastWaypointIndex } ) + +-- Follow = { +-- id = 'Follow', +-- params = { +-- groupId = Group.ID, +-- pos = Vec3, +-- lastWptIndexFlag = boolean, +-- lastWptIndex = number +-- } +-- } + + local LastWaypointIndexFlag = false + if LastWaypointIndex then + LastWaypointIndexFlag = true + end + + local DCSTask + DCSTask = { + id = 'Follow', + params = { + groupId = FollowControllable:GetID(), + pos = Vec3, + lastWptIndexFlag = LastWaypointIndexFlag, + lastWptIndex = LastWaypointIndex + } + } + + self:T3( { DCSTask } ) + return DCSTask +end + + +--- (AIR) Escort another airborne controllable. +-- The unit / controllable will follow lead unit of another controllable, wingmens of both controllables will continue following their leaders. +-- The unit / controllable will also protect that controllable from threats of specified types. +-- @param #CONTROLLABLE self +-- @param Wrapper.Controllable#CONTROLLABLE EscortControllable The controllable to be escorted. +-- @param Dcs.DCSTypes#Vec3 Vec3 Position of the unit / lead unit of the controllable relative lead unit of another controllable in frame reference oriented by course of lead unit of another controllable. If another controllable is on land the unit / controllable will orbit around. +-- @param #number LastWaypointIndex Detach waypoint of another controllable. Once reached the unit / controllable Follow task is finished. +-- @param #number EngagementDistanceMax Maximal distance from escorted controllable to threat. If the threat is already engaged by escort escort will disengage if the distance becomes greater than 1.5 * engagementDistMax. +-- @param Dcs.DCSTypes#AttributeNameArray TargetTypes Array of AttributeName that is contains threat categories allowed to engage. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. +function CONTROLLABLE:TaskEscort( FollowControllable, Vec3, LastWaypointIndex, EngagementDistance, TargetTypes ) + self:F2( { self.ControllableName, FollowControllable, Vec3, LastWaypointIndex, EngagementDistance, TargetTypes } ) + +-- Escort = { +-- id = 'Escort', +-- params = { +-- groupId = Group.ID, +-- pos = Vec3, +-- lastWptIndexFlag = boolean, +-- lastWptIndex = number, +-- engagementDistMax = Distance, +-- targetTypes = array of AttributeName, +-- } +-- } + + local LastWaypointIndexFlag = false + if LastWaypointIndex then + LastWaypointIndexFlag = true + end + + local DCSTask + DCSTask = { id = 'Escort', + params = { + groupId = FollowControllable:GetID(), + pos = Vec3, + lastWptIndexFlag = LastWaypointIndexFlag, + lastWptIndex = LastWaypointIndex, + engagementDistMax = EngagementDistance, + targetTypes = TargetTypes, + }, + }, + + self:T3( { DCSTask } ) + return DCSTask +end + + +-- GROUND TASKS + +--- (GROUND) Fire at a VEC2 point until ammunition is finished. +-- @param #CONTROLLABLE self +-- @param Dcs.DCSTypes#Vec2 Vec2 The point to fire at. +-- @param Dcs.DCSTypes#Distance Radius The radius of the zone to deploy the fire at. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. +function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius ) + self:F2( { self.ControllableName, Vec2, Radius } ) + + -- FireAtPoint = { + -- id = 'FireAtPoint', + -- params = { + -- point = Vec2, + -- radius = Distance, + -- } + -- } + + local DCSTask + DCSTask = { id = 'FireAtPoint', + params = { + point = Vec2, + radius = Radius, + } + } + + self:T3( { DCSTask } ) + return DCSTask +end + +--- (GROUND) Hold ground controllable from moving. +-- @param #CONTROLLABLE self +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. +function CONTROLLABLE:TaskHold() + self:F2( { self.ControllableName } ) + +-- Hold = { +-- id = 'Hold', +-- params = { +-- } +-- } + + local DCSTask + DCSTask = { id = 'Hold', + params = { + } + } + + self:T3( { DCSTask } ) + return DCSTask +end + + +-- TASKS FOR AIRBORNE AND GROUND UNITS/CONTROLLABLES + +--- (AIR + GROUND) The task makes the controllable/unit a FAC and orders the FAC to control the target (enemy ground controllable) destruction. +-- The killer is player-controlled allied CAS-aircraft that is in contact with the FAC. +-- If the task is assigned to the controllable lead unit will be a FAC. +-- @param #CONTROLLABLE self +-- @param Wrapper.Controllable#CONTROLLABLE AttackGroup Target CONTROLLABLE. +-- @param #number WeaponType Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. +-- @param Dcs.DCSTypes#AI.Task.Designation Designation (optional) Designation type. +-- @param #boolean Datalink (optional) Allows to use datalink to send the target information to attack aircraft. Enabled by default. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. +function CONTROLLABLE:TaskFAC_AttackGroup( AttackGroup, WeaponType, Designation, Datalink ) + self:F2( { self.ControllableName, AttackGroup, WeaponType, Designation, Datalink } ) + +-- FAC_AttackControllable = { +-- id = 'FAC_AttackControllable', +-- params = { +-- groupId = Group.ID, +-- weaponType = number, +-- designation = enum AI.Task.Designation, +-- datalink = boolean +-- } +-- } + + local DCSTask + DCSTask = { id = 'FAC_AttackControllable', + params = { + groupId = AttackGroup:GetID(), + weaponType = WeaponType, + designation = Designation, + datalink = Datalink, + } + } + + self:T3( { DCSTask } ) + return DCSTask +end + +-- EN-ACT_ROUTE TASKS FOR AIRBORNE CONTROLLABLES + +--- (AIR) Engaging targets of defined types. +-- @param #CONTROLLABLE self +-- @param Dcs.DCSTypes#Distance Distance Maximal distance from the target to a route leg. If the target is on a greater distance it will be ignored. +-- @param Dcs.DCSTypes#AttributeNameArray TargetTypes Array of target categories allowed to engage. +-- @param #number Priority All enroute tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. +function CONTROLLABLE:EnRouteTaskEngageTargets( Distance, TargetTypes, Priority ) + self:F2( { self.ControllableName, Distance, TargetTypes, Priority } ) + +-- EngageTargets ={ +-- id = 'EngageTargets', +-- params = { +-- maxDist = Distance, +-- targetTypes = array of AttributeName, +-- priority = number +-- } +-- } + + local DCSTask + DCSTask = { id = 'EngageTargets', + params = { + maxDist = Distance, + targetTypes = TargetTypes, + priority = Priority + } + } + + self:T3( { DCSTask } ) + return DCSTask +end + + + +--- (AIR) Engaging a targets of defined types at circle-shaped zone. +-- @param #CONTROLLABLE self +-- @param Dcs.DCSTypes#Vec2 Vec2 2D-coordinates of the zone. +-- @param Dcs.DCSTypes#Distance Radius Radius of the zone. +-- @param Dcs.DCSTypes#AttributeNameArray TargetTypes Array of target categories allowed to engage. +-- @param #number Priority All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. +function CONTROLLABLE:EnRouteTaskEngageTargets( Vec2, Radius, TargetTypes, Priority ) + self:F2( { self.ControllableName, Vec2, Radius, TargetTypes, Priority } ) + +-- EngageTargetsInZone = { +-- id = 'EngageTargetsInZone', +-- params = { +-- point = Vec2, +-- zoneRadius = Distance, +-- targetTypes = array of AttributeName, +-- priority = number +-- } +-- } + + local DCSTask + DCSTask = { id = 'EngageTargetsInZone', + params = { + point = Vec2, + zoneRadius = Radius, + targetTypes = TargetTypes, + priority = Priority + } + } + + self:T3( { DCSTask } ) + return DCSTask +end + + +--- (AIR) Engaging a controllable. The task does not assign the target controllable to the unit/controllable to attack now; it just allows the unit/controllable to engage the target controllable as well as other assigned targets. +-- @param #CONTROLLABLE self +-- @param Wrapper.Controllable#CONTROLLABLE AttackGroup The Controllable to be attacked. +-- @param #number Priority All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. +-- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. +-- @param Dcs.DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. +-- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. +-- @param Dcs.DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. +-- @param Dcs.DCSTypes#Distance Altitude (optional) Desired attack start altitude. Controllable/aircraft will make its attacks from the altitude. If the altitude is too low or too high to use weapon aircraft/controllable will choose closest altitude to the desired attack start altitude. If the desired altitude is defined controllable/aircraft will not attack from safe altitude. +-- @param #boolean AttackQtyLimit (optional) The flag determines how to interpret attackQty parameter. If the flag is true then attackQty is a limit on maximal attack quantity for "AttackControllable" and "AttackUnit" tasks. If the flag is false then attackQty is a desired attack quantity for "Bombing" and "BombingRunway" tasks. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. +function CONTROLLABLE:EnRouteTaskEngageGroup( AttackGroup, Priority, WeaponType, WeaponExpend, AttackQty, Direction, Altitude, AttackQtyLimit ) + self:F2( { self.ControllableName, AttackGroup, Priority, WeaponType, WeaponExpend, AttackQty, Direction, Altitude, AttackQtyLimit } ) + + -- EngageControllable = { + -- id = 'EngageControllable ', + -- params = { + -- groupId = Group.ID, + -- weaponType = number, + -- expend = enum AI.Task.WeaponExpend, + -- attackQty = number, + -- directionEnabled = boolean, + -- direction = Azimuth, + -- altitudeEnabled = boolean, + -- altitude = Distance, + -- attackQtyLimit = boolean, + -- priority = number, + -- } + -- } + + local DirectionEnabled = nil + if Direction then + DirectionEnabled = true + end + + local AltitudeEnabled = nil + if Altitude then + AltitudeEnabled = true + end + + local DCSTask + DCSTask = { id = 'EngageControllable', + params = { + groupId = AttackGroup:GetID(), + weaponType = WeaponType, + expend = WeaponExpend, + attackQty = AttackQty, + directionEnabled = DirectionEnabled, + direction = Direction, + altitudeEnabled = AltitudeEnabled, + altitude = Altitude, + attackQtyLimit = AttackQtyLimit, + priority = Priority, + }, + }, + + self:T3( { DCSTask } ) + return DCSTask +end + + +--- (AIR) Attack the Unit. +-- @param #CONTROLLABLE self +-- @param Wrapper.Unit#UNIT AttackUnit The UNIT. +-- @param #number Priority All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. +-- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. +-- @param Dcs.DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. +-- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. +-- @param Dcs.DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. +-- @param #boolean AttackQtyLimit (optional) The flag determines how to interpret attackQty parameter. If the flag is true then attackQty is a limit on maximal attack quantity for "AttackControllable" and "AttackUnit" tasks. If the flag is false then attackQty is a desired attack quantity for "Bombing" and "BombingRunway" tasks. +-- @param #boolean ControllableAttack (optional) Flag indicates that the target must be engaged by all aircrafts of the controllable. Has effect only if the task is assigned to a controllable, not to a single aircraft. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. +function CONTROLLABLE:EnRouteTaskEngageUnit( AttackUnit, Priority, WeaponType, WeaponExpend, AttackQty, Direction, AttackQtyLimit, ControllableAttack ) + self:F2( { self.ControllableName, AttackUnit, Priority, WeaponType, WeaponExpend, AttackQty, Direction, AttackQtyLimit, ControllableAttack } ) + + -- EngageUnit = { + -- id = 'EngageUnit', + -- params = { + -- unitId = Unit.ID, + -- weaponType = number, + -- expend = enum AI.Task.WeaponExpend + -- attackQty = number, + -- direction = Azimuth, + -- attackQtyLimit = boolean, + -- controllableAttack = boolean, + -- priority = number, + -- } + -- } + + local DCSTask + DCSTask = { id = 'EngageUnit', + params = { + unitId = AttackUnit:GetID(), + weaponType = WeaponType, + expend = WeaponExpend, + attackQty = AttackQty, + direction = Direction, + attackQtyLimit = AttackQtyLimit, + controllableAttack = ControllableAttack, + priority = Priority, + }, + }, + + self:T3( { DCSTask } ) + return DCSTask +end + + + +--- (AIR) Aircraft will act as an AWACS for friendly units (will provide them with information about contacts). No parameters. +-- @param #CONTROLLABLE self +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. +function CONTROLLABLE:EnRouteTaskAWACS( ) + self:F2( { self.ControllableName } ) + +-- AWACS = { +-- id = 'AWACS', +-- params = { +-- } +-- } + + local DCSTask + DCSTask = { id = 'AWACS', + params = { + } + } + + self:T3( { DCSTask } ) + return DCSTask +end + + +--- (AIR) Aircraft will act as a tanker for friendly units. No parameters. +-- @param #CONTROLLABLE self +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. +function CONTROLLABLE:EnRouteTaskTanker( ) + self:F2( { self.ControllableName } ) + +-- Tanker = { +-- id = 'Tanker', +-- params = { +-- } +-- } + + local DCSTask + DCSTask = { id = 'Tanker', + params = { + } + } + + self:T3( { DCSTask } ) + return DCSTask +end + + +-- En-route tasks for ground units/controllables + +--- (GROUND) Ground unit (EW-radar) will act as an EWR for friendly units (will provide them with information about contacts). No parameters. +-- @param #CONTROLLABLE self +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. +function CONTROLLABLE:EnRouteTaskEWR( ) + self:F2( { self.ControllableName } ) + +-- EWR = { +-- id = 'EWR', +-- params = { +-- } +-- } + + local DCSTask + DCSTask = { id = 'EWR', + params = { + } + } + + self:T3( { DCSTask } ) + return DCSTask +end + + +-- En-route tasks for airborne and ground units/controllables + +--- (AIR + GROUND) The task makes the controllable/unit a FAC and lets the FAC to choose the target (enemy ground controllable) as well as other assigned targets. +-- The killer is player-controlled allied CAS-aircraft that is in contact with the FAC. +-- If the task is assigned to the controllable lead unit will be a FAC. +-- @param #CONTROLLABLE self +-- @param Wrapper.Controllable#CONTROLLABLE AttackGroup Target CONTROLLABLE. +-- @param #number Priority All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. +-- @param #number WeaponType Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. +-- @param Dcs.DCSTypes#AI.Task.Designation Designation (optional) Designation type. +-- @param #boolean Datalink (optional) Allows to use datalink to send the target information to attack aircraft. Enabled by default. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. +function CONTROLLABLE:EnRouteTaskFAC_EngageGroup( AttackGroup, Priority, WeaponType, Designation, Datalink ) + self:F2( { self.ControllableName, AttackGroup, WeaponType, Priority, Designation, Datalink } ) + +-- FAC_EngageControllable = { +-- id = 'FAC_EngageControllable', +-- params = { +-- groupId = Group.ID, +-- weaponType = number, +-- designation = enum AI.Task.Designation, +-- datalink = boolean, +-- priority = number, +-- } +-- } + + local DCSTask + DCSTask = { id = 'FAC_EngageControllable', + params = { + groupId = AttackGroup:GetID(), + weaponType = WeaponType, + designation = Designation, + datalink = Datalink, + priority = Priority, + } + } + + self:T3( { DCSTask } ) + return DCSTask +end + + +--- (AIR + GROUND) The task makes the controllable/unit a FAC and lets the FAC to choose a targets (enemy ground controllable) around as well as other assigned targets. +-- The killer is player-controlled allied CAS-aircraft that is in contact with the FAC. +-- If the task is assigned to the controllable lead unit will be a FAC. +-- @param #CONTROLLABLE self +-- @param Dcs.DCSTypes#Distance Radius The maximal distance from the FAC to a target. +-- @param #number Priority All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. +function CONTROLLABLE:EnRouteTaskFAC( Radius, Priority ) + self:F2( { self.ControllableName, Radius, Priority } ) + +-- FAC = { +-- id = 'FAC', +-- params = { +-- radius = Distance, +-- priority = number +-- } +-- } + + local DCSTask + DCSTask = { id = 'FAC', + params = { + radius = Radius, + priority = Priority + } + } + + self:T3( { DCSTask } ) + return DCSTask +end + + + + +--- (AIR) Move the controllable to a Vec2 Point, wait for a defined duration and embark a controllable. +-- @param #CONTROLLABLE self +-- @param Dcs.DCSTypes#Vec2 Point The point where to wait. +-- @param #number Duration The duration in seconds to wait. +-- @param #CONTROLLABLE EmbarkingControllable The controllable to be embarked. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure +function CONTROLLABLE:TaskEmbarking( Point, Duration, EmbarkingControllable ) + self:F2( { self.ControllableName, Point, Duration, EmbarkingControllable.DCSControllable } ) + + local DCSTask + DCSTask = { id = 'Embarking', + params = { x = Point.x, + y = Point.y, + duration = Duration, + controllablesForEmbarking = { EmbarkingControllable.ControllableID }, + durationFlag = true, + distributionFlag = false, + distribution = {}, + } + } + + self:T3( { DCSTask } ) + return DCSTask +end + +--- (GROUND) Embark to a Transport landed at a location. + +--- Move to a defined Vec2 Point, and embark to a controllable when arrived within a defined Radius. +-- @param #CONTROLLABLE self +-- @param Dcs.DCSTypes#Vec2 Point The point where to wait. +-- @param #number Radius The radius of the embarking zone around the Point. +-- @return Dcs.DCSTasking.Task#Task The DCS task structure. +function CONTROLLABLE:TaskEmbarkToTransport( Point, Radius ) + self:F2( { self.ControllableName, Point, Radius } ) + + local DCSTask --Dcs.DCSTasking.Task#Task + DCSTask = { id = 'EmbarkToTransport', + params = { x = Point.x, + y = Point.y, + zoneRadius = Radius, + } + } + + self:T3( { DCSTask } ) + return DCSTask +end + + + +--- (AIR + GROUND) Return a mission task from a mission template. +-- @param #CONTROLLABLE self +-- @param #table TaskMission A table containing the mission task. +-- @return Dcs.DCSTasking.Task#Task +function CONTROLLABLE:TaskMission( TaskMission ) + self:F2( Points ) + + local DCSTask + DCSTask = { id = 'Mission', params = { TaskMission, }, } + + self:T3( { DCSTask } ) + return DCSTask +end + +--- Return a Misson task to follow a given route defined by Points. +-- @param #CONTROLLABLE self +-- @param #table Points A table of route points. +-- @return Dcs.DCSTasking.Task#Task +function CONTROLLABLE:TaskRoute( Points ) + self:F2( Points ) + + local DCSTask + DCSTask = { id = 'Mission', params = { route = { points = Points, }, }, } + + self:T3( { DCSTask } ) + return DCSTask +end + +--- (AIR + GROUND) Make the Controllable move to fly to a given point. +-- @param #CONTROLLABLE self +-- @param Dcs.DCSTypes#Vec3 Point The destination point in Vec3 format. +-- @param #number Speed The speed to travel. +-- @return #CONTROLLABLE self +function CONTROLLABLE:TaskRouteToVec2( Point, Speed ) + self:F2( { Point, Speed } ) + + local ControllablePoint = self:GetUnit( 1 ):GetVec2() + + local PointFrom = {} + PointFrom.x = ControllablePoint.x + PointFrom.y = ControllablePoint.y + PointFrom.type = "Turning Point" + PointFrom.action = "Turning Point" + PointFrom.speed = Speed + PointFrom.speed_locked = true + PointFrom.properties = { + ["vnav"] = 1, + ["scale"] = 0, + ["angle"] = 0, + ["vangle"] = 0, + ["steer"] = 2, + } + + + local PointTo = {} + PointTo.x = Point.x + PointTo.y = Point.y + PointTo.type = "Turning Point" + PointTo.action = "Fly Over Point" + PointTo.speed = Speed + PointTo.speed_locked = true + PointTo.properties = { + ["vnav"] = 1, + ["scale"] = 0, + ["angle"] = 0, + ["vangle"] = 0, + ["steer"] = 2, + } + + + local Points = { PointFrom, PointTo } + + self:T3( Points ) + + self:Route( Points ) + + return self +end + +--- (AIR + GROUND) Make the Controllable move to a given point. +-- @param #CONTROLLABLE self +-- @param Dcs.DCSTypes#Vec3 Point The destination point in Vec3 format. +-- @param #number Speed The speed to travel. +-- @return #CONTROLLABLE self +function CONTROLLABLE:TaskRouteToVec3( Point, Speed ) + self:F2( { Point, Speed } ) + + local ControllableVec3 = self:GetUnit( 1 ):GetVec3() + + local PointFrom = {} + PointFrom.x = ControllableVec3.x + PointFrom.y = ControllableVec3.z + PointFrom.alt = ControllableVec3.y + PointFrom.alt_type = "BARO" + PointFrom.type = "Turning Point" + PointFrom.action = "Turning Point" + PointFrom.speed = Speed + PointFrom.speed_locked = true + PointFrom.properties = { + ["vnav"] = 1, + ["scale"] = 0, + ["angle"] = 0, + ["vangle"] = 0, + ["steer"] = 2, + } + + + local PointTo = {} + PointTo.x = Point.x + PointTo.y = Point.z + PointTo.alt = Point.y + PointTo.alt_type = "BARO" + PointTo.type = "Turning Point" + PointTo.action = "Fly Over Point" + PointTo.speed = Speed + PointTo.speed_locked = true + PointTo.properties = { + ["vnav"] = 1, + ["scale"] = 0, + ["angle"] = 0, + ["vangle"] = 0, + ["steer"] = 2, + } + + + local Points = { PointFrom, PointTo } + + self:T3( Points ) + + self:Route( Points ) + + return self +end + + + +--- Make the controllable to follow a given route. +-- @param #CONTROLLABLE self +-- @param #table GoPoints A table of Route Points. +-- @return #CONTROLLABLE self +function CONTROLLABLE:Route( GoPoints ) + self:F2( GoPoints ) + + local DCSControllable = self:GetDCSObject() + + if DCSControllable then + local Points = routines.utils.deepCopy( GoPoints ) + local MissionTask = { id = 'Mission', params = { route = { points = Points, }, }, } + local Controller = self:_GetController() + --Controller.setTask( Controller, MissionTask ) + SCHEDULER:New( Controller, Controller.setTask, { MissionTask }, 1 ) + return self + end + + return nil +end + + + +--- (AIR + GROUND) Route the controllable to a given zone. +-- The controllable final destination point can be randomized. +-- A speed can be given in km/h. +-- A given formation can be given. +-- @param #CONTROLLABLE self +-- @param Core.Zone#ZONE Zone The zone where to route to. +-- @param #boolean Randomize Defines whether to target point gets randomized within the Zone. +-- @param #number Speed The speed. +-- @param Base#FORMATION Formation The formation string. +function CONTROLLABLE:TaskRouteToZone( Zone, Randomize, Speed, Formation ) + self:F2( Zone ) + + local DCSControllable = self:GetDCSObject() + + if DCSControllable then + + local ControllablePoint = self:GetVec2() + + local PointFrom = {} + PointFrom.x = ControllablePoint.x + PointFrom.y = ControllablePoint.y + PointFrom.type = "Turning Point" + PointFrom.action = "Cone" + PointFrom.speed = 20 / 1.6 + + + local PointTo = {} + local ZonePoint + + if Randomize then + ZonePoint = Zone:GetRandomVec2() + else + ZonePoint = Zone:GetVec2() + end + + PointTo.x = ZonePoint.x + PointTo.y = ZonePoint.y + PointTo.type = "Turning Point" + + if Formation then + PointTo.action = Formation + else + PointTo.action = "Cone" + end + + if Speed then + PointTo.speed = Speed + else + PointTo.speed = 20 / 1.6 + end + + local Points = { PointFrom, PointTo } + + self:T3( Points ) + + self:Route( Points ) + + return self + end + + return nil +end + +--- (AIR) Return the Controllable to an @{Wrapper.Airbase#AIRBASE} +-- A speed can be given in km/h. +-- A given formation can be given. +-- @param #CONTROLLABLE self +-- @param Wrapper.Airbase#AIRBASE ReturnAirbase The @{Wrapper.Airbase#AIRBASE} to return to. +-- @param #number Speed (optional) The speed. +-- @return #string The route +function CONTROLLABLE:RouteReturnToAirbase( ReturnAirbase, Speed ) + self:F2( { ReturnAirbase, Speed } ) + +-- Example +-- [4] = +-- { +-- ["alt"] = 45, +-- ["type"] = "Land", +-- ["action"] = "Landing", +-- ["alt_type"] = "BARO", +-- ["formation_template"] = "", +-- ["properties"] = +-- { +-- ["vnav"] = 1, +-- ["scale"] = 0, +-- ["angle"] = 0, +-- ["vangle"] = 0, +-- ["steer"] = 2, +-- }, -- end of ["properties"] +-- ["ETA"] = 527.81058817743, +-- ["airdromeId"] = 12, +-- ["y"] = 243127.2973737, +-- ["x"] = -5406.2803440839, +-- ["name"] = "DictKey_WptName_53", +-- ["speed"] = 138.88888888889, +-- ["ETA_locked"] = false, +-- ["task"] = +-- { +-- ["id"] = "ComboTask", +-- ["params"] = +-- { +-- ["tasks"] = +-- { +-- }, -- end of ["tasks"] +-- }, -- end of ["params"] +-- }, -- end of ["task"] +-- ["speed_locked"] = true, +-- }, -- end of [4] + + + local DCSControllable = self:GetDCSObject() + + if DCSControllable then + + local ControllablePoint = self:GetVec2() + local ControllableVelocity = self:GetMaxVelocity() + + local PointFrom = {} + PointFrom.x = ControllablePoint.x + PointFrom.y = ControllablePoint.y + PointFrom.type = "Turning Point" + PointFrom.action = "Turning Point" + PointFrom.speed = ControllableVelocity + + + local PointTo = {} + local AirbasePoint = ReturnAirbase:GetVec2() + + PointTo.x = AirbasePoint.x + PointTo.y = AirbasePoint.y + PointTo.type = "Land" + PointTo.action = "Landing" + PointTo.airdromeId = ReturnAirbase:GetID()-- Airdrome ID + self:T(PointTo.airdromeId) + --PointTo.alt = 0 + + local Points = { PointFrom, PointTo } + + self:T3( Points ) + + local Route = { points = Points, } + + return Route + end + + return nil +end + +-- Commands + +--- Do Script command +-- @param #CONTROLLABLE self +-- @param #string DoScript +-- @return #DCSCommand +function CONTROLLABLE:CommandDoScript( DoScript ) + + local DCSDoScript = { + id = "Script", + params = { + command = DoScript, + }, + } + + self:T3( DCSDoScript ) + return DCSDoScript +end + + +--- Return the mission template of the controllable. +-- @param #CONTROLLABLE self +-- @return #table The MissionTemplate +-- TODO: Rework the method how to retrieve a template ... +function CONTROLLABLE:GetTaskMission() + self:F2( self.ControllableName ) + + return routines.utils.deepCopy( _DATABASE.Templates.Controllables[self.ControllableName].Template ) +end + +--- Return the mission route of the controllable. +-- @param #CONTROLLABLE self +-- @return #table The mission route defined by points. +function CONTROLLABLE:GetTaskRoute() + self:F2( self.ControllableName ) + + return routines.utils.deepCopy( _DATABASE.Templates.Controllables[self.ControllableName].Template.route.points ) +end + +--- Return the route of a controllable by using the @{Core.Database#DATABASE} class. +-- @param #CONTROLLABLE self +-- @param #number Begin The route point from where the copy will start. The base route point is 0. +-- @param #number End The route point where the copy will end. The End point is the last point - the End point. The last point has base 0. +-- @param #boolean Randomize Randomization of the route, when true. +-- @param #number Radius When randomization is on, the randomization is within the radius. +function CONTROLLABLE:CopyRoute( Begin, End, Randomize, Radius ) + self:F2( { Begin, End } ) + + local Points = {} + + -- Could be a Spawned Controllable + local ControllableName = string.match( self:GetName(), ".*#" ) + if ControllableName then + ControllableName = ControllableName:sub( 1, -2 ) + else + ControllableName = self:GetName() + end + + self:T3( { ControllableName } ) + + local Template = _DATABASE.Templates.Controllables[ControllableName].Template + + if Template then + if not Begin then + Begin = 0 + end + if not End then + End = 0 + end + + for TPointID = Begin + 1, #Template.route.points - End do + if Template.route.points[TPointID] then + Points[#Points+1] = routines.utils.deepCopy( Template.route.points[TPointID] ) + if Randomize then + if not Radius then + Radius = 500 + end + Points[#Points].x = Points[#Points].x + math.random( Radius * -1, Radius ) + Points[#Points].y = Points[#Points].y + math.random( Radius * -1, Radius ) + end + end + end + return Points + else + error( "Template not found for Controllable : " .. ControllableName ) + end + + return nil +end + + +--- Return the detected targets of the controllable. +-- The optional parametes specify the detection methods that can be applied. +-- If no detection method is given, the detection will use all the available methods by default. +-- @param Wrapper.Controllable#CONTROLLABLE self +-- @param #boolean DetectVisual (optional) +-- @param #boolean DetectOptical (optional) +-- @param #boolean DetectRadar (optional) +-- @param #boolean DetectIRST (optional) +-- @param #boolean DetectRWR (optional) +-- @param #boolean DetectDLINK (optional) +-- @return #table DetectedTargets +function CONTROLLABLE:GetDetectedTargets( DetectVisual, DetectOptical, DetectRadar, DetectIRST, DetectRWR, DetectDLINK ) + self:F2( self.ControllableName ) + + local DCSControllable = self:GetDCSObject() + if DCSControllable then + local DetectionVisual = ( DetectVisual and DetectVisual == true ) and Controller.Detection.VISUAL or nil + local DetectionOptical = ( DetectOptical and DetectOptical == true ) and Controller.Detection.OPTICAL or nil + local DetectionRadar = ( DetectRadar and DetectRadar == true ) and Controller.Detection.RADAR or nil + local DetectionIRST = ( DetectIRST and DetectIRST == true ) and Controller.Detection.IRST or nil + local DetectionRWR = ( DetectRWR and DetectRWR == true ) and Controller.Detection.RWR or nil + local DetectionDLINK = ( DetectDLINK and DetectDLINK == true ) and Controller.Detection.DLINK or nil + + + return self:_GetController():getDetectedTargets( DetectionVisual, DetectionOptical, DetectionRadar, DetectionIRST, DetectionRWR, DetectionDLINK ) + end + + return nil +end + +function CONTROLLABLE:IsTargetDetected( DCSObject ) + self:F2( self.ControllableName ) + + local DCSControllable = self:GetDCSObject() + if DCSControllable then + + local TargetIsDetected, TargetIsVisible, TargetLastTime, TargetKnowType, TargetKnowDistance, TargetLastPos, TargetLastVelocity + = self:_GetController().isTargetDetected( self:_GetController(), DCSObject, + Controller.Detection.VISUAL, + Controller.Detection.OPTIC, + Controller.Detection.RADAR, + Controller.Detection.IRST, + Controller.Detection.RWR, + Controller.Detection.DLINK + ) + return TargetIsDetected, TargetIsVisible, TargetLastTime, TargetKnowType, TargetKnowDistance, TargetLastPos, TargetLastVelocity + end + + return nil +end + +-- Options + +--- Can the CONTROLLABLE hold their weapons? +-- @param #CONTROLLABLE self +-- @return #boolean +function CONTROLLABLE:OptionROEHoldFirePossible() + self:F2( { self.ControllableName } ) + + local DCSControllable = self:GetDCSObject() + if DCSControllable then + if self:IsAir() or self:IsGround() or self:IsShip() then + return true + end + + return false + end + + return nil +end + +--- Holding weapons. +-- @param Wrapper.Controllable#CONTROLLABLE self +-- @return Wrapper.Controllable#CONTROLLABLE self +function CONTROLLABLE:OptionROEHoldFire() + self:F2( { self.ControllableName } ) + + local DCSControllable = self:GetDCSObject() + if DCSControllable then + local Controller = self:_GetController() + + if self:IsAir() then + Controller:setOption( AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.WEAPON_HOLD ) + elseif self:IsGround() then + Controller:setOption( AI.Option.Ground.id.ROE, AI.Option.Ground.val.ROE.WEAPON_HOLD ) + elseif self:IsShip() then + Controller:setOption( AI.Option.Naval.id.ROE, AI.Option.Naval.val.ROE.WEAPON_HOLD ) + end + + return self + end + + return nil +end + +--- Can the CONTROLLABLE attack returning on enemy fire? +-- @param #CONTROLLABLE self +-- @return #boolean +function CONTROLLABLE:OptionROEReturnFirePossible() + self:F2( { self.ControllableName } ) + + local DCSControllable = self:GetDCSObject() + if DCSControllable then + if self:IsAir() or self:IsGround() or self:IsShip() then + return true + end + + return false + end + + return nil +end + +--- Return fire. +-- @param #CONTROLLABLE self +-- @return #CONTROLLABLE self +function CONTROLLABLE:OptionROEReturnFire() + self:F2( { self.ControllableName } ) + + local DCSControllable = self:GetDCSObject() + if DCSControllable then + local Controller = self:_GetController() + + if self:IsAir() then + Controller:setOption( AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.RETURN_FIRE ) + elseif self:IsGround() then + Controller:setOption( AI.Option.Ground.id.ROE, AI.Option.Ground.val.ROE.RETURN_FIRE ) + elseif self:IsShip() then + Controller:setOption( AI.Option.Naval.id.ROE, AI.Option.Naval.val.ROE.RETURN_FIRE ) + end + + return self + end + + return nil +end + +--- Can the CONTROLLABLE attack designated targets? +-- @param #CONTROLLABLE self +-- @return #boolean +function CONTROLLABLE:OptionROEOpenFirePossible() + self:F2( { self.ControllableName } ) + + local DCSControllable = self:GetDCSObject() + if DCSControllable then + if self:IsAir() or self:IsGround() or self:IsShip() then + return true + end + + return false + end + + return nil +end + +--- Openfire. +-- @param #CONTROLLABLE self +-- @return #CONTROLLABLE self +function CONTROLLABLE:OptionROEOpenFire() + self:F2( { self.ControllableName } ) + + local DCSControllable = self:GetDCSObject() + if DCSControllable then + local Controller = self:_GetController() + + if self:IsAir() then + Controller:setOption( AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.OPEN_FIRE ) + elseif self:IsGround() then + Controller:setOption( AI.Option.Ground.id.ROE, AI.Option.Ground.val.ROE.OPEN_FIRE ) + elseif self:IsShip() then + Controller:setOption( AI.Option.Naval.id.ROE, AI.Option.Naval.val.ROE.OPEN_FIRE ) + end + + return self + end + + return nil +end + +--- Can the CONTROLLABLE attack targets of opportunity? +-- @param #CONTROLLABLE self +-- @return #boolean +function CONTROLLABLE:OptionROEWeaponFreePossible() + self:F2( { self.ControllableName } ) + + local DCSControllable = self:GetDCSObject() + if DCSControllable then + if self:IsAir() then + return true + end + + return false + end + + return nil +end + +--- Weapon free. +-- @param #CONTROLLABLE self +-- @return #CONTROLLABLE self +function CONTROLLABLE:OptionROEWeaponFree() + self:F2( { self.ControllableName } ) + + local DCSControllable = self:GetDCSObject() + if DCSControllable then + local Controller = self:_GetController() + + if self:IsAir() then + Controller:setOption( AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.WEAPON_FREE ) + end + + return self + end + + return nil +end + +--- Can the CONTROLLABLE ignore enemy fire? +-- @param #CONTROLLABLE self +-- @return #boolean +function CONTROLLABLE:OptionROTNoReactionPossible() + self:F2( { self.ControllableName } ) + + local DCSControllable = self:GetDCSObject() + if DCSControllable then + if self:IsAir() then + return true + end + + return false + end + + return nil +end + + +--- No evasion on enemy threats. +-- @param #CONTROLLABLE self +-- @return #CONTROLLABLE self +function CONTROLLABLE:OptionROTNoReaction() + self:F2( { self.ControllableName } ) + + local DCSControllable = self:GetDCSObject() + if DCSControllable then + local Controller = self:_GetController() + + if self:IsAir() then + Controller:setOption( AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.NO_REACTION ) + end + + return self + end + + return nil +end + +--- Can the CONTROLLABLE evade using passive defenses? +-- @param #CONTROLLABLE self +-- @return #boolean +function CONTROLLABLE:OptionROTPassiveDefensePossible() + self:F2( { self.ControllableName } ) + + local DCSControllable = self:GetDCSObject() + if DCSControllable then + if self:IsAir() then + return true + end + + return false + end + + return nil +end + +--- Evasion passive defense. +-- @param #CONTROLLABLE self +-- @return #CONTROLLABLE self +function CONTROLLABLE:OptionROTPassiveDefense() + self:F2( { self.ControllableName } ) + + local DCSControllable = self:GetDCSObject() + if DCSControllable then + local Controller = self:_GetController() + + if self:IsAir() then + Controller:setOption( AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.PASSIVE_DEFENCE ) + end + + return self + end + + return nil +end + +--- Can the CONTROLLABLE evade on enemy fire? +-- @param #CONTROLLABLE self +-- @return #boolean +function CONTROLLABLE:OptionROTEvadeFirePossible() + self:F2( { self.ControllableName } ) + + local DCSControllable = self:GetDCSObject() + if DCSControllable then + if self:IsAir() then + return true + end + + return false + end + + return nil +end + + +--- Evade on fire. +-- @param #CONTROLLABLE self +-- @return #CONTROLLABLE self +function CONTROLLABLE:OptionROTEvadeFire() + self:F2( { self.ControllableName } ) + + local DCSControllable = self:GetDCSObject() + if DCSControllable then + local Controller = self:_GetController() + + if self:IsAir() then + Controller:setOption( AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.EVADE_FIRE ) + end + + return self + end + + return nil +end + +--- Can the CONTROLLABLE evade on fire using vertical manoeuvres? +-- @param #CONTROLLABLE self +-- @return #boolean +function CONTROLLABLE:OptionROTVerticalPossible() + self:F2( { self.ControllableName } ) + + local DCSControllable = self:GetDCSObject() + if DCSControllable then + if self:IsAir() then + return true + end + + return false + end + + return nil +end + + +--- Evade on fire using vertical manoeuvres. +-- @param #CONTROLLABLE self +-- @return #CONTROLLABLE self +function CONTROLLABLE:OptionROTVertical() + self:F2( { self.ControllableName } ) + + local DCSControllable = self:GetDCSObject() + if DCSControllable then + local Controller = self:_GetController() + + if self:IsAir() then + Controller:setOption( AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.BYPASS_AND_ESCAPE ) + end + + return self + end + + return nil +end + +--- Retrieve the controllable mission and allow to place function hooks within the mission waypoint plan. +-- Use the method @{Wrapper.Controllable#CONTROLLABLE:WayPointFunction} to define the hook functions for specific waypoints. +-- Use the method @{Controllable@CONTROLLABLE:WayPointExecute) to start the execution of the new mission plan. +-- Note that when WayPointInitialize is called, the Mission of the controllable is RESTARTED! +-- @param #CONTROLLABLE self +-- @param #table WayPoints If WayPoints is given, then use the route. +-- @return #CONTROLLABLE +function CONTROLLABLE:WayPointInitialize( WayPoints ) + self:F( { WayPoint, WayPointIndex, WayPointFunction } ) + + if WayPoints then + self.WayPoints = WayPoints + else + self.WayPoints = self:GetTaskRoute() + end + + return self +end + + +--- Registers a waypoint function that will be executed when the controllable moves over the WayPoint. +-- @param #CONTROLLABLE self +-- @param #number WayPoint The waypoint number. Note that the start waypoint on the route is WayPoint 1! +-- @param #number WayPointIndex When defining multiple WayPoint functions for one WayPoint, use WayPointIndex to set the sequence of actions. +-- @param #function WayPointFunction The waypoint function to be called when the controllable moves over the waypoint. The waypoint function takes variable parameters. +-- @return #CONTROLLABLE +function CONTROLLABLE:WayPointFunction( WayPoint, WayPointIndex, WayPointFunction, ... ) + self:F2( { WayPoint, WayPointIndex, WayPointFunction } ) + + table.insert( self.WayPoints[WayPoint].task.params.tasks, WayPointIndex ) + self.WayPoints[WayPoint].task.params.tasks[WayPointIndex] = self:TaskFunction( WayPoint, WayPointIndex, WayPointFunction, arg ) + return self +end + + +function CONTROLLABLE:TaskFunction( WayPoint, WayPointIndex, FunctionString, FunctionArguments ) + self:F2( { WayPoint, WayPointIndex, FunctionString, FunctionArguments } ) + + local DCSTask + + local DCSScript = {} + DCSScript[#DCSScript+1] = "local MissionControllable = GROUP:Find( ... ) " + + if FunctionArguments and #FunctionArguments > 0 then + DCSScript[#DCSScript+1] = FunctionString .. "( MissionControllable, " .. table.concat( FunctionArguments, "," ) .. ")" + else + DCSScript[#DCSScript+1] = FunctionString .. "( MissionControllable )" + end + + DCSTask = self:TaskWrappedAction( + self:CommandDoScript( + table.concat( DCSScript ) + ), WayPointIndex + ) + + self:T3( DCSTask ) + + return DCSTask + +end + +--- Executes the WayPoint plan. +-- The function gets a WayPoint parameter, that you can use to restart the mission at a specific WayPoint. +-- Note that when the WayPoint parameter is used, the new start mission waypoint of the controllable will be 1! +-- @param #CONTROLLABLE self +-- @param #number WayPoint The WayPoint from where to execute the mission. +-- @param #number WaitTime The amount seconds to wait before initiating the mission. +-- @return #CONTROLLABLE +function CONTROLLABLE:WayPointExecute( WayPoint, WaitTime ) + self:F( { WayPoint, WaitTime } ) + + if not WayPoint then + WayPoint = 1 + end + + -- When starting the mission from a certain point, the TaskPoints need to be deleted before the given WayPoint. + for TaskPointID = 1, WayPoint - 1 do + table.remove( self.WayPoints, 1 ) + end + + self:T3( self.WayPoints ) + + self:SetTask( self:TaskRoute( self.WayPoints ), WaitTime ) + + return self +end + +-- Message APIs + + +--- This module contains the GROUP class. +-- +-- 1) @{Wrapper.Group#GROUP} class, extends @{Wrapper.Controllable#CONTROLLABLE} +-- ============================================================= +-- The @{Wrapper.Group#GROUP} class is a wrapper class to handle the DCS Group objects: +-- +-- * Support all DCS Group APIs. +-- * Enhance with Group specific APIs not in the DCS Group API set. +-- * Handle local Group Controller. +-- * Manage the "state" of the DCS Group. +-- +-- **IMPORTANT: ONE SHOULD NEVER SANATIZE these GROUP OBJECT REFERENCES! (make the GROUP object references nil).** +-- +-- 1.1) GROUP reference methods +-- ----------------------- +-- For each DCS Group object alive within a running mission, a GROUP wrapper object (instance) will be created within the _@{DATABASE} object. +-- This is done at the beginning of the mission (when the mission starts), and dynamically when new DCS Group objects are spawned (using the @{SPAWN} class). +-- +-- The GROUP class does not contain a :New() method, rather it provides :Find() methods to retrieve the object reference +-- using the DCS Group or the DCS GroupName. +-- +-- Another thing to know is that GROUP objects do not "contain" the DCS Group object. +-- The GROUP methods will reference the DCS Group object by name when it is needed during API execution. +-- If the DCS Group object does not exist or is nil, the GROUP methods will return nil and log an exception in the DCS.log file. +-- +-- The GROUP class provides the following functions to retrieve quickly the relevant GROUP instance: +-- +-- * @{#GROUP.Find}(): Find a GROUP instance from the _DATABASE object using a DCS Group object. +-- * @{#GROUP.FindByName}(): Find a GROUP instance from the _DATABASE object using a DCS Group name. +-- +-- 1.2) GROUP task methods +-- ----------------------- +-- Several group task methods are available that help you to prepare tasks. +-- These methods return a string consisting of the task description, which can then be given to either a +-- @{Wrapper.Controllable#CONTROLLABLE.PushTask} or @{Wrapper.Controllable#CONTROLLABLE.SetTask} method to assign the task to the GROUP. +-- Tasks are specific for the category of the GROUP, more specific, for AIR, GROUND or AIR and GROUND. +-- Each task description where applicable indicates for which group category the task is valid. +-- There are 2 main subdivisions of tasks: Assigned tasks and EnRoute tasks. +-- +-- ### 1.2.1) Assigned task methods +-- +-- Assigned task methods make the group execute the task where the location of the (possible) targets of the task are known before being detected. +-- This is different from the EnRoute tasks, where the targets of the task need to be detected before the task can be executed. +-- +-- Find below a list of the **assigned task** methods: +-- +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskAttackGroup}: (AIR) Attack a Group. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskAttackMapObject}: (AIR) Attacking the map object (building, structure, e.t.c). +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskAttackUnit}: (AIR) Attack the Unit. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskBombing}: (Wrapper.Controllable#CONTROLLABLEDelivering weapon at the point on the ground. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskBombingRunway}: (AIR) Delivering weapon on the runway. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskEmbarking}: (AIR) Move the group to a Vec2 Point, wait for a defined duration and embark a group. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskEmbarkToTransport}: (GROUND) Embark to a Transport landed at a location. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskEscort}: (AIR) Escort another airborne group. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskFAC_AttackGroup}: (AIR + GROUND) The task makes the group/unit a FAC and orders the FAC to control the target (enemy ground group) destruction. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskFireAtPoint}: (GROUND) Fire at a VEC2 point until ammunition is finished. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskFollow}: (AIR) Following another airborne group. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskHold}: (GROUND) Hold ground group from moving. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskHoldPosition}: (AIR) Hold position at the current position of the first unit of the group. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskLand}: (AIR HELICOPTER) Landing at the ground. For helicopters only. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskLandAtZone}: (AIR) Land the group at a @{Core.Zone#ZONE_RADIUS). +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskOrbitCircle}: (AIR) Orbit at the current position of the first unit of the group at a specified alititude. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskOrbitCircleAtVec2}: (AIR) Orbit at a specified position at a specified alititude during a specified duration with a specified speed. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskRefueling}: (AIR) Refueling from the nearest tanker. No parameters. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskRoute}: (AIR + GROUND) Return a Misson task to follow a given route defined by Points. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskRouteToVec2}: (AIR + GROUND) Make the Group move to a given point. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskRouteToVec3}: (AIR + GROUND) Make the Group move to a given point. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskRouteToZone}: (AIR + GROUND) Route the group to a given zone. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskReturnToBase}: (AIR) Route the group to an airbase. +-- +-- ### 1.2.2) EnRoute task methods +-- +-- EnRoute tasks require the targets of the task need to be detected by the group (using its sensors) before the task can be executed: +-- +-- * @{Wrapper.Controllable#CONTROLLABLE.EnRouteTaskAWACS}: (AIR) Aircraft will act as an AWACS for friendly units (will provide them with information about contacts). No parameters. +-- * @{Wrapper.Controllable#CONTROLLABLE.EnRouteTaskEngageGroup}: (AIR) Engaging a group. The task does not assign the target group to the unit/group to attack now; it just allows the unit/group to engage the target group as well as other assigned targets. +-- * @{Wrapper.Controllable#CONTROLLABLE.EnRouteTaskEngageTargets}: (AIR) Engaging targets of defined types. +-- * @{Wrapper.Controllable#CONTROLLABLE.EnRouteTaskEWR}: (AIR) Attack the Unit. +-- * @{Wrapper.Controllable#CONTROLLABLE.EnRouteTaskFAC}: (AIR + GROUND) The task makes the group/unit a FAC and lets the FAC to choose a targets (enemy ground group) around as well as other assigned targets. +-- * @{Wrapper.Controllable#CONTROLLABLE.EnRouteTaskFAC_EngageGroup}: (AIR + GROUND) The task makes the group/unit a FAC and lets the FAC to choose the target (enemy ground group) as well as other assigned targets. +-- * @{Wrapper.Controllable#CONTROLLABLE.EnRouteTaskTanker}: (AIR) Aircraft will act as a tanker for friendly units. No parameters. +-- +-- ### 1.2.3) Preparation task methods +-- +-- There are certain task methods that allow to tailor the task behaviour: +-- +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskWrappedAction}: Return a WrappedAction Task taking a Command. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskCombo}: Return a Combo Task taking an array of Tasks. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskCondition}: Return a condition section for a controlled task. +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskControlled}: Return a Controlled Task taking a Task and a TaskCondition. +-- +-- ### 1.2.4) Obtain the mission from group templates +-- +-- Group templates contain complete mission descriptions. Sometimes you want to copy a complete mission from a group and assign it to another: +-- +-- * @{Wrapper.Controllable#CONTROLLABLE.TaskMission}: (AIR + GROUND) Return a mission task from a mission template. +-- +-- 1.3) GROUP Command methods +-- -------------------------- +-- Group **command methods** prepare the execution of commands using the @{Wrapper.Controllable#CONTROLLABLE.SetCommand} method: +-- +-- * @{Wrapper.Controllable#CONTROLLABLE.CommandDoScript}: Do Script command. +-- * @{Wrapper.Controllable#CONTROLLABLE.CommandSwitchWayPoint}: Perform a switch waypoint command. +-- +-- 1.4) GROUP Option methods +-- ------------------------- +-- Group **Option methods** change the behaviour of the Group while being alive. +-- +-- ### 1.4.1) Rule of Engagement: +-- +-- * @{Wrapper.Controllable#CONTROLLABLE.OptionROEWeaponFree} +-- * @{Wrapper.Controllable#CONTROLLABLE.OptionROEOpenFire} +-- * @{Wrapper.Controllable#CONTROLLABLE.OptionROEReturnFire} +-- * @{Wrapper.Controllable#CONTROLLABLE.OptionROEEvadeFire} +-- +-- To check whether an ROE option is valid for a specific group, use: +-- +-- * @{Wrapper.Controllable#CONTROLLABLE.OptionROEWeaponFreePossible} +-- * @{Wrapper.Controllable#CONTROLLABLE.OptionROEOpenFirePossible} +-- * @{Wrapper.Controllable#CONTROLLABLE.OptionROEReturnFirePossible} +-- * @{Wrapper.Controllable#CONTROLLABLE.OptionROEEvadeFirePossible} +-- +-- ### 1.4.2) Rule on thread: +-- +-- * @{Wrapper.Controllable#CONTROLLABLE.OptionROTNoReaction} +-- * @{Wrapper.Controllable#CONTROLLABLE.OptionROTPassiveDefense} +-- * @{Wrapper.Controllable#CONTROLLABLE.OptionROTEvadeFire} +-- * @{Wrapper.Controllable#CONTROLLABLE.OptionROTVertical} +-- +-- To test whether an ROT option is valid for a specific group, use: +-- +-- * @{Wrapper.Controllable#CONTROLLABLE.OptionROTNoReactionPossible} +-- * @{Wrapper.Controllable#CONTROLLABLE.OptionROTPassiveDefensePossible} +-- * @{Wrapper.Controllable#CONTROLLABLE.OptionROTEvadeFirePossible} +-- * @{Wrapper.Controllable#CONTROLLABLE.OptionROTVerticalPossible} +-- +-- 1.5) GROUP Zone validation methods +-- ---------------------------------- +-- The group can be validated whether it is completely, partly or not within a @{Zone}. +-- Use the following Zone validation methods on the group: +-- +-- * @{#GROUP.IsCompletelyInZone}: Returns true if all units of the group are within a @{Zone}. +-- * @{#GROUP.IsPartlyInZone}: Returns true if some units of the group are within a @{Zone}. +-- * @{#GROUP.IsNotInZone}: Returns true if none of the group units of the group are within a @{Zone}. +-- +-- The zone can be of any @{Zone} class derived from @{Core.Zone#ZONE_BASE}. So, these methods are polymorphic to the zones tested on. +-- +-- @module Group +-- @author FlightControl + +--- The GROUP class +-- @type GROUP +-- @extends Wrapper.Controllable#CONTROLLABLE +-- @field #string GroupName The name of the group. +GROUP = { + ClassName = "GROUP", +} + +--- Create a new GROUP from a DCSGroup +-- @param #GROUP self +-- @param Dcs.DCSWrapper.Group#Group GroupName The DCS Group name +-- @return #GROUP self +function GROUP:Register( GroupName ) + local self = BASE:Inherit( self, CONTROLLABLE:New( GroupName ) ) + self:F2( GroupName ) + self.GroupName = GroupName + return self +end + +-- Reference methods. + +--- Find the GROUP wrapper class instance using the DCS Group. +-- @param #GROUP self +-- @param Dcs.DCSWrapper.Group#Group DCSGroup The DCS Group. +-- @return #GROUP The GROUP. +function GROUP:Find( DCSGroup ) + + local GroupName = DCSGroup:getName() -- Wrapper.Group#GROUP + local GroupFound = _DATABASE:FindGroup( GroupName ) + return GroupFound +end + +--- Find the created GROUP using the DCS Group Name. +-- @param #GROUP self +-- @param #string GroupName The DCS Group Name. +-- @return #GROUP The GROUP. +function GROUP:FindByName( GroupName ) + + local GroupFound = _DATABASE:FindGroup( GroupName ) + return GroupFound +end + +-- DCS Group methods support. + +--- Returns the DCS Group. +-- @param #GROUP self +-- @return Dcs.DCSWrapper.Group#Group The DCS Group. +function GROUP:GetDCSObject() + local DCSGroup = Group.getByName( self.GroupName ) + + if DCSGroup then + return DCSGroup + end + + return nil +end + + +--- Returns if the DCS Group is alive. +-- When the group exists at run-time, this method will return true, otherwise false. +-- @param #GROUP self +-- @return #boolean true if the DCS Group is alive. +function GROUP:IsAlive() + self:F2( self.GroupName ) + + local DCSGroup = self:GetDCSObject() + + if DCSGroup then + local GroupIsAlive = DCSGroup:isExist() + self:T3( GroupIsAlive ) + return GroupIsAlive + end + + return nil +end + +--- Destroys the DCS Group and all of its DCS Units. +-- Note that this destroy method also raises a destroy event at run-time. +-- So all event listeners will catch the destroy event of this DCS Group. +-- @param #GROUP self +function GROUP:Destroy() + self:F2( self.GroupName ) + + local DCSGroup = self:GetDCSObject() + + if DCSGroup then + for Index, UnitData in pairs( DCSGroup:getUnits() ) do + self:CreateEventCrash( timer.getTime(), UnitData ) + end + DCSGroup:destroy() + DCSGroup = nil + end + + return nil +end + +--- Returns category of the DCS Group. +-- @param #GROUP self +-- @return Dcs.DCSWrapper.Group#Group.Category The category ID +function GROUP:GetCategory() + self:F2( self.GroupName ) + + local DCSGroup = self:GetDCSObject() + if DCSGroup then + local GroupCategory = DCSGroup:getCategory() + self:T3( GroupCategory ) + return GroupCategory + end + + return nil +end + +--- Returns the category name of the DCS Group. +-- @param #GROUP self +-- @return #string Category name = Helicopter, Airplane, Ground Unit, Ship +function GROUP:GetCategoryName() + self:F2( self.GroupName ) + + local DCSGroup = self:GetDCSObject() + if DCSGroup then + local CategoryNames = { + [Group.Category.AIRPLANE] = "Airplane", + [Group.Category.HELICOPTER] = "Helicopter", + [Group.Category.GROUND] = "Ground Unit", + [Group.Category.SHIP] = "Ship", + } + local GroupCategory = DCSGroup:getCategory() + self:T3( GroupCategory ) + + return CategoryNames[GroupCategory] + end + + return nil +end + + +--- Returns the coalition of the DCS Group. +-- @param #GROUP self +-- @return Dcs.DCSCoalitionWrapper.Object#coalition.side The coalition side of the DCS Group. +function GROUP:GetCoalition() + self:F2( self.GroupName ) + + local DCSGroup = self:GetDCSObject() + if DCSGroup then + local GroupCoalition = DCSGroup:getCoalition() + self:T3( GroupCoalition ) + return GroupCoalition + end + + return nil +end + +--- Returns the country of the DCS Group. +-- @param #GROUP self +-- @return Dcs.DCScountry#country.id The country identifier. +-- @return #nil The DCS Group is not existing or alive. +function GROUP:GetCountry() + self:F2( self.GroupName ) + + local DCSGroup = self:GetDCSObject() + if DCSGroup then + local GroupCountry = DCSGroup:getUnit(1):getCountry() + self:T3( GroupCountry ) + return GroupCountry + end + + return nil +end + +--- Returns the UNIT wrapper class with number UnitNumber. +-- If the underlying DCS Unit does not exist, the method will return nil. . +-- @param #GROUP self +-- @param #number UnitNumber The number of the UNIT wrapper class to be returned. +-- @return Wrapper.Unit#UNIT The UNIT wrapper class. +function GROUP:GetUnit( UnitNumber ) + self:F2( { self.GroupName, UnitNumber } ) + + local DCSGroup = self:GetDCSObject() + + if DCSGroup then + local UnitFound = UNIT:Find( DCSGroup:getUnit( UnitNumber ) ) + self:T3( UnitFound.UnitName ) + self:T2( UnitFound ) + return UnitFound + end + + return nil +end + +--- Returns the DCS Unit with number UnitNumber. +-- If the underlying DCS Unit does not exist, the method will return nil. . +-- @param #GROUP self +-- @param #number UnitNumber The number of the DCS Unit to be returned. +-- @return Dcs.DCSWrapper.Unit#Unit The DCS Unit. +function GROUP:GetDCSUnit( UnitNumber ) + self:F2( { self.GroupName, UnitNumber } ) + + local DCSGroup = self:GetDCSObject() + + if DCSGroup then + local DCSUnitFound = DCSGroup:getUnit( UnitNumber ) + self:T3( DCSUnitFound ) + return DCSUnitFound + end + + return nil +end + +--- Returns current size of the DCS Group. +-- If some of the DCS Units of the DCS Group are destroyed the size of the DCS Group is changed. +-- @param #GROUP self +-- @return #number The DCS Group size. +function GROUP:GetSize() + self:F2( { self.GroupName } ) + local DCSGroup = self:GetDCSObject() + + if DCSGroup then + local GroupSize = DCSGroup:getSize() + self:T3( GroupSize ) + return GroupSize + end + + return nil +end + +--- +--- Returns the initial size of the DCS Group. +-- If some of the DCS Units of the DCS Group are destroyed, the initial size of the DCS Group is unchanged. +-- @param #GROUP self +-- @return #number The DCS Group initial size. +function GROUP:GetInitialSize() + self:F2( { self.GroupName } ) + local DCSGroup = self:GetDCSObject() + + if DCSGroup then + local GroupInitialSize = DCSGroup:getInitialSize() + self:T3( GroupInitialSize ) + return GroupInitialSize + end + + return nil +end + +--- Returns the UNITs wrappers of the DCS Units of the DCS Group. +-- @param #GROUP self +-- @return #table The UNITs wrappers. +function GROUP:GetUnits() + self:F2( { self.GroupName } ) + local DCSGroup = self:GetDCSObject() + + if DCSGroup then + local DCSUnits = DCSGroup:getUnits() + local Units = {} + for Index, UnitData in pairs( DCSUnits ) do + Units[#Units+1] = UNIT:Find( UnitData ) + end + self:T3( Units ) + return Units + end + + return nil +end + + +--- Returns the DCS Units of the DCS Group. +-- @param #GROUP self +-- @return #table The DCS Units. +function GROUP:GetDCSUnits() + self:F2( { self.GroupName } ) + local DCSGroup = self:GetDCSObject() + + if DCSGroup then + local DCSUnits = DCSGroup:getUnits() + self:T3( DCSUnits ) + return DCSUnits + end + + return nil +end + + +--- Activates a GROUP. +-- @param #GROUP self +function GROUP:Activate() + self:F2( { self.GroupName } ) + trigger.action.activateGroup( self:GetDCSObject() ) + return self:GetDCSObject() +end + + +--- Gets the type name of the group. +-- @param #GROUP self +-- @return #string The type name of the group. +function GROUP:GetTypeName() + self:F2( self.GroupName ) + + local DCSGroup = self:GetDCSObject() + + if DCSGroup then + local GroupTypeName = DCSGroup:getUnit(1):getTypeName() + self:T3( GroupTypeName ) + return( GroupTypeName ) + end + + return nil +end + +--- Gets the CallSign of the first DCS Unit of the DCS Group. +-- @param #GROUP self +-- @return #string The CallSign of the first DCS Unit of the DCS Group. +function GROUP:GetCallsign() + self:F2( self.GroupName ) + + local DCSGroup = self:GetDCSObject() + + if DCSGroup then + local GroupCallSign = DCSGroup:getUnit(1):getCallsign() + self:T3( GroupCallSign ) + return GroupCallSign + end + + return nil +end + +--- Returns the current point (Vec2 vector) of the first DCS Unit in the DCS Group. +-- @param #GROUP self +-- @return Dcs.DCSTypes#Vec2 Current Vec2 point of the first DCS Unit of the DCS Group. +function GROUP:GetVec2() + self:F2( self.GroupName ) + + local UnitPoint = self:GetUnit(1) + UnitPoint:GetVec2() + local GroupPointVec2 = UnitPoint:GetVec2() + self:T3( GroupPointVec2 ) + return GroupPointVec2 +end + +--- Returns the current Vec3 vector of the first DCS Unit in the GROUP. +-- @return Dcs.DCSTypes#Vec3 Current Vec3 of the first DCS Unit of the GROUP. +function GROUP:GetVec3() + self:F2( self.GroupName ) + + local GroupVec3 = self:GetUnit(1):GetVec3() + self:T3( GroupVec3 ) + return GroupVec3 +end + + + +-- Is Zone Functions + +--- Returns true if all units of the group are within a @{Zone}. +-- @param #GROUP self +-- @param Core.Zone#ZONE_BASE Zone The zone to test. +-- @return #boolean Returns true if the Group is completely within the @{Core.Zone#ZONE_BASE} +function GROUP:IsCompletelyInZone( Zone ) + self:F2( { self.GroupName, Zone } ) + + for UnitID, UnitData in pairs( self:GetUnits() ) do + local Unit = UnitData -- Wrapper.Unit#UNIT + -- TODO: Rename IsPointVec3InZone to IsVec3InZone + if Zone:IsPointVec3InZone( Unit:GetVec3() ) then + else + return false + end + end + + return true +end + +--- Returns true if some units of the group are within a @{Zone}. +-- @param #GROUP self +-- @param Core.Zone#ZONE_BASE Zone The zone to test. +-- @return #boolean Returns true if the Group is completely within the @{Core.Zone#ZONE_BASE} +function GROUP:IsPartlyInZone( Zone ) + self:F2( { self.GroupName, Zone } ) + + for UnitID, UnitData in pairs( self:GetUnits() ) do + local Unit = UnitData -- Wrapper.Unit#UNIT + if Zone:IsPointVec3InZone( Unit:GetVec3() ) then + return true + end + end + + return false +end + +--- Returns true if none of the group units of the group are within a @{Zone}. +-- @param #GROUP self +-- @param Core.Zone#ZONE_BASE Zone The zone to test. +-- @return #boolean Returns true if the Group is completely within the @{Core.Zone#ZONE_BASE} +function GROUP:IsNotInZone( Zone ) + self:F2( { self.GroupName, Zone } ) + + for UnitID, UnitData in pairs( self:GetUnits() ) do + local Unit = UnitData -- Wrapper.Unit#UNIT + if Zone:IsPointVec3InZone( Unit:GetVec3() ) then + return false + end + end + + return true +end + +--- Returns if the group is of an air category. +-- If the group is a helicopter or a plane, then this method will return true, otherwise false. +-- @param #GROUP self +-- @return #boolean Air category evaluation result. +function GROUP:IsAir() + self:F2( self.GroupName ) + + local DCSGroup = self:GetDCSObject() + + if DCSGroup then + local IsAirResult = DCSGroup:getCategory() == Group.Category.AIRPLANE or DCSGroup:getCategory() == Group.Category.HELICOPTER + self:T3( IsAirResult ) + return IsAirResult + end + + return nil +end + +--- Returns if the DCS Group contains Helicopters. +-- @param #GROUP self +-- @return #boolean true if DCS Group contains Helicopters. +function GROUP:IsHelicopter() + self:F2( self.GroupName ) + + local DCSGroup = self:GetDCSObject() + + if DCSGroup then + local GroupCategory = DCSGroup:getCategory() + self:T2( GroupCategory ) + return GroupCategory == Group.Category.HELICOPTER + end + + return nil +end + +--- Returns if the DCS Group contains AirPlanes. +-- @param #GROUP self +-- @return #boolean true if DCS Group contains AirPlanes. +function GROUP:IsAirPlane() + self:F2() + + local DCSGroup = self:GetDCSObject() + + if DCSGroup then + local GroupCategory = DCSGroup:getCategory() + self:T2( GroupCategory ) + return GroupCategory == Group.Category.AIRPLANE + end + + return nil +end + +--- Returns if the DCS Group contains Ground troops. +-- @param #GROUP self +-- @return #boolean true if DCS Group contains Ground troops. +function GROUP:IsGround() + self:F2() + + local DCSGroup = self:GetDCSObject() + + if DCSGroup then + local GroupCategory = DCSGroup:getCategory() + self:T2( GroupCategory ) + return GroupCategory == Group.Category.GROUND + end + + return nil +end + +--- Returns if the DCS Group contains Ships. +-- @param #GROUP self +-- @return #boolean true if DCS Group contains Ships. +function GROUP:IsShip() + self:F2() + + local DCSGroup = self:GetDCSObject() + + if DCSGroup then + local GroupCategory = DCSGroup:getCategory() + self:T2( GroupCategory ) + return GroupCategory == Group.Category.SHIP + end + + return nil +end + +--- Returns if all units of the group are on the ground or landed. +-- If all units of this group are on the ground, this function will return true, otherwise false. +-- @param #GROUP self +-- @return #boolean All units on the ground result. +function GROUP:AllOnGround() + self:F2() + + local DCSGroup = self:GetDCSObject() + + if DCSGroup then + local AllOnGroundResult = true + + for Index, UnitData in pairs( DCSGroup:getUnits() ) do + if UnitData:inAir() then + AllOnGroundResult = false + end + end + + self:T3( AllOnGroundResult ) + return AllOnGroundResult + end + + return nil +end + +--- Returns the current maximum velocity of the group. +-- Each unit within the group gets evaluated, and the maximum velocity (= the unit which is going the fastest) is returned. +-- @param #GROUP self +-- @return #number Maximum velocity found. +function GROUP:GetMaxVelocity() + self:F2() + + local DCSGroup = self:GetDCSObject() + + if DCSGroup then + local GroupVelocityMax = 0 + + for Index, UnitData in pairs( DCSGroup:getUnits() ) do + + local UnitVelocityVec3 = UnitData:getVelocity() + local UnitVelocity = math.abs( UnitVelocityVec3.x ) + math.abs( UnitVelocityVec3.y ) + math.abs( UnitVelocityVec3.z ) + + if UnitVelocity > GroupVelocityMax then + GroupVelocityMax = UnitVelocity + end + end + + return GroupVelocityMax + end + + return nil +end + +--- Returns the current minimum height of the group. +-- Each unit within the group gets evaluated, and the minimum height (= the unit which is the lowest elevated) is returned. +-- @param #GROUP self +-- @return #number Minimum height found. +function GROUP:GetMinHeight() + self:F2() + +end + +--- Returns the current maximum height of the group. +-- Each unit within the group gets evaluated, and the maximum height (= the unit which is the highest elevated) is returned. +-- @param #GROUP self +-- @return #number Maximum height found. +function GROUP:GetMaxHeight() + self:F2() + +end + +-- SPAWNING + +--- Respawn the @{GROUP} using a (tweaked) template of the Group. +-- The template must be retrieved with the @{Wrapper.Group#GROUP.GetTemplate}() function. +-- The template contains all the definitions as declared within the mission file. +-- To understand templates, do the following: +-- +-- * unpack your .miz file into a directory using 7-zip. +-- * browse in the directory created to the file **mission**. +-- * open the file and search for the country group definitions. +-- +-- Your group template will contain the fields as described within the mission file. +-- +-- This function will: +-- +-- * Get the current position and heading of the group. +-- * When the group is alive, it will tweak the template x, y and heading coordinates of the group and the embedded units to the current units positions. +-- * Then it will destroy the current alive group. +-- * And it will respawn the group using your new template definition. +-- @param Wrapper.Group#GROUP self +-- @param #table Template The template of the Group retrieved with GROUP:GetTemplate() +function GROUP:Respawn( Template ) + + local Vec3 = self:GetVec3() + Template.x = Vec3.x + Template.y = Vec3.z + --Template.x = nil + --Template.y = nil + + self:E( #Template.units ) + for UnitID, UnitData in pairs( self:GetUnits() ) do + local GroupUnit = UnitData -- Wrapper.Unit#UNIT + self:E( GroupUnit:GetName() ) + if GroupUnit:IsAlive() then + local GroupUnitVec3 = GroupUnit:GetVec3() + local GroupUnitHeading = GroupUnit:GetHeading() + Template.units[UnitID].alt = GroupUnitVec3.y + Template.units[UnitID].x = GroupUnitVec3.x + Template.units[UnitID].y = GroupUnitVec3.z + Template.units[UnitID].heading = GroupUnitHeading + self:E( { UnitID, Template.units[UnitID], Template.units[UnitID] } ) + end + end + + self:Destroy() + _DATABASE:Spawn( Template ) +end + +--- Returns the group template from the @{DATABASE} (_DATABASE object). +-- @param #GROUP self +-- @return #table +function GROUP:GetTemplate() + local GroupName = self:GetName() + self:E( GroupName ) + return _DATABASE:GetGroupTemplate( GroupName ) +end + +--- Sets the controlled status in a Template. +-- @param #GROUP self +-- @param #boolean Controlled true is controlled, false is uncontrolled. +-- @return #table +function GROUP:SetTemplateControlled( Template, Controlled ) + Template.uncontrolled = not Controlled + return Template +end + +--- Sets the CountryID of the group in a Template. +-- @param #GROUP self +-- @param Dcs.DCScountry#country.id CountryID The country ID. +-- @return #table +function GROUP:SetTemplateCountry( Template, CountryID ) + Template.CountryID = CountryID + return Template +end + +--- Sets the CoalitionID of the group in a Template. +-- @param #GROUP self +-- @param Dcs.DCSCoalitionWrapper.Object#coalition.side CoalitionID The coalition ID. +-- @return #table +function GROUP:SetTemplateCoalition( Template, CoalitionID ) + Template.CoalitionID = CoalitionID + return Template +end + + + + +--- Return the mission template of the group. +-- @param #GROUP self +-- @return #table The MissionTemplate +function GROUP:GetTaskMission() + self:F2( self.GroupName ) + + return routines.utils.deepCopy( _DATABASE.Templates.Groups[self.GroupName].Template ) +end + +--- Return the mission route of the group. +-- @param #GROUP self +-- @return #table The mission route defined by points. +function GROUP:GetTaskRoute() + self:F2( self.GroupName ) + + return routines.utils.deepCopy( _DATABASE.Templates.Groups[self.GroupName].Template.route.points ) +end + +--- Return the route of a group by using the @{Core.Database#DATABASE} class. +-- @param #GROUP self +-- @param #number Begin The route point from where the copy will start. The base route point is 0. +-- @param #number End The route point where the copy will end. The End point is the last point - the End point. The last point has base 0. +-- @param #boolean Randomize Randomization of the route, when true. +-- @param #number Radius When randomization is on, the randomization is within the radius. +function GROUP:CopyRoute( Begin, End, Randomize, Radius ) + self:F2( { Begin, End } ) + + local Points = {} + + -- Could be a Spawned Group + local GroupName = string.match( self:GetName(), ".*#" ) + if GroupName then + GroupName = GroupName:sub( 1, -2 ) + else + GroupName = self:GetName() + end + + self:T3( { GroupName } ) + + local Template = _DATABASE.Templates.Groups[GroupName].Template + + if Template then + if not Begin then + Begin = 0 + end + if not End then + End = 0 + end + + for TPointID = Begin + 1, #Template.route.points - End do + if Template.route.points[TPointID] then + Points[#Points+1] = routines.utils.deepCopy( Template.route.points[TPointID] ) + if Randomize then + if not Radius then + Radius = 500 + end + Points[#Points].x = Points[#Points].x + math.random( Radius * -1, Radius ) + Points[#Points].y = Points[#Points].y + math.random( Radius * -1, Radius ) + end + end + end + return Points + else + error( "Template not found for Group : " .. GroupName ) + end + + return nil +end + + +--- This module contains the UNIT class. +-- +-- 1) @{#UNIT} class, extends @{Wrapper.Controllable#CONTROLLABLE} +-- =========================================================== +-- The @{#UNIT} class is a wrapper class to handle the DCS Unit objects: +-- +-- * Support all DCS Unit APIs. +-- * Enhance with Unit specific APIs not in the DCS Unit API set. +-- * Handle local Unit Controller. +-- * Manage the "state" of the DCS Unit. +-- +-- +-- 1.1) UNIT reference methods +-- ---------------------- +-- For each DCS Unit object alive within a running mission, a UNIT wrapper object (instance) will be created within the _@{DATABASE} object. +-- This is done at the beginning of the mission (when the mission starts), and dynamically when new DCS Unit objects are spawned (using the @{SPAWN} class). +-- +-- The UNIT class **does not contain a :New()** method, rather it provides **:Find()** methods to retrieve the object reference +-- using the DCS Unit or the DCS UnitName. +-- +-- Another thing to know is that UNIT objects do not "contain" the DCS Unit object. +-- The UNIT methods will reference the DCS Unit object by name when it is needed during API execution. +-- If the DCS Unit object does not exist or is nil, the UNIT methods will return nil and log an exception in the DCS.log file. +-- +-- The UNIT class provides the following functions to retrieve quickly the relevant UNIT instance: +-- +-- * @{#UNIT.Find}(): Find a UNIT instance from the _DATABASE object using a DCS Unit object. +-- * @{#UNIT.FindByName}(): Find a UNIT instance from the _DATABASE object using a DCS Unit name. +-- +-- IMPORTANT: ONE SHOULD NEVER SANATIZE these UNIT OBJECT REFERENCES! (make the UNIT object references nil). +-- +-- 1.2) DCS UNIT APIs +-- ------------------ +-- The DCS Unit APIs are used extensively within MOOSE. The UNIT class has for each DCS Unit API a corresponding method. +-- To be able to distinguish easily in your code the difference between a UNIT API call and a DCS Unit API call, +-- the first letter of the method is also capitalized. So, by example, the DCS Unit method @{Dcs.DCSWrapper.Unit#Unit.getName}() +-- is implemented in the UNIT class as @{#UNIT.GetName}(). +-- +-- 1.3) Smoke, Flare Units +-- ----------------------- +-- The UNIT class provides methods to smoke or flare units easily. +-- The @{#UNIT.SmokeBlue}(), @{#UNIT.SmokeGreen}(),@{#UNIT.SmokeOrange}(), @{#UNIT.SmokeRed}(), @{#UNIT.SmokeRed}() methods +-- will smoke the unit in the corresponding color. Note that smoking a unit is done at the current position of the DCS Unit. +-- When the DCS Unit moves for whatever reason, the smoking will still continue! +-- The @{#UNIT.FlareGreen}(), @{#UNIT.FlareRed}(), @{#UNIT.FlareWhite}(), @{#UNIT.FlareYellow}() +-- methods will fire off a flare in the air with the corresponding color. Note that a flare is a one-off shot and its effect is of very short duration. +-- +-- 1.4) Location Position, Point +-- ----------------------------- +-- The UNIT class provides methods to obtain the current point or position of the DCS Unit. +-- The @{#UNIT.GetPointVec2}(), @{#UNIT.GetVec3}() will obtain the current **location** of the DCS Unit in a Vec2 (2D) or a **point** in a Vec3 (3D) vector respectively. +-- If you want to obtain the complete **3D position** including ori�ntation and direction vectors, consult the @{#UNIT.GetPositionVec3}() method respectively. +-- +-- 1.5) Test if alive +-- ------------------ +-- The @{#UNIT.IsAlive}(), @{#UNIT.IsActive}() methods determines if the DCS Unit is alive, meaning, it is existing and active. +-- +-- 1.6) Test for proximity +-- ----------------------- +-- The UNIT class contains methods to test the location or proximity against zones or other objects. +-- +-- ### 1.6.1) Zones +-- To test whether the Unit is within a **zone**, use the @{#UNIT.IsInZone}() or the @{#UNIT.IsNotInZone}() methods. Any zone can be tested on, but the zone must be derived from @{Core.Zone#ZONE_BASE}. +-- +-- ### 1.6.2) Units +-- Test if another DCS Unit is within a given radius of the current DCS Unit, use the @{#UNIT.OtherUnitInRadius}() method. +-- +-- @module Unit +-- @author FlightControl + + + + + +--- The UNIT class +-- @type UNIT +-- @extends Wrapper.Controllable#CONTROLLABLE +UNIT = { + ClassName="UNIT", +} + + +--- Unit.SensorType +-- @type Unit.SensorType +-- @field OPTIC +-- @field RADAR +-- @field IRST +-- @field RWR + + +-- Registration. + +--- Create a new UNIT from DCSUnit. +-- @param #UNIT self +-- @param #string UnitName The name of the DCS unit. +-- @return #UNIT +function UNIT:Register( UnitName ) + local self = BASE:Inherit( self, CONTROLLABLE:New( UnitName ) ) + self.UnitName = UnitName + return self +end + +-- Reference methods. + +--- Finds a UNIT from the _DATABASE using a DCSUnit object. +-- @param #UNIT self +-- @param Dcs.DCSWrapper.Unit#Unit DCSUnit An existing DCS Unit object reference. +-- @return #UNIT self +function UNIT:Find( DCSUnit ) + + local UnitName = DCSUnit:getName() + local UnitFound = _DATABASE:FindUnit( UnitName ) + return UnitFound +end + +--- Find a UNIT in the _DATABASE using the name of an existing DCS Unit. +-- @param #UNIT self +-- @param #string UnitName The Unit Name. +-- @return #UNIT self +function UNIT:FindByName( UnitName ) + + local UnitFound = _DATABASE:FindUnit( UnitName ) + return UnitFound +end + +--- Return the name of the UNIT. +-- @param #UNIT self +-- @return #string The UNIT name. +function UNIT:Name() + + return self.UnitName +end + + +--- @param #UNIT self +-- @return Dcs.DCSWrapper.Unit#Unit +function UNIT:GetDCSObject() + + local DCSUnit = Unit.getByName( self.UnitName ) + + if DCSUnit then + return DCSUnit + end + + return nil +end + +--- Respawn the @{Unit} using a (tweaked) template of the parent Group. +-- +-- This function will: +-- +-- * Get the current position and heading of the group. +-- * When the unit is alive, it will tweak the template x, y and heading coordinates of the group and the embedded units to the current units positions. +-- * Then it will respawn the re-modelled group. +-- +-- @param #UNIT self +-- @param Dcs.DCSTypes#Vec3 SpawnVec3 The position where to Spawn the new Unit at. +-- @param #number Heading The heading of the unit respawn. +function UNIT:ReSpawn( SpawnVec3, Heading ) + + local SpawnGroupTemplate = UTILS.DeepCopy( _DATABASE:GetGroupTemplateFromUnitName( self:Name() ) ) + self:T( SpawnGroupTemplate ) + + local SpawnGroup = self:GetGroup() + + if SpawnGroup then + + local Vec3 = SpawnGroup:GetVec3() + SpawnGroupTemplate.x = SpawnVec3.x + SpawnGroupTemplate.y = SpawnVec3.z + + self:E( #SpawnGroupTemplate.units ) + for UnitID, UnitData in pairs( SpawnGroup:GetUnits() ) do + local GroupUnit = UnitData -- #UNIT + self:E( GroupUnit:GetName() ) + if GroupUnit:IsAlive() then + local GroupUnitVec3 = GroupUnit:GetVec3() + local GroupUnitHeading = GroupUnit:GetHeading() + SpawnGroupTemplate.units[UnitID].alt = GroupUnitVec3.y + SpawnGroupTemplate.units[UnitID].x = GroupUnitVec3.x + SpawnGroupTemplate.units[UnitID].y = GroupUnitVec3.z + SpawnGroupTemplate.units[UnitID].heading = GroupUnitHeading + self:E( { UnitID, SpawnGroupTemplate.units[UnitID], SpawnGroupTemplate.units[UnitID] } ) + end + end + end + + for UnitTemplateID, UnitTemplateData in pairs( SpawnGroupTemplate.units ) do + self:T( UnitTemplateData.name ) + if UnitTemplateData.name == self:Name() then + self:T("Adjusting") + SpawnGroupTemplate.units[UnitTemplateID].alt = SpawnVec3.y + SpawnGroupTemplate.units[UnitTemplateID].x = SpawnVec3.x + SpawnGroupTemplate.units[UnitTemplateID].y = SpawnVec3.z + SpawnGroupTemplate.units[UnitTemplateID].heading = Heading + self:E( { UnitTemplateID, SpawnGroupTemplate.units[UnitTemplateID], SpawnGroupTemplate.units[UnitTemplateID] } ) + else + self:E( SpawnGroupTemplate.units[UnitTemplateID].name ) + local GroupUnit = UNIT:FindByName( SpawnGroupTemplate.units[UnitTemplateID].name ) -- #UNIT + if GroupUnit and GroupUnit:IsAlive() then + local GroupUnitVec3 = GroupUnit:GetVec3() + local GroupUnitHeading = GroupUnit:GetHeading() + UnitTemplateData.alt = GroupUnitVec3.y + UnitTemplateData.x = GroupUnitVec3.x + UnitTemplateData.y = GroupUnitVec3.z + UnitTemplateData.heading = GroupUnitHeading + else + if SpawnGroupTemplate.units[UnitTemplateID].name ~= self:Name() then + self:T("nilling") + SpawnGroupTemplate.units[UnitTemplateID].delete = true + end + end + end + end + + -- Remove obscolete units from the group structure + i = 1 + while i <= #SpawnGroupTemplate.units do + + local UnitTemplateData = SpawnGroupTemplate.units[i] + self:T( UnitTemplateData.name ) + + if UnitTemplateData.delete then + table.remove( SpawnGroupTemplate.units, i ) + else + i = i + 1 + end + end + + _DATABASE:Spawn( SpawnGroupTemplate ) +end + + + +--- Returns if the unit is activated. +-- @param #UNIT self +-- @return #boolean true if Unit is activated. +-- @return #nil The DCS Unit is not existing or alive. +function UNIT:IsActive() + self:F2( self.UnitName ) + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + + local UnitIsActive = DCSUnit:isActive() + return UnitIsActive + end + + return nil +end + + + +--- Returns the Unit's callsign - the localized string. +-- @param #UNIT self +-- @return #string The Callsign of the Unit. +-- @return #nil The DCS Unit is not existing or alive. +function UNIT:GetCallsign() + self:F2( self.UnitName ) + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitCallSign = DCSUnit:getCallsign() + return UnitCallSign + end + + self:E( self.ClassName .. " " .. self.UnitName .. " not found!" ) + return nil +end + + +--- Returns name of the player that control the unit or nil if the unit is controlled by A.I. +-- @param #UNIT self +-- @return #string Player Name +-- @return #nil The DCS Unit is not existing or alive. +function UNIT:GetPlayerName() + self:F2( self.UnitName ) + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + + local PlayerName = DCSUnit:getPlayerName() + if PlayerName == nil then + PlayerName = "" + end + return PlayerName + end + + return nil +end + +--- Returns the unit's number in the group. +-- The number is the same number the unit has in ME. +-- It may not be changed during the mission. +-- If any unit in the group is destroyed, the numbers of another units will not be changed. +-- @param #UNIT self +-- @return #number The Unit number. +-- @return #nil The DCS Unit is not existing or alive. +function UNIT:GetNumber() + self:F2( self.UnitName ) + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitNumber = DCSUnit:getNumber() + return UnitNumber + end + + return nil +end + +--- Returns the unit's group if it exist and nil otherwise. +-- @param Wrapper.Unit#UNIT self +-- @return Wrapper.Group#GROUP The Group of the Unit. +-- @return #nil The DCS Unit is not existing or alive. +function UNIT:GetGroup() + self:F2( self.UnitName ) + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitGroup = GROUP:Find( DCSUnit:getGroup() ) + return UnitGroup + end + + return nil +end + + +-- Need to add here functions to check if radar is on and which object etc. + +--- Returns the prefix name of the DCS Unit. A prefix name is a part of the name before a '#'-sign. +-- DCS Units spawned with the @{SPAWN} class contain a '#'-sign to indicate the end of the (base) DCS Unit name. +-- The spawn sequence number and unit number are contained within the name after the '#' sign. +-- @param #UNIT self +-- @return #string The name of the DCS Unit. +-- @return #nil The DCS Unit is not existing or alive. +function UNIT:GetPrefix() + self:F2( self.UnitName ) + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitPrefix = string.match( self.UnitName, ".*#" ):sub( 1, -2 ) + self:T3( UnitPrefix ) + return UnitPrefix + end + + return nil +end + +--- Returns the Unit's ammunition. +-- @param #UNIT self +-- @return Dcs.DCSWrapper.Unit#Unit.Ammo +-- @return #nil The DCS Unit is not existing or alive. +function UNIT:GetAmmo() + self:F2( self.UnitName ) + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitAmmo = DCSUnit:getAmmo() + return UnitAmmo + end + + return nil +end + +--- Returns the unit sensors. +-- @param #UNIT self +-- @return Dcs.DCSWrapper.Unit#Unit.Sensors +-- @return #nil The DCS Unit is not existing or alive. +function UNIT:GetSensors() + self:F2( self.UnitName ) + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitSensors = DCSUnit:getSensors() + return UnitSensors + end + + return nil +end + +-- Need to add here a function per sensortype +-- unit:hasSensors(Unit.SensorType.RADAR, Unit.RadarType.AS) + +--- Returns if the unit has sensors of a certain type. +-- @param #UNIT self +-- @return #boolean returns true if the unit has specified types of sensors. This function is more preferable than Unit.getSensors() if you don't want to get information about all the unit's sensors, and just want to check if the unit has specified types of sensors. +-- @return #nil The DCS Unit is not existing or alive. +function UNIT:HasSensors( ... ) + self:F2( arg ) + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local HasSensors = DCSUnit:hasSensors( unpack( arg ) ) + return HasSensors + end + + return nil +end + +--- Returns if the unit is SEADable. +-- @param #UNIT self +-- @return #boolean returns true if the unit is SEADable. +-- @return #nil The DCS Unit is not existing or alive. +function UNIT:HasSEAD() + self:F2() + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitSEADAttributes = DCSUnit:getDesc().attributes + + local HasSEAD = false + if UnitSEADAttributes["RADAR_BAND1_FOR_ARM"] and UnitSEADAttributes["RADAR_BAND1_FOR_ARM"] == true or + UnitSEADAttributes["RADAR_BAND2_FOR_ARM"] and UnitSEADAttributes["RADAR_BAND2_FOR_ARM"] == true then + HasSEAD = true + end + return HasSEAD + end + + return nil +end + +--- Returns two values: +-- +-- * First value indicates if at least one of the unit's radar(s) is on. +-- * Second value is the object of the radar's interest. Not nil only if at least one radar of the unit is tracking a target. +-- @param #UNIT self +-- @return #boolean Indicates if at least one of the unit's radar(s) is on. +-- @return Dcs.DCSWrapper.Object#Object The object of the radar's interest. Not nil only if at least one radar of the unit is tracking a target. +-- @return #nil The DCS Unit is not existing or alive. +function UNIT:GetRadar() + self:F2( self.UnitName ) + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitRadarOn, UnitRadarObject = DCSUnit:getRadar() + return UnitRadarOn, UnitRadarObject + end + + return nil, nil +end + +--- Returns relative amount of fuel (from 0.0 to 1.0) the unit has in its internal tanks. If there are additional fuel tanks the value may be greater than 1.0. +-- @param #UNIT self +-- @return #number The relative amount of fuel (from 0.0 to 1.0). +-- @return #nil The DCS Unit is not existing or alive. +function UNIT:GetFuel() + self:F2( self.UnitName ) + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitFuel = DCSUnit:getFuel() + return UnitFuel + end + + return nil +end + +--- Returns the unit's health. Dead units has health <= 1.0. +-- @param #UNIT self +-- @return #number The Unit's health value. +-- @return #nil The DCS Unit is not existing or alive. +function UNIT:GetLife() + self:F2( self.UnitName ) + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitLife = DCSUnit:getLife() + return UnitLife + end + + return nil +end + +--- Returns the Unit's initial health. +-- @param #UNIT self +-- @return #number The Unit's initial health value. +-- @return #nil The DCS Unit is not existing or alive. +function UNIT:GetLife0() + self:F2( self.UnitName ) + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitLife0 = DCSUnit:getLife0() + return UnitLife0 + end + + return nil +end + +--- Returns the Unit's A2G threat level on a scale from 1 to 10 ... +-- The following threat levels are foreseen: +-- +-- * Threat level 0: Unit is unarmed. +-- * Threat level 1: Unit is infantry. +-- * Threat level 2: Unit is an infantry vehicle. +-- * Threat level 3: Unit is ground artillery. +-- * Threat level 4: Unit is a tank. +-- * Threat level 5: Unit is a modern tank or ifv with ATGM. +-- * Threat level 6: Unit is a AAA. +-- * Threat level 7: Unit is a SAM or manpad, IR guided. +-- * Threat level 8: Unit is a Short Range SAM, radar guided. +-- * Threat level 9: Unit is a Medium Range SAM, radar guided. +-- * Threat level 10: Unit is a Long Range SAM, radar guided. +function UNIT:GetThreatLevel() + + local Attributes = self:GetDesc().attributes + local ThreatLevel = 0 + + local ThreatLevels = { + "Unarmed", + "Infantry", + "Old Tanks & APCs", + "Tanks & IFVs without ATGM", + "Tanks & IFV with ATGM", + "Modern Tanks", + "AAA", + "IR Guided SAMs", + "SR SAMs", + "MR SAMs", + "LR SAMs" + } + + self:T2( Attributes ) + + if Attributes["LR SAM"] then ThreatLevel = 10 + elseif Attributes["MR SAM"] then ThreatLevel = 9 + elseif Attributes["SR SAM"] and + not Attributes["IR Guided SAM"] then ThreatLevel = 8 + elseif ( Attributes["SR SAM"] or Attributes["MANPADS"] ) and + Attributes["IR Guided SAM"] then ThreatLevel = 7 + elseif Attributes["AAA"] then ThreatLevel = 6 + elseif Attributes["Modern Tanks"] then ThreatLevel = 5 + elseif ( Attributes["Tanks"] or Attributes["IFV"] ) and + Attributes["ATGM"] then ThreatLevel = 4 + elseif ( Attributes["Tanks"] or Attributes["IFV"] ) and + not Attributes["ATGM"] then ThreatLevel = 3 + elseif Attributes["Old Tanks"] or Attributes["APC"] then ThreatLevel = 2 + elseif Attributes["Infantry"] then ThreatLevel = 1 + end + + self:T2( ThreatLevel ) + return ThreatLevel, ThreatLevels[ThreatLevel+1] + +end + + +-- Is functions + +--- Returns true if the unit is within a @{Zone}. +-- @param #UNIT self +-- @param Core.Zone#ZONE_BASE Zone The zone to test. +-- @return #boolean Returns true if the unit is within the @{Core.Zone#ZONE_BASE} +function UNIT:IsInZone( Zone ) + self:F2( { self.UnitName, Zone } ) + + if self:IsAlive() then + local IsInZone = Zone:IsPointVec3InZone( self:GetVec3() ) + + self:T( { IsInZone } ) + return IsInZone + end + + return false +end + +--- Returns true if the unit is not within a @{Zone}. +-- @param #UNIT self +-- @param Core.Zone#ZONE_BASE Zone The zone to test. +-- @return #boolean Returns true if the unit is not within the @{Core.Zone#ZONE_BASE} +function UNIT:IsNotInZone( Zone ) + self:F2( { self.UnitName, Zone } ) + + if self:IsAlive() then + local IsInZone = not Zone:IsPointVec3InZone( self:GetVec3() ) + + self:T( { IsInZone } ) + return IsInZone + else + return false + end +end + + +--- Returns true if there is an **other** DCS Unit within a radius of the current 2D point of the DCS Unit. +-- @param #UNIT self +-- @param #UNIT AwaitUnit The other UNIT wrapper object. +-- @param Radius The radius in meters with the DCS Unit in the centre. +-- @return true If the other DCS Unit is within the radius of the 2D point of the DCS Unit. +-- @return #nil The DCS Unit is not existing or alive. +function UNIT:OtherUnitInRadius( AwaitUnit, Radius ) + self:F2( { self.UnitName, AwaitUnit.UnitName, Radius } ) + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitVec3 = self:GetVec3() + local AwaitUnitVec3 = AwaitUnit:GetVec3() + + if (((UnitVec3.x - AwaitUnitVec3.x)^2 + (UnitVec3.z - AwaitUnitVec3.z)^2)^0.5 <= Radius) then + self:T3( "true" ) + return true + else + self:T3( "false" ) + return false + end + end + + return nil +end + + + +--- Signal a flare at the position of the UNIT. +-- @param #UNIT self +-- @param Utilities.Utils#FLARECOLOR FlareColor +function UNIT:Flare( FlareColor ) + self:F2() + trigger.action.signalFlare( self:GetVec3(), FlareColor , 0 ) +end + +--- Signal a white flare at the position of the UNIT. +-- @param #UNIT self +function UNIT:FlareWhite() + self:F2() + trigger.action.signalFlare( self:GetVec3(), trigger.flareColor.White , 0 ) +end + +--- Signal a yellow flare at the position of the UNIT. +-- @param #UNIT self +function UNIT:FlareYellow() + self:F2() + trigger.action.signalFlare( self:GetVec3(), trigger.flareColor.Yellow , 0 ) +end + +--- Signal a green flare at the position of the UNIT. +-- @param #UNIT self +function UNIT:FlareGreen() + self:F2() + trigger.action.signalFlare( self:GetVec3(), trigger.flareColor.Green , 0 ) +end + +--- Signal a red flare at the position of the UNIT. +-- @param #UNIT self +function UNIT:FlareRed() + self:F2() + local Vec3 = self:GetVec3() + if Vec3 then + trigger.action.signalFlare( Vec3, trigger.flareColor.Red, 0 ) + end +end + +--- Smoke the UNIT. +-- @param #UNIT self +function UNIT:Smoke( SmokeColor, Range ) + self:F2() + if Range then + trigger.action.smoke( self:GetRandomVec3( Range ), SmokeColor ) + else + trigger.action.smoke( self:GetVec3(), SmokeColor ) + end + +end + +--- Smoke the UNIT Green. +-- @param #UNIT self +function UNIT:SmokeGreen() + self:F2() + trigger.action.smoke( self:GetVec3(), trigger.smokeColor.Green ) +end + +--- Smoke the UNIT Red. +-- @param #UNIT self +function UNIT:SmokeRed() + self:F2() + trigger.action.smoke( self:GetVec3(), trigger.smokeColor.Red ) +end + +--- Smoke the UNIT White. +-- @param #UNIT self +function UNIT:SmokeWhite() + self:F2() + trigger.action.smoke( self:GetVec3(), trigger.smokeColor.White ) +end + +--- Smoke the UNIT Orange. +-- @param #UNIT self +function UNIT:SmokeOrange() + self:F2() + trigger.action.smoke( self:GetVec3(), trigger.smokeColor.Orange ) +end + +--- Smoke the UNIT Blue. +-- @param #UNIT self +function UNIT:SmokeBlue() + self:F2() + trigger.action.smoke( self:GetVec3(), trigger.smokeColor.Blue ) +end + +-- Is methods + +--- Returns if the unit is of an air category. +-- If the unit is a helicopter or a plane, then this method will return true, otherwise false. +-- @param #UNIT self +-- @return #boolean Air category evaluation result. +function UNIT:IsAir() + self:F2() + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitDescriptor = DCSUnit:getDesc() + self:T3( { UnitDescriptor.category, Unit.Category.AIRPLANE, Unit.Category.HELICOPTER } ) + + local IsAirResult = ( UnitDescriptor.category == Unit.Category.AIRPLANE ) or ( UnitDescriptor.category == Unit.Category.HELICOPTER ) + + self:T3( IsAirResult ) + return IsAirResult + end + + return nil +end + +--- Returns if the unit is of an ground category. +-- If the unit is a ground vehicle or infantry, this method will return true, otherwise false. +-- @param #UNIT self +-- @return #boolean Ground category evaluation result. +function UNIT:IsGround() + self:F2() + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitDescriptor = DCSUnit:getDesc() + self:T3( { UnitDescriptor.category, Unit.Category.GROUND_UNIT } ) + + local IsGroundResult = ( UnitDescriptor.category == Unit.Category.GROUND_UNIT ) + + self:T3( IsGroundResult ) + return IsGroundResult + end + + return nil +end + +--- Returns if the unit is a friendly unit. +-- @param #UNIT self +-- @return #boolean IsFriendly evaluation result. +function UNIT:IsFriendly( FriendlyCoalition ) + self:F2() + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitCoalition = DCSUnit:getCoalition() + self:T3( { UnitCoalition, FriendlyCoalition } ) + + local IsFriendlyResult = ( UnitCoalition == FriendlyCoalition ) + + self:E( IsFriendlyResult ) + return IsFriendlyResult + end + + return nil +end + +--- Returns if the unit is of a ship category. +-- If the unit is a ship, this method will return true, otherwise false. +-- @param #UNIT self +-- @return #boolean Ship category evaluation result. +function UNIT:IsShip() + self:F2() + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitDescriptor = DCSUnit:getDesc() + self:T3( { UnitDescriptor.category, Unit.Category.SHIP } ) + + local IsShipResult = ( UnitDescriptor.category == Unit.Category.SHIP ) + + self:T3( IsShipResult ) + return IsShipResult + end + + return nil +end + +--- This module contains the CLIENT class. +-- +-- 1) @{Wrapper.Client#CLIENT} class, extends @{Wrapper.Unit#UNIT} +-- =============================================== +-- Clients are those **Units** defined within the Mission Editor that have the skillset defined as __Client__ or __Player__. +-- Note that clients are NOT the same as Units, they are NOT necessarily alive. +-- The @{Wrapper.Client#CLIENT} class is a wrapper class to handle the DCS Unit objects that have the skillset defined as __Client__ or __Player__: +-- +-- * Wraps the DCS Unit objects with skill level set to Player or Client. +-- * Support all DCS Unit APIs. +-- * Enhance with Unit specific APIs not in the DCS Group API set. +-- * When player joins Unit, execute alive init logic. +-- * Handles messages to players. +-- * Manage the "state" of the DCS Unit. +-- +-- Clients are being used by the @{MISSION} class to follow players and register their successes. +-- +-- 1.1) CLIENT reference methods +-- ----------------------------- +-- For each DCS Unit having skill level Player or Client, a CLIENT wrapper object (instance) will be created within the _@{DATABASE} object. +-- This is done at the beginning of the mission (when the mission starts). +-- +-- The CLIENT class does not contain a :New() method, rather it provides :Find() methods to retrieve the object reference +-- using the DCS Unit or the DCS UnitName. +-- +-- Another thing to know is that CLIENT objects do not "contain" the DCS Unit object. +-- The CLIENT methods will reference the DCS Unit object by name when it is needed during API execution. +-- If the DCS Unit object does not exist or is nil, the CLIENT methods will return nil and log an exception in the DCS.log file. +-- +-- The CLIENT class provides the following functions to retrieve quickly the relevant CLIENT instance: +-- +-- * @{#CLIENT.Find}(): Find a CLIENT instance from the _DATABASE object using a DCS Unit object. +-- * @{#CLIENT.FindByName}(): Find a CLIENT instance from the _DATABASE object using a DCS Unit name. +-- +-- IMPORTANT: ONE SHOULD NEVER SANATIZE these CLIENT OBJECT REFERENCES! (make the CLIENT object references nil). +-- +-- @module Client +-- @author FlightControl + +--- The CLIENT class +-- @type CLIENT +-- @extends Wrapper.Unit#UNIT +CLIENT = { + ONBOARDSIDE = { + NONE = 0, + LEFT = 1, + RIGHT = 2, + BACK = 3, + FRONT = 4 + }, + ClassName = "CLIENT", + ClientName = nil, + ClientAlive = false, + ClientTransport = false, + ClientBriefingShown = false, + _Menus = {}, + _Tasks = {}, + Messages = { + } +} + + +--- Finds a CLIENT from the _DATABASE using the relevant DCS Unit. +-- @param #CLIENT self +-- @param #string ClientName Name of the DCS **Unit** as defined within the Mission Editor. +-- @param #string ClientBriefing Text that describes the briefing of the mission when a Player logs into the Client. +-- @return #CLIENT +-- @usage +-- -- Create new Clients. +-- local Mission = MISSIONSCHEDULER.AddMission( 'Russia Transport Troops SA-6', 'Operational', 'Transport troops from the control center to one of the SA-6 SAM sites to activate their operation.', 'Russia' ) +-- Mission:AddGoal( DeploySA6TroopsGoal ) +-- +-- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*HOT-Deploy Troops 1' ):Transport() ) +-- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*RAMP-Deploy Troops 3' ):Transport() ) +-- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*HOT-Deploy Troops 2' ):Transport() ) +-- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*RAMP-Deploy Troops 4' ):Transport() ) +function CLIENT:Find( DCSUnit ) + local ClientName = DCSUnit:getName() + local ClientFound = _DATABASE:FindClient( ClientName ) + + if ClientFound then + ClientFound:F( ClientName ) + return ClientFound + end + + error( "CLIENT not found for: " .. ClientName ) +end + + +--- Finds a CLIENT from the _DATABASE using the relevant Client Unit Name. +-- As an optional parameter, a briefing text can be given also. +-- @param #CLIENT self +-- @param #string ClientName Name of the DCS **Unit** as defined within the Mission Editor. +-- @param #string ClientBriefing Text that describes the briefing of the mission when a Player logs into the Client. +-- @param #boolean Error A flag that indicates whether an error should be raised if the CLIENT cannot be found. By default an error will be raised. +-- @return #CLIENT +-- @usage +-- -- Create new Clients. +-- local Mission = MISSIONSCHEDULER.AddMission( 'Russia Transport Troops SA-6', 'Operational', 'Transport troops from the control center to one of the SA-6 SAM sites to activate their operation.', 'Russia' ) +-- Mission:AddGoal( DeploySA6TroopsGoal ) +-- +-- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*HOT-Deploy Troops 1' ):Transport() ) +-- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*RAMP-Deploy Troops 3' ):Transport() ) +-- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*HOT-Deploy Troops 2' ):Transport() ) +-- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*RAMP-Deploy Troops 4' ):Transport() ) +function CLIENT:FindByName( ClientName, ClientBriefing, Error ) + local ClientFound = _DATABASE:FindClient( ClientName ) + + if ClientFound then + ClientFound:F( { ClientName, ClientBriefing } ) + ClientFound:AddBriefing( ClientBriefing ) + ClientFound.MessageSwitch = true + + return ClientFound + end + + if not Error then + error( "CLIENT not found for: " .. ClientName ) + end +end + +function CLIENT:Register( ClientName ) + local self = BASE:Inherit( self, UNIT:Register( ClientName ) ) + + self:F( ClientName ) + self.ClientName = ClientName + self.MessageSwitch = true + self.ClientAlive2 = false + + --self.AliveCheckScheduler = routines.scheduleFunction( self._AliveCheckScheduler, { self }, timer.getTime() + 1, 5 ) + self.AliveCheckScheduler = SCHEDULER:New( self, self._AliveCheckScheduler, { "Client Alive " .. ClientName }, 1, 5 ) + + self:E( self ) + return self +end + + +--- Transport defines that the Client is a Transport. Transports show cargo. +-- @param #CLIENT self +-- @return #CLIENT +function CLIENT:Transport() + self:F() + + self.ClientTransport = true + return self +end + +--- AddBriefing adds a briefing to a CLIENT when a player joins a mission. +-- @param #CLIENT self +-- @param #string ClientBriefing is the text defining the Mission briefing. +-- @return #CLIENT self +function CLIENT:AddBriefing( ClientBriefing ) + self:F( ClientBriefing ) + self.ClientBriefing = ClientBriefing + self.ClientBriefingShown = false + + return self +end + +--- Show the briefing of a CLIENT. +-- @param #CLIENT self +-- @return #CLIENT self +function CLIENT:ShowBriefing() + self:F( { self.ClientName, self.ClientBriefingShown } ) + + if not self.ClientBriefingShown then + self.ClientBriefingShown = true + local Briefing = "" + if self.ClientBriefing then + Briefing = Briefing .. self.ClientBriefing + end + Briefing = Briefing .. " Press [LEFT ALT]+[B] to view the complete mission briefing." + self:Message( Briefing, 60, "Briefing" ) + end + + return self +end + +--- Show the mission briefing of a MISSION to the CLIENT. +-- @param #CLIENT self +-- @param #string MissionBriefing +-- @return #CLIENT self +function CLIENT:ShowMissionBriefing( MissionBriefing ) + self:F( { self.ClientName } ) + + if MissionBriefing then + self:Message( MissionBriefing, 60, "Mission Briefing" ) + end + + return self +end + + + +--- Resets a CLIENT. +-- @param #CLIENT self +-- @param #string ClientName Name of the Group as defined within the Mission Editor. The Group must have a Unit with the type Client. +function CLIENT:Reset( ClientName ) + self:F() + self._Menus = {} +end + +-- Is Functions + +--- Checks if the CLIENT is a multi-seated UNIT. +-- @param #CLIENT self +-- @return #boolean true if multi-seated. +function CLIENT:IsMultiSeated() + self:F( self.ClientName ) + + local ClientMultiSeatedTypes = { + ["Mi-8MT"] = "Mi-8MT", + ["UH-1H"] = "UH-1H", + ["P-51B"] = "P-51B" + } + + if self:IsAlive() then + local ClientTypeName = self:GetClientGroupUnit():GetTypeName() + if ClientMultiSeatedTypes[ClientTypeName] then + return true + end + end + + return false +end + +--- Checks for a client alive event and calls a function on a continuous basis. +-- @param #CLIENT self +-- @param #function CallBack Function. +-- @return #CLIENT +function CLIENT:Alive( CallBackFunction, ... ) + self:F() + + self.ClientCallBack = CallBackFunction + self.ClientParameters = arg + + return self +end + +--- @param #CLIENT self +function CLIENT:_AliveCheckScheduler( SchedulerName ) + self:F3( { SchedulerName, self.ClientName, self.ClientAlive2, self.ClientBriefingShown, self.ClientCallBack } ) + + if self:IsAlive() then + if self.ClientAlive2 == false then + self:ShowBriefing() + if self.ClientCallBack then + self:T("Calling Callback function") + self.ClientCallBack( self, unpack( self.ClientParameters ) ) + end + self.ClientAlive2 = true + end + else + if self.ClientAlive2 == true then + self.ClientAlive2 = false + end + end + + return true +end + +--- Return the DCSGroup of a Client. +-- This function is modified to deal with a couple of bugs in DCS 1.5.3 +-- @param #CLIENT self +-- @return Dcs.DCSWrapper.Group#Group +function CLIENT:GetDCSGroup() + self:F3() + +-- local ClientData = Group.getByName( self.ClientName ) +-- if ClientData and ClientData:isExist() then +-- self:T( self.ClientName .. " : group found!" ) +-- return ClientData +-- else +-- return nil +-- end + + local ClientUnit = Unit.getByName( self.ClientName ) + + local CoalitionsData = { AlivePlayersRed = coalition.getPlayers( coalition.side.RED ), AlivePlayersBlue = coalition.getPlayers( coalition.side.BLUE ) } + for CoalitionId, CoalitionData in pairs( CoalitionsData ) do + self:T3( { "CoalitionData:", CoalitionData } ) + for UnitId, UnitData in pairs( CoalitionData ) do + self:T3( { "UnitData:", UnitData } ) + if UnitData and UnitData:isExist() then + + --self:E(self.ClientName) + if ClientUnit then + local ClientGroup = ClientUnit:getGroup() + if ClientGroup then + self:T3( "ClientGroup = " .. self.ClientName ) + if ClientGroup:isExist() and UnitData:getGroup():isExist() then + if ClientGroup:getID() == UnitData:getGroup():getID() then + self:T3( "Normal logic" ) + self:T3( self.ClientName .. " : group found!" ) + self.ClientGroupID = ClientGroup:getID() + self.ClientGroupName = ClientGroup:getName() + return ClientGroup + end + else + -- Now we need to resolve the bugs in DCS 1.5 ... + -- Consult the database for the units of the Client Group. (ClientGroup:getUnits() returns nil) + self:T3( "Bug 1.5 logic" ) + local ClientGroupTemplate = _DATABASE.Templates.Units[self.ClientName].GroupTemplate + self.ClientGroupID = ClientGroupTemplate.groupId + self.ClientGroupName = _DATABASE.Templates.Units[self.ClientName].GroupName + self:T3( self.ClientName .. " : group found in bug 1.5 resolvement logic!" ) + return ClientGroup + end + -- else + -- error( "Client " .. self.ClientName .. " not found!" ) + end + else + --self:E( { "Client not found!", self.ClientName } ) + end + end + end + end + + -- For non player clients + if ClientUnit then + local ClientGroup = ClientUnit:getGroup() + if ClientGroup then + self:T3( "ClientGroup = " .. self.ClientName ) + if ClientGroup:isExist() then + self:T3( "Normal logic" ) + self:T3( self.ClientName .. " : group found!" ) + return ClientGroup + end + end + end + + self.ClientGroupID = nil + self.ClientGroupUnit = nil + + return nil +end + + +-- TODO: Check Dcs.DCSTypes#Group.ID +--- Get the group ID of the client. +-- @param #CLIENT self +-- @return Dcs.DCSTypes#Group.ID +function CLIENT:GetClientGroupID() + + local ClientGroup = self:GetDCSGroup() + + --self:E( self.ClientGroupID ) -- Determined in GetDCSGroup() + return self.ClientGroupID +end + + +--- Get the name of the group of the client. +-- @param #CLIENT self +-- @return #string +function CLIENT:GetClientGroupName() + + local ClientGroup = self:GetDCSGroup() + + self:T( self.ClientGroupName ) -- Determined in GetDCSGroup() + return self.ClientGroupName +end + +--- Returns the UNIT of the CLIENT. +-- @param #CLIENT self +-- @return Wrapper.Unit#UNIT +function CLIENT:GetClientGroupUnit() + self:F2() + + local ClientDCSUnit = Unit.getByName( self.ClientName ) + + self:T( self.ClientDCSUnit ) + if ClientDCSUnit and ClientDCSUnit:isExist() then + local ClientUnit = _DATABASE:FindUnit( self.ClientName ) + self:T2( ClientUnit ) + return ClientUnit + end +end + +--- Returns the DCSUnit of the CLIENT. +-- @param #CLIENT self +-- @return Dcs.DCSTypes#Unit +function CLIENT:GetClientGroupDCSUnit() + self:F2() + + local ClientDCSUnit = Unit.getByName( self.ClientName ) + + if ClientDCSUnit and ClientDCSUnit:isExist() then + self:T2( ClientDCSUnit ) + return ClientDCSUnit + end +end + + +--- Evaluates if the CLIENT is a transport. +-- @param #CLIENT self +-- @return #boolean true is a transport. +function CLIENT:IsTransport() + self:F() + return self.ClientTransport +end + +--- Shows the @{AI.AI_Cargo#CARGO} contained within the CLIENT to the player as a message. +-- The @{AI.AI_Cargo#CARGO} is shown using the @{Core.Message#MESSAGE} distribution system. +-- @param #CLIENT self +function CLIENT:ShowCargo() + self:F() + + local CargoMsg = "" + + for CargoName, Cargo in pairs( CARGOS ) do + if self == Cargo:IsLoadedInClient() then + CargoMsg = CargoMsg .. Cargo.CargoName .. " Type:" .. Cargo.CargoType .. " Weight: " .. Cargo.CargoWeight .. "\n" + end + end + + if CargoMsg == "" then + CargoMsg = "empty" + end + + self:Message( CargoMsg, 15, "Co-Pilot: Cargo Status", 30 ) + +end + +-- TODO (1) I urgently need to revise this. +--- A local function called by the DCS World Menu system to switch off messages. +function CLIENT.SwitchMessages( PrmTable ) + PrmTable[1].MessageSwitch = PrmTable[2] +end + +--- The main message driver for the CLIENT. +-- This function displays various messages to the Player logged into the CLIENT through the DCS World Messaging system. +-- @param #CLIENT self +-- @param #string Message is the text describing the message. +-- @param #number MessageDuration is the duration in seconds that the Message should be displayed. +-- @param #string MessageCategory is the category of the message (the title). +-- @param #number MessageInterval is the interval in seconds between the display of the @{Core.Message#MESSAGE} when the CLIENT is in the air. +-- @param #string MessageID is the identifier of the message when displayed with intervals. +function CLIENT:Message( Message, MessageDuration, MessageCategory, MessageInterval, MessageID ) + self:F( { Message, MessageDuration, MessageCategory, MessageInterval } ) + + if self.MessageSwitch == true then + if MessageCategory == nil then + MessageCategory = "Messages" + end + if MessageID ~= nil then + if self.Messages[MessageID] == nil then + self.Messages[MessageID] = {} + self.Messages[MessageID].MessageId = MessageID + self.Messages[MessageID].MessageTime = timer.getTime() + self.Messages[MessageID].MessageDuration = MessageDuration + if MessageInterval == nil then + self.Messages[MessageID].MessageInterval = 600 + else + self.Messages[MessageID].MessageInterval = MessageInterval + end + MESSAGE:New( Message, MessageDuration, MessageCategory ):ToClient( self ) + else + if self:GetClientGroupDCSUnit() and not self:GetClientGroupDCSUnit():inAir() then + if timer.getTime() - self.Messages[MessageID].MessageTime >= self.Messages[MessageID].MessageDuration + 10 then + MESSAGE:New( Message, MessageDuration , MessageCategory):ToClient( self ) + self.Messages[MessageID].MessageTime = timer.getTime() + end + else + if timer.getTime() - self.Messages[MessageID].MessageTime >= self.Messages[MessageID].MessageDuration + self.Messages[MessageID].MessageInterval then + MESSAGE:New( Message, MessageDuration, MessageCategory ):ToClient( self ) + self.Messages[MessageID].MessageTime = timer.getTime() + end + end + end + else + MESSAGE:New( Message, MessageDuration, MessageCategory ):ToClient( self ) + end + end +end +--- This module contains the STATIC class. +-- +-- 1) @{Wrapper.Static#STATIC} class, extends @{Wrapper.Positionable#POSITIONABLE} +-- =============================================================== +-- Statics are **Static Units** defined within the Mission Editor. +-- Note that Statics are almost the same as Units, but they don't have a controller. +-- The @{Wrapper.Static#STATIC} class is a wrapper class to handle the DCS Static objects: +-- +-- * Wraps the DCS Static objects. +-- * Support all DCS Static APIs. +-- * Enhance with Static specific APIs not in the DCS API set. +-- +-- 1.1) STATIC reference methods +-- ----------------------------- +-- For each DCS Static will have a STATIC wrapper object (instance) within the _@{DATABASE} object. +-- This is done at the beginning of the mission (when the mission starts). +-- +-- The STATIC class does not contain a :New() method, rather it provides :Find() methods to retrieve the object reference +-- using the Static Name. +-- +-- Another thing to know is that STATIC objects do not "contain" the DCS Static object. +-- The STATIc methods will reference the DCS Static object by name when it is needed during API execution. +-- If the DCS Static object does not exist or is nil, the STATIC methods will return nil and log an exception in the DCS.log file. +-- +-- The STATIc class provides the following functions to retrieve quickly the relevant STATIC instance: +-- +-- * @{#STATIC.FindByName}(): Find a STATIC instance from the _DATABASE object using a DCS Static name. +-- +-- IMPORTANT: ONE SHOULD NEVER SANATIZE these STATIC OBJECT REFERENCES! (make the STATIC object references nil). +-- +-- @module Static +-- @author FlightControl + + + + + + +--- The STATIC class +-- @type STATIC +-- @extends Wrapper.Positionable#POSITIONABLE +STATIC = { + ClassName = "STATIC", +} + + +--- Finds a STATIC from the _DATABASE using the relevant Static Name. +-- As an optional parameter, a briefing text can be given also. +-- @param #STATIC self +-- @param #string StaticName Name of the DCS **Static** as defined within the Mission Editor. +-- @return #STATIC +function STATIC:FindByName( StaticName ) + local StaticFound = _DATABASE:FindStatic( StaticName ) + + self.StaticName = StaticName + + if StaticFound then + StaticFound:F( { StaticName } ) + + return StaticFound + end + + error( "STATIC not found for: " .. StaticName ) +end + +function STATIC:Register( StaticName ) + local self = BASE:Inherit( self, POSITIONABLE:New( StaticName ) ) + self.StaticName = StaticName + return self +end + + +function STATIC:GetDCSObject() + local DCSStatic = StaticObject.getByName( self.StaticName ) + + if DCSStatic then + return DCSStatic + end + + return nil +end +--- This module contains the AIRBASE classes. +-- +-- === +-- +-- 1) @{Wrapper.Airbase#AIRBASE} class, extends @{Wrapper.Positionable#POSITIONABLE} +-- ================================================================= +-- The @{AIRBASE} class is a wrapper class to handle the DCS Airbase objects: +-- +-- * Support all DCS Airbase APIs. +-- * Enhance with Airbase specific APIs not in the DCS Airbase API set. +-- +-- +-- 1.1) AIRBASE reference methods +-- ------------------------------ +-- For each DCS Airbase object alive within a running mission, a AIRBASE wrapper object (instance) will be created within the _@{DATABASE} object. +-- This is done at the beginning of the mission (when the mission starts). +-- +-- The AIRBASE class **does not contain a :New()** method, rather it provides **:Find()** methods to retrieve the object reference +-- using the DCS Airbase or the DCS AirbaseName. +-- +-- Another thing to know is that AIRBASE objects do not "contain" the DCS Airbase object. +-- The AIRBASE methods will reference the DCS Airbase object by name when it is needed during API execution. +-- If the DCS Airbase object does not exist or is nil, the AIRBASE methods will return nil and log an exception in the DCS.log file. +-- +-- The AIRBASE class provides the following functions to retrieve quickly the relevant AIRBASE instance: +-- +-- * @{#AIRBASE.Find}(): Find a AIRBASE instance from the _DATABASE object using a DCS Airbase object. +-- * @{#AIRBASE.FindByName}(): Find a AIRBASE instance from the _DATABASE object using a DCS Airbase name. +-- +-- IMPORTANT: ONE SHOULD NEVER SANATIZE these AIRBASE OBJECT REFERENCES! (make the AIRBASE object references nil). +-- +-- 1.2) DCS AIRBASE APIs +-- --------------------- +-- The DCS Airbase APIs are used extensively within MOOSE. The AIRBASE class has for each DCS Airbase API a corresponding method. +-- To be able to distinguish easily in your code the difference between a AIRBASE API call and a DCS Airbase API call, +-- the first letter of the method is also capitalized. So, by example, the DCS Airbase method @{Dcs.DCSWrapper.Airbase#Airbase.getName}() +-- is implemented in the AIRBASE class as @{#AIRBASE.GetName}(). +-- +-- More functions will be added +-- ---------------------------- +-- During the MOOSE development, more functions will be added. +-- +-- @module Airbase +-- @author FlightControl + + + + + +--- The AIRBASE class +-- @type AIRBASE +-- @extends Wrapper.Positionable#POSITIONABLE +AIRBASE = { + ClassName="AIRBASE", + CategoryName = { + [Airbase.Category.AIRDROME] = "Airdrome", + [Airbase.Category.HELIPAD] = "Helipad", + [Airbase.Category.SHIP] = "Ship", + }, + } + +-- Registration. + +--- Create a new AIRBASE from DCSAirbase. +-- @param #AIRBASE self +-- @param #string AirbaseName The name of the airbase. +-- @return Wrapper.Airbase#AIRBASE +function AIRBASE:Register( AirbaseName ) + + local self = BASE:Inherit( self, POSITIONABLE:New( AirbaseName ) ) + self.AirbaseName = AirbaseName + return self +end + +-- Reference methods. + +--- Finds a AIRBASE from the _DATABASE using a DCSAirbase object. +-- @param #AIRBASE self +-- @param Dcs.DCSWrapper.Airbase#Airbase DCSAirbase An existing DCS Airbase object reference. +-- @return Wrapper.Airbase#AIRBASE self +function AIRBASE:Find( DCSAirbase ) + + local AirbaseName = DCSAirbase:getName() + local AirbaseFound = _DATABASE:FindAirbase( AirbaseName ) + return AirbaseFound +end + +--- Find a AIRBASE in the _DATABASE using the name of an existing DCS Airbase. +-- @param #AIRBASE self +-- @param #string AirbaseName The Airbase Name. +-- @return Wrapper.Airbase#AIRBASE self +function AIRBASE:FindByName( AirbaseName ) + + local AirbaseFound = _DATABASE:FindAirbase( AirbaseName ) + return AirbaseFound +end + +function AIRBASE:GetDCSObject() + local DCSAirbase = Airbase.getByName( self.AirbaseName ) + + if DCSAirbase then + return DCSAirbase + end + + return nil +end ---- Declare the event dispatcher based on the EVENT class -_EVENTDISPATCHER = EVENT:New() -- Event#EVENT ---- Declare the main database object, which is used internally by the MOOSE classes. -_DATABASE = DATABASE:New() -- Database#DATABASE --- Scoring system for MOOSE. -- This scoring class calculates the hits and kills that players make within a simulation session. @@ -14981,7 +16592,7 @@ _DATABASE = DATABASE:New() -- Database#DATABASE --- The Scoring class -- @type SCORING -- @field Players A collection of the current players that have joined the game. --- @extends Base#BASE +-- @extends Core.Base#BASE SCORING = { ClassName = "SCORING", ClassID = 0, @@ -15071,7 +16682,7 @@ end --- Track DEAD or CRASH events for the scoring. -- @param #SCORING self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event function SCORING:_EventOnDeadOrCrash( Event ) self:F( { Event } ) @@ -15236,8 +16847,8 @@ end --- Registers Scores the players completing a Mission Task. -- @param #SCORING self --- @param Mission#MISSION Mission --- @param Unit#UNIT PlayerUnit +-- @param Tasking.Mission#MISSION Mission +-- @param Wrapper.Unit#UNIT PlayerUnit -- @param #string Text -- @param #number Score function SCORING:_AddMissionTaskScore( Mission, PlayerUnit, Text, Score ) @@ -15246,18 +16857,20 @@ function SCORING:_AddMissionTaskScore( Mission, PlayerUnit, Text, Score ) local MissionName = Mission:GetName() self:F( { Mission:GetName(), PlayerUnit.UnitName, PlayerName, Text, Score } ) + + local PlayerData = self.Players[PlayerName] - if not self.Players[PlayerName].Mission[MissionName] then - self.Players[PlayerName].Mission[MissionName] = {} - self.Players[PlayerName].Mission[MissionName].ScoreTask = 0 - self.Players[PlayerName].Mission[MissionName].ScoreMission = 0 + if not PlayerData.Mission[MissionName] then + PlayerData.Mission[MissionName] = {} + PlayerData.Mission[MissionName].ScoreTask = 0 + PlayerData.Mission[MissionName].ScoreMission = 0 end self:T( PlayerName ) - self:T( self.Players[PlayerName].Mission[MissionName] ) + self:T( PlayerData.Mission[MissionName] ) - self.Players[PlayerName].Score = self.Players[PlayerName].Score + Score - self.Players[PlayerName].Mission[MissionName].ScoreTask = self.Players[PlayerName].Mission[MissionName].ScoreTask + Score + PlayerData.Score = self.Players[PlayerName].Score + Score + PlayerData.Mission[MissionName].ScoreTask = self.Players[PlayerName].Mission[MissionName].ScoreTask + Score MESSAGE:New( "Player '" .. PlayerName .. "' has " .. Text .. " in Mission '" .. MissionName .. "'. " .. Score .. " task score!", @@ -15269,18 +16882,20 @@ end --- Registers Mission Scores for possible multiple players that contributed in the Mission. -- @param #SCORING self --- @param Mission#MISSION Mission --- @param Unit#UNIT PlayerUnit +-- @param Tasking.Mission#MISSION Mission +-- @param Wrapper.Unit#UNIT PlayerUnit -- @param #string Text -- @param #number Score function SCORING:_AddMissionScore( Mission, Text, Score ) local MissionName = Mission:GetName() - self:F( { Mission, Text, Score } ) + self:E( { Mission, Text, Score } ) + self:E( self.Players ) for PlayerName, PlayerData in pairs( self.Players ) do + self:E( PlayerData ) if PlayerData.Mission[MissionName] then PlayerData.Score = PlayerData.Score + Score @@ -15297,7 +16912,7 @@ end --- Handles the OnHit event for the scoring. -- @param #SCORING self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event function SCORING:_EventOnHit( Event ) self:F( { Event } ) @@ -15790,4776 +17405,6 @@ function SCORING:CloseCSV() end end ---- This module contains the CARGO classes. --- --- === --- --- 1) @{Cargo#CARGO_BASE} class, extends @{Base#BASE} --- ================================================== --- The @{#CARGO_BASE} class defines the core functions that defines a cargo object within MOOSE. --- A cargo is a logical object defined within a @{Mission}, that is available for transport, and has a life status within a simulation. --- --- Cargo can be of various forms: --- --- * CARGO_UNIT, represented by a @{Unit} in a @{Group}: Cargo can be represented by a Unit in a Group. Destruction of the Unit will mean that the cargo is lost. --- * CARGO_STATIC, represented by a @{Static}: Cargo can be represented by a Static. Destruction of the Static will mean that the cargo is lost. --- * CARGO_PACKAGE, contained in a @{Unit} of a @{Group}: Cargo can be contained within a Unit of a Group. The cargo can be **delivered** by the @{Unit}. If the Unit is destroyed, the cargo will be destroyed also. --- * CARGO_PACKAGE, Contained in a @{Static}: Cargo can be contained within a Static. The cargo can be **collected** from the @Static. If the @{Static} is destroyed, the cargo will be destroyed. --- * CARGO_SLINGLOAD, represented by a @{Cargo} that is transportable: Cargo can be represented by a Cargo object that is transportable. Destruction of the Cargo will mean that the cargo is lost. --- --- @module Cargo - - - -CARGOS = {} - -do -- CARGO - - --- @type CARGO - -- @extends Base#BASE - -- @field #string Type A string defining the type of the cargo. eg. Engineers, Equipment, Screwdrivers. - -- @field #string Name A string defining the name of the cargo. The name is the unique identifier of the cargo. - -- @field #number Weight A number defining the weight of the cargo. The weight is expressed in kg. - -- @field #number ReportRadius (optional) A number defining the radius in meters when the cargo is signalling or reporting to a Carrier. - -- @field #number NearRadius (optional) A number defining the radius in meters when the cargo is near to a Carrier, so that it can be loaded. - -- @field Controllable#CONTROLLABLE CargoObject The alive DCS object representing the cargo. This value can be nil, meaning, that the cargo is not represented anywhere... - -- @field Positionable#POSITIONABLE CargoCarrier The alive DCS object carrying the cargo. This value can be nil, meaning, that the cargo is not contained anywhere... - -- @field #boolean Slingloadable This flag defines if the cargo can be slingloaded. - -- @field #boolean Moveable This flag defines if the cargo is moveable. - -- @field #boolean Representable This flag defines if the cargo can be represented by a DCS Unit. - -- @field #boolean Containable This flag defines if the cargo can be contained within a DCS Unit. - CARGO = { - ClassName = "CARGO", - Type = nil, - Name = nil, - Weight = nil, - CargoObject = nil, - CargoCarrier = nil, - Representable = false, - Slingloadable = false, - Moveable = false, - Containable = false, - } - ---- @type CARGO.CargoObjects --- @map < #string, Positionable#POSITIONABLE > The alive POSITIONABLE objects representing the the cargo. - - ---- CARGO Constructor. --- @param #CARGO self --- @param Mission#MISSION Mission --- @param #string Type --- @param #string Name --- @param #number Weight --- @param #number ReportRadius (optional) --- @param #number NearRadius (optional) --- @return #CARGO -function CARGO:New( Mission, Type, Name, Weight, ReportRadius, NearRadius ) - local self = BASE:Inherit( self, BASE:New() ) -- #CARGO - self:F( { Type, Name, Weight, ReportRadius, NearRadius } ) - - - self.Type = Type - self.Name = Name - self.Weight = Weight - self.ReportRadius = ReportRadius - self.NearRadius = NearRadius - self.CargoObject = nil - self.CargoCarrier = nil - self.Representable = false - self.Slingloadable = false - self.Moveable = false - self.Containable = false - - - self.CargoScheduler = SCHEDULER:New() - - CARGOS[self.Name] = self - - return self -end - - ---- Template method to spawn a new representation of the CARGO in the simulator. --- @param #CARGO self --- @return #CARGO -function CARGO:Spawn( PointVec2 ) - self:F() - -end - ---- Load Cargo to a Carrier. --- @param #CARGO self --- @param Unit#UNIT CargoCarrier -function CARGO:Load( CargoCarrier ) - self:F() - - self:_NextEvent( self.FsmP.Load, CargoCarrier ) -end - ---- UnLoad Cargo from a Carrier with a UnLoadDistance and an Angle. --- @param #CARGO self --- @param #number UnLoadDistance --- @param #number Angle -function CARGO:UnLoad( CargoCarrier ) - self:F() - - self:_NextEvent( self.FsmP.Board, CargoCarrier ) -end - ---- Board Cargo to a Carrier with a defined Speed. --- @param #CARGO self --- @param Unit#UNIT CargoCarrier -function CARGO:Board( CargoCarrier ) - self:F() - - self:_NextEvent( self.FsmP.Board, CargoCarrier ) -end - ---- UnLoad Cargo from a Carrier. --- @param #CARGO self -function CARGO:UnLoad() - self:F() - - self:_NextEvent( self.FsmP.UnLoad ) -end - ---- Check if CargoCarrier is near the Cargo to be Loaded. --- @param #CARGO self --- @param Point#POINT_VEC2 PointVec2 --- @return #boolean -function CARGO:IsNear( PointVec2 ) - self:F() - - local Distance = PointVec2:DistanceFromPointVec2( self.CargoObject:GetPointVec2() ) - self:T( Distance ) - - if Distance <= self.NearRadius then - return true - else - return false - end -end - - ---- On Loaded callback function. -function CARGO:OnLoaded( CallBackFunction, ... ) - self:F() - - self.OnLoadedCallBack = CallBackFunction - self.OnLoadedParameters = arg - -end - ---- On UnLoaded callback function. -function CARGO:OnUnLoaded( CallBackFunction, ... ) - self:F() - - self.OnUnLoadedCallBack = CallBackFunction - self.OnUnLoadedParameters = arg -end - ---- @param #CARGO self -function CARGO:_NextEvent( NextEvent, ... ) - self:F( self.Name ) - SCHEDULER:New( self.FsmP, NextEvent, arg, 1 ) -- This schedules the next event, but only if scheduling is activated. -end - ---- @param #CARGO self -function CARGO:_Next( NextEvent, ... ) - self:F( self.Name ) - self.FsmP.NextEvent( self, unpack(arg) ) -- This calls the next event... -end - -end - -do -- CARGO_REPRESENTABLE - - --- @type CARGO_REPRESENTABLE - -- @extends #CARGO - CARGO_REPRESENTABLE = { - ClassName = "CARGO_REPRESENTABLE" - } - ---- CARGO_REPRESENTABLE Constructor. --- @param #CARGO_REPRESENTABLE self --- @param Mission#MISSION Mission --- @param Controllable#Controllable CargoObject --- @param #string Type --- @param #string Name --- @param #number Weight --- @param #number ReportRadius (optional) --- @param #number NearRadius (optional) --- @return #CARGO_REPRESENTABLE -function CARGO_REPRESENTABLE:New( Mission, CargoObject, Type, Name, Weight, ReportRadius, NearRadius ) - local self = BASE:Inherit( self, CARGO:New( Mission, Type, Name, Weight, ReportRadius, NearRadius ) ) -- #CARGO - self:F( { Type, Name, Weight, ReportRadius, NearRadius } ) - - - - - return self -end - - - -end - -do -- CARGO_UNIT - - --- @type CARGO_UNIT - -- @extends #CARGO_REPRESENTABLE - CARGO_UNIT = { - ClassName = "CARGO_UNIT" - } - ---- CARGO_UNIT Constructor. --- @param #CARGO_UNIT self --- @param Mission#MISSION Mission --- @param Unit#UNIT CargoUnit --- @param #string Type --- @param #string Name --- @param #number Weight --- @param #number ReportRadius (optional) --- @param #number NearRadius (optional) --- @return #CARGO_UNIT -function CARGO_UNIT:New( Mission, CargoUnit, Type, Name, Weight, ReportRadius, NearRadius ) - local self = BASE:Inherit( self, CARGO_REPRESENTABLE:New( Mission, CargoUnit, Type, Name, Weight, ReportRadius, NearRadius ) ) -- #CARGO - self:F( { Type, Name, Weight, ReportRadius, NearRadius } ) - - self:T( CargoUnit ) - self.CargoObject = CargoUnit - - self.FsmP = STATEMACHINE_PROCESS:New( self, { - initial = 'UnLoaded', - events = { - { name = 'Board', from = 'UnLoaded', to = 'Boarding' }, - { name = 'Load', from = 'Boarding', to = 'Loaded' }, - { name = 'UnLoad', from = 'Loaded', to = 'UnBoarding' }, - { name = 'UnBoard', from = 'UnBoarding', to = 'UnLoaded' }, - { name = 'Load', from = 'UnLoaded', to = 'Loaded' }, - }, - callbacks = { - onafterBoard = self.EventBoard, - onafterLoad = self.EventLoad, - onafterUnBoard = self.EventUnBoard, - onafterUnLoad = self.EventUnLoad, - onenterBoarding = self.EnterStateBoarding, - onleaveBoarding = self.LeaveStateBoarding, - onenterLoaded = self.EnterStateLoaded, - onenterUnBoarding = self.EnterStateUnBoarding, - onleaveUnBoarding = self.LeaveStateUnBoarding, - onenterUnLoaded = self.EnterStateUnLoaded, - }, - } ) - - self:T( self.ClassName ) - - return self -end - ---- Enter UnBoarding State. --- @param #CARGO_UNIT self --- @param StateMachine#STATEMACHINE_PROCESS FsmP --- @param #string Event --- @param #string From --- @param #string To --- @param Point#POINT_VEC2 ToPointVec2 -function CARGO_UNIT:EnterStateUnBoarding( FsmP, Event, From, To, ToPointVec2 ) - self:F() - - local Angle = 180 - local Speed = 10 - local DeployDistance = 5 - local RouteDistance = 60 - - if From == "Loaded" then - - local CargoCarrierPointVec2 = self.CargoCarrier:GetPointVec2() - local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - local CargoDeployPointVec2 = CargoCarrierPointVec2:Translate( DeployDistance, CargoDeployHeading ) - local CargoRoutePointVec2 = CargoCarrierPointVec2:Translate( RouteDistance, CargoDeployHeading ) - - if not ToPointVec2 then - ToPointVec2 = CargoRoutePointVec2 - end - - local FromPointVec2 = CargoCarrierPointVec2 - - -- Respawn the group... - if self.CargoObject then - self.CargoObject:ReSpawn( CargoDeployPointVec2:GetVec3(), CargoDeployHeading ) - self.CargoCarrier = nil - - local Points = {} - Points[#Points+1] = FromPointVec2:RoutePointGround( Speed ) - Points[#Points+1] = ToPointVec2:RoutePointGround( Speed ) - - local TaskRoute = self.CargoObject:TaskRoute( Points ) - self.CargoObject:SetTask( TaskRoute, 1 ) - - self:_NextEvent( FsmP.UnBoard, ToPointVec2 ) - end - end - -end - ---- Leave UnBoarding State. --- @param #CARGO_UNIT self --- @param StateMachine#STATEMACHINE_PROCESS FsmP --- @param #string Event --- @param #string From --- @param #string To --- @param Point#POINT_VEC2 ToPointVec2 -function CARGO_UNIT:LeaveStateUnBoarding( FsmP, Event, From, To, ToPointVec2 ) - self:F() - - local Angle = 180 - local Speed = 10 - local Distance = 5 - - if From == "UnBoarding" then - if self:IsNear( ToPointVec2 ) then - return true - else - self:_NextEvent( FsmP.UnBoard, ToPointVec2 ) - end - return false - end - -end - ---- Enter UnLoaded State. --- @param #CARGO_UNIT self --- @param StateMachine#STATEMACHINE_PROCESS FsmP --- @param #string Event --- @param #string From --- @param #string To -function CARGO_UNIT:EnterStateUnLoaded( FsmP, Event, From, To, ToPointVec2 ) - self:F() - - local Angle = 180 - local Speed = 10 - local Distance = 5 - - if From == "Loaded" then - local StartPointVec2 = self.CargoCarrier:GetPointVec2() - local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - local CargoDeployPointVec2 = StartPointVec2:Translate( Distance, CargoDeployHeading ) - - -- Respawn the group... - if self.CargoObject then - self.CargoObject:ReSpawn( ToPointVec2:GetVec3(), 0 ) - self.CargoCarrier = nil - end - - end - - if self.OnUnLoadedCallBack then - self.OnUnLoadedCallBack( self, unpack( self.OnUnLoadedParameters ) ) - self.OnUnLoadedCallBack = nil - end - -end - - - ---- Enter Boarding State. --- @param #CARGO_UNIT self --- @param StateMachine#STATEMACHINE_PROCESS FsmP --- @param #string Event --- @param #string From --- @param #string To --- @param Unit#UNIT CargoCarrier -function CARGO_UNIT:EnterStateBoarding( FsmP, Event, From, To, CargoCarrier ) - self:F() - - local Speed = 10 - local Angle = 180 - local Distance = 5 - - if From == "UnLoaded" then - local CargoCarrierPointVec2 = CargoCarrier:GetPointVec2() - local CargoCarrierHeading = CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - local CargoDeployPointVec2 = CargoCarrierPointVec2:Translate( Distance, CargoDeployHeading ) - - local Points = {} - - local PointStartVec2 = self.CargoObject:GetPointVec2() - - Points[#Points+1] = PointStartVec2:RoutePointGround( Speed ) - Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) - - local TaskRoute = self.CargoObject:TaskRoute( Points ) - self.CargoObject:SetTask( TaskRoute, 2 ) - end -end - ---- Leave Boarding State. --- @param #CARGO_UNIT self --- @param StateMachine#STATEMACHINE_PROCESS FsmP --- @param #string Event --- @param #string From --- @param #string To --- @param Unit#UNIT CargoCarrier -function CARGO_UNIT:LeaveStateBoarding( FsmP, Event, From, To, CargoCarrier ) - self:F() - - if self:IsNear( CargoCarrier:GetPointVec2() ) then - return true - else - self:_NextEvent( FsmP.Load, CargoCarrier ) - end - return false -end - ---- Loaded State. --- @param #CARGO_UNIT self --- @param StateMachine#STATEMACHINE_PROCESS FsmP --- @param #string Event --- @param #string From --- @param #string To --- @param Unit#UNIT CargoCarrier -function CARGO_UNIT:EnterStateLoaded( FsmP, Event, From, To, CargoCarrier ) - self:F() - - self.CargoCarrier = CargoCarrier - - -- Only destroy the CargoObject is if there is a CargoObject (packages don't have CargoObjects). - if self.CargoObject then - self.CargoObject:Destroy() - end - - if self.OnLoadedCallBack then - self.OnLoadedCallBack( self, unpack( self.OnLoadedParameters ) ) - self.OnLoadedCallBack = nil - end - -end - - ---- Board Event. --- @param #CARGO_UNIT self --- @param StateMachine#STATEMACHINE_PROCESS FsmP --- @param #string Event --- @param #string From --- @param #string To -function CARGO_UNIT:EventBoard( FsmP, Event, From, To, CargoCarrier ) - self:F() - - self.CargoInAir = self.CargoObject:InAir() - - self:T( self.CargoInAir ) - - -- Only move the group to the carrier when the cargo is not in the air - -- (eg. cargo can be on a oil derrick, moving the cargo on the oil derrick will drop the cargo on the sea). - if not self.CargoInAir then - self:_NextEvent( FsmP.Load, CargoCarrier ) - end - - -end - ---- UnBoard Event. --- @param #CARGO_UNIT self --- @param StateMachine#STATEMACHINE_PROCESS FsmP --- @param #string Event --- @param #string From --- @param #string To -function CARGO_UNIT:EventUnBoard( FsmP, Event, From, To ) - self:F() - - self.CargoInAir = self.CargoObject:InAir() - - self:T( self.CargoInAir ) - - -- Only unboard the cargo when the carrier is not in the air. - -- (eg. cargo can be on a oil derrick, moving the cargo on the oil derrick will drop the cargo on the sea). - if not self.CargoInAir then - - end - - self:_NextEvent( FsmP.UnLoad ) - -end - ---- Load Event. --- @param #CARGO_UNIT self --- @param StateMachine#STATEMACHINE_PROCESS FsmP --- @param #string Event --- @param #string From --- @param #string To --- @param Unit#UNIT CargoCarrier -function CARGO_UNIT:EventLoad( FsmP, Event, From, To, CargoCarrier ) - self:F() - - self:T( self.ClassName ) - -end - ---- UnLoad Event. --- @param #CARGO_UNIT self --- @param StateMachine#STATEMACHINE_PROCESS FsmP --- @param #string Event --- @param #string From --- @param #string To -function CARGO_UNIT:EventUnLoad( FsmP, Event, From, To ) - self:F() - -end - -end - -do -- CARGO_PACKAGE - - --- @type CARGO_PACKAGE - -- @extends #CARGO_REPRESENTABLE - CARGO_PACKAGE = { - ClassName = "CARGO_PACKAGE" - } - ---- CARGO_PACKAGE Constructor. --- @param #CARGO_PACKAGE self --- @param Mission#MISSION Mission --- @param Unit#UNIT CargoCarrier The UNIT carrying the package. --- @param #string Type --- @param #string Name --- @param #number Weight --- @param #number ReportRadius (optional) --- @param #number NearRadius (optional) --- @return #CARGO_PACKAGE -function CARGO_PACKAGE:New( Mission, CargoCarrier, Type, Name, Weight, ReportRadius, NearRadius ) - local self = BASE:Inherit( self, CARGO_REPRESENTABLE:New( Mission, CargoCarrier, Type, Name, Weight, ReportRadius, NearRadius ) ) -- #CARGO - self:F( { Type, Name, Weight, ReportRadius, NearRadius } ) - - self:T( CargoCarrier ) - self.CargoCarrier = CargoCarrier - - self.FsmP = STATEMACHINE_PROCESS:New( self, { - initial = 'UnLoaded', - events = { - { name = 'Board', from = 'UnLoaded', to = 'Boarding' }, - { name = 'Boarded', from = 'Boarding', to = 'Boarding' }, - { name = 'Load', from = 'Boarding', to = 'Loaded' }, - { name = 'Load', from = 'UnLoaded', to = 'Loaded' }, - { name = 'UnBoard', from = 'Loaded', to = 'UnBoarding' }, - { name = 'UnBoarded', from = 'UnBoarding', to = 'UnBoarding' }, - { name = 'UnLoad', from = 'UnBoarding', to = 'UnLoaded' }, - { name = 'UnLoad', from = 'Loaded', to = 'UnLoaded' }, - }, - callbacks = { - onBoard = self.OnBoard, - onBoarded = self.OnBoarded, - onLoad = self.OnLoad, - onUnBoard = self.OnUnBoard, - onUnBoarded = self.OnUnBoarded, - onUnLoad = self.OnUnLoad, - onLoaded = self.OnLoaded, - onUnLoaded = self.OnUnLoaded, - }, - } ) - - return self -end - ---- Board Event. --- @param #CARGO_PACKAGE self --- @param StateMachine#STATEMACHINE_PROCESS FsmP --- @param #string Event --- @param #string From --- @param #string To --- @param Unit#UNIT CargoCarrier --- @param #number Speed --- @param #number BoardDistance --- @param #number Angle -function CARGO_PACKAGE:OnBoard( FsmP, Event, From, To, CargoCarrier, Speed, BoardDistance, LoadDistance, Angle ) - self:F() - - self.CargoInAir = self.CargoCarrier:InAir() - - self:T( self.CargoInAir ) - - -- Only move the CargoCarrier to the New CargoCarrier when the New CargoCarrier is not in the air. - if not self.CargoInAir then - - local Points = {} - - local StartPointVec2 = self.CargoCarrier:GetPointVec2() - local CargoCarrierHeading = CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - self:T( { CargoCarrierHeading, CargoDeployHeading } ) - local CargoDeployPointVec2 = CargoCarrier:GetPointVec2():Translate( BoardDistance, CargoDeployHeading ) - - Points[#Points+1] = StartPointVec2:RoutePointGround( Speed ) - Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) - - local TaskRoute = self.CargoCarrier:TaskRoute( Points ) - self.CargoCarrier:SetTask( TaskRoute, 1 ) - end - - self:_NextEvent( FsmP.Boarded, CargoCarrier, Speed, BoardDistance, LoadDistance, Angle ) - -end - ---- Check if CargoCarrier is near the Cargo to be Loaded. --- @param #CARGO_PACKAGE self --- @param Unit#UNIT CargoCarrier --- @return #boolean -function CARGO_PACKAGE:IsNear( CargoCarrier ) - self:F() - - local CargoCarrierPoint = CargoCarrier:GetPointVec2() - - local Distance = CargoCarrierPoint:DistanceFromPointVec2( self.CargoCarrier:GetPointVec2() ) - self:T( Distance ) - - if Distance <= self.NearRadius then - return true - else - return false - end -end - ---- Boarded Event. --- @param #CARGO_PACKAGE self --- @param StateMachine#STATEMACHINE_PROCESS FsmP --- @param #string Event --- @param #string From --- @param #string To --- @param Unit#UNIT CargoCarrier -function CARGO_PACKAGE:OnBoarded( FsmP, Event, From, To, CargoCarrier, Speed, BoardDistance, LoadDistance, Angle ) - self:F() - - if self:IsNear( CargoCarrier ) then - self:_NextEvent( FsmP.Load, CargoCarrier, Speed, LoadDistance, Angle ) - else - self:_NextEvent( FsmP.Boarded, CargoCarrier, Speed, BoardDistance, LoadDistance, Angle ) - end -end - ---- UnBoard Event. --- @param #CARGO_PACKAGE self --- @param StateMachine#STATEMACHINE_PROCESS FsmP --- @param #string Event --- @param #string From --- @param #string To --- @param #number Speed --- @param #number UnLoadDistance --- @param #number UnBoardDistance --- @param #number Radius --- @param #number Angle -function CARGO_PACKAGE:OnUnBoard( FsmP, Event, From, To, CargoCarrier, Speed, UnLoadDistance, UnBoardDistance, Radius, Angle ) - self:F() - - self.CargoInAir = self.CargoCarrier:InAir() - - self:T( self.CargoInAir ) - - -- Only unboard the cargo when the carrier is not in the air. - -- (eg. cargo can be on a oil derrick, moving the cargo on the oil derrick will drop the cargo on the sea). - if not self.CargoInAir then - - self:_Next( self.FsmP.UnLoad, UnLoadDistance, Angle ) - - local Points = {} - - local StartPointVec2 = CargoCarrier:GetPointVec2() - local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - self:T( { CargoCarrierHeading, CargoDeployHeading } ) - local CargoDeployPointVec2 = StartPointVec2:Translate( UnBoardDistance, CargoDeployHeading ) - - Points[#Points+1] = StartPointVec2:RoutePointGround( Speed ) - Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) - - local TaskRoute = CargoCarrier:TaskRoute( Points ) - CargoCarrier:SetTask( TaskRoute, 1 ) - end - - self:_NextEvent( FsmP.UnBoarded, CargoCarrier, Speed ) - -end - ---- UnBoarded Event. --- @param #CARGO_PACKAGE self --- @param StateMachine#STATEMACHINE_PROCESS FsmP --- @param #string Event --- @param #string From --- @param #string To --- @param Unit#UNIT CargoCarrier -function CARGO_PACKAGE:OnUnBoarded( FsmP, Event, From, To, CargoCarrier, Speed ) - self:F() - - if self:IsNear( CargoCarrier ) then - self:_NextEvent( FsmP.UnLoad, CargoCarrier, Speed ) - else - self:_NextEvent( FsmP.UnBoarded, CargoCarrier, Speed ) - end -end - ---- Load Event. --- @param #CARGO_PACKAGE self --- @param StateMachine#STATEMACHINE_PROCESS FsmP --- @param #string Event --- @param #string From --- @param #string To --- @param Unit#UNIT CargoCarrier --- @param #number Speed --- @param #number LoadDistance --- @param #number Angle -function CARGO_PACKAGE:OnLoad( FsmP, Event, From, To, CargoCarrier, Speed, LoadDistance, Angle ) - self:F() - - self.CargoCarrier = CargoCarrier - - local StartPointVec2 = self.CargoCarrier:GetPointVec2() - local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - local CargoDeployPointVec2 = StartPointVec2:Translate( LoadDistance, CargoDeployHeading ) - - local Points = {} - Points[#Points+1] = StartPointVec2:RoutePointGround( Speed ) - Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) - - local TaskRoute = self.CargoCarrier:TaskRoute( Points ) - self.CargoCarrier:SetTask( TaskRoute, 1 ) - -end - ---- UnLoad Event. --- @param #CARGO_PACKAGE self --- @param StateMachine#STATEMACHINE_PROCESS FsmP --- @param #string Event --- @param #string From --- @param #string To --- @param #number Distance --- @param #number Angle -function CARGO_PACKAGE:OnUnLoad( FsmP, Event, From, To, CargoCarrier, Speed, Distance, Angle ) - self:F() - - local StartPointVec2 = self.CargoCarrier:GetPointVec2() - local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - local CargoDeployPointVec2 = StartPointVec2:Translate( Distance, CargoDeployHeading ) - - self.CargoCarrier = CargoCarrier - - local Points = {} - Points[#Points+1] = StartPointVec2:RoutePointGround( Speed ) - Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) - - local TaskRoute = self.CargoCarrier:TaskRoute( Points ) - self.CargoCarrier:SetTask( TaskRoute, 1 ) - -end - - -end - - - -CARGO_SLINGLOAD = { - ClassName = "CARGO_SLINGLOAD" -} - - -function CARGO_SLINGLOAD:New( CargoType, CargoName, CargoWeight, CargoZone, CargoHostName, CargoCountryID ) - local self = BASE:Inherit( self, CARGO:New( CargoType, CargoName, CargoWeight ) ) - self:F( { CargoType, CargoName, CargoWeight, CargoZone, CargoHostName, CargoCountryID } ) - - self.CargoHostName = CargoHostName - - -- Cargo will be initialized around the CargoZone position. - self.CargoZone = CargoZone - - self.CargoCount = 0 - self.CargoStaticName = string.format( "%s#%03d", self.CargoName, self.CargoCount ) - - -- The country ID needs to be correctly set. - self.CargoCountryID = CargoCountryID - - CARGOS[self.CargoName] = self - - return self - -end - - -function CARGO_SLINGLOAD:IsLandingRequired() - self:F() - return false -end - - -function CARGO_SLINGLOAD:IsSlingLoad() - self:F() - return true -end - - -function CARGO_SLINGLOAD:Spawn( Client ) - self:F( { self, Client } ) - - local Zone = trigger.misc.getZone( self.CargoZone ) - - local ZonePos = {} - ZonePos.x = Zone.point.x + math.random( Zone.radius / 2 * -1, Zone.radius / 2 ) - ZonePos.y = Zone.point.z + math.random( Zone.radius / 2 * -1, Zone.radius / 2 ) - - self:T( "Cargo Location = " .. ZonePos.x .. ", " .. ZonePos.y ) - - --[[ - - - - - - - - -- This does not work in 1.5.2. - - - - - - - - CargoStatic = StaticObject.getByName( self.CargoName ) - - - - - - - - if CargoStatic then - - - - - - - - CargoStatic:destroy() - - - - - - - - end - - - - - - - - --]] - - CargoStatic = StaticObject.getByName( self.CargoStaticName ) - - if CargoStatic and CargoStatic:isExist() then - CargoStatic:destroy() - end - - -- I need to make every time a new cargo due to bugs in 1.5.2. - - self.CargoCount = self.CargoCount + 1 - self.CargoStaticName = string.format( "%s#%03d", self.CargoName, self.CargoCount ) - - local CargoTemplate = { - ["category"] = "Cargo", - ["shape_name"] = "ab-212_cargo", - ["type"] = "Cargo1", - ["x"] = ZonePos.x, - ["y"] = ZonePos.y, - ["mass"] = self.CargoWeight, - ["name"] = self.CargoStaticName, - ["canCargo"] = true, - ["heading"] = 0, - } - - coalition.addStaticObject( self.CargoCountryID, CargoTemplate ) - - -- end - - return self -end - - -function CARGO_SLINGLOAD:IsNear( Client, LandingZone ) - self:F() - - local Near = false - - return Near -end - - -function CARGO_SLINGLOAD:IsInLandingZone( Client, LandingZone ) - self:F() - - local Near = false - - local CargoStaticUnit = StaticObject.getByName( self.CargoName ) - if CargoStaticUnit then - if routines.IsStaticInZones( CargoStaticUnit, LandingZone ) then - Near = true - end - end - - return Near -end - - -function CARGO_SLINGLOAD:OnBoard( Client, LandingZone, OnBoardSide ) - self:F() - - local Valid = true - - - return Valid -end - - -function CARGO_SLINGLOAD:OnBoarded( Client, LandingZone ) - self:F() - - local OnBoarded = false - - local CargoStaticUnit = StaticObject.getByName( self.CargoName ) - if CargoStaticUnit then - if not routines.IsStaticInZones( CargoStaticUnit, LandingZone ) then - OnBoarded = true - end - end - - return OnBoarded -end - - -function CARGO_SLINGLOAD:UnLoad( Client, TargetZoneName ) - self:F() - - self:T( 'self.CargoName = ' .. self.CargoName ) - self:T( 'self.CargoGroupName = ' .. self.CargoGroupName ) - - self:StatusUnLoaded() - - return Cargo -end - -CARGO_ZONE = { - ClassName="CARGO_ZONE", - CargoZoneName = '', - CargoHostUnitName = '', - SIGNAL = { - TYPE = { - SMOKE = { ID = 1, TEXT = "smoke" }, - FLARE = { ID = 2, TEXT = "flare" } - }, - COLOR = { - GREEN = { ID = 1, TRIGGERCOLOR = trigger.smokeColor.Green, TEXT = "A green" }, - RED = { ID = 2, TRIGGERCOLOR = trigger.smokeColor.Red, TEXT = "A red" }, - WHITE = { ID = 3, TRIGGERCOLOR = trigger.smokeColor.White, TEXT = "A white" }, - ORANGE = { ID = 4, TRIGGERCOLOR = trigger.smokeColor.Orange, TEXT = "An orange" }, - BLUE = { ID = 5, TRIGGERCOLOR = trigger.smokeColor.Blue, TEXT = "A blue" }, - YELLOW = { ID = 6, TRIGGERCOLOR = trigger.flareColor.Yellow, TEXT = "A yellow" } - } - } -} - ---- Creates a new zone where cargo can be collected or deployed. --- The zone functionality is useful to smoke or indicate routes for cargo pickups or deployments. --- Provide the zone name as declared in the mission file into the CargoZoneName in the :New method. --- An optional parameter is the CargoHostName, which is a Group declared with Late Activation switched on in the mission file. --- The CargoHostName is the "host" of the cargo zone: --- --- * It will smoke the zone position when a client is approaching the zone. --- * Depending on the cargo type, it will assist in the delivery of the cargo by driving to and from the client. --- --- @param #CARGO_ZONE self --- @param #string CargoZoneName The name of the zone as declared within the mission editor. --- @param #string CargoHostName The name of the Group "hosting" the zone. The Group MUST NOT be a static, and must be a "mobile" unit. -function CARGO_ZONE:New( CargoZoneName, CargoHostName ) local self = BASE:Inherit( self, ZONE:New( CargoZoneName ) ) - self:F( { CargoZoneName, CargoHostName } ) - - self.CargoZoneName = CargoZoneName - self.SignalHeight = 2 - --self.CargoZone = trigger.misc.getZone( CargoZoneName ) - - - if CargoHostName then - self.CargoHostName = CargoHostName - end - - self:T( self.CargoZoneName ) - - return self -end - -function CARGO_ZONE:Spawn() - self:F( self.CargoHostName ) - - if self.CargoHostName then -- Only spawn a host in the zone when there is one given as a parameter in the New function. - if self.CargoHostSpawn then - local CargoHostGroup = self.CargoHostSpawn:GetGroupFromIndex() - if CargoHostGroup and CargoHostGroup:IsAlive() then - else - self.CargoHostSpawn:ReSpawn( 1 ) - end - else - self:T( "Initialize CargoHostSpawn" ) - self.CargoHostSpawn = SPAWN:New( self.CargoHostName ):Limit( 1, 1 ) - self.CargoHostSpawn:ReSpawn( 1 ) - end - end - - return self -end - -function CARGO_ZONE:GetHostUnit() - self:F( self ) - - if self.CargoHostName then - - -- A Host has been given, signal the host - local CargoHostGroup = self.CargoHostSpawn:GetGroupFromIndex() - local CargoHostUnit - if CargoHostGroup and CargoHostGroup:IsAlive() then - CargoHostUnit = CargoHostGroup:GetUnit(1) - else - CargoHostUnit = StaticObject.getByName( self.CargoHostName ) - end - - return CargoHostUnit - end - - return nil -end - -function CARGO_ZONE:ReportCargosToClient( Client, CargoType ) - self:F() - - local SignalUnit = self:GetHostUnit() - - if SignalUnit then - - local SignalUnitTypeName = SignalUnit:getTypeName() - - local HostMessage = "" - - local IsCargo = false - for CargoID, Cargo in pairs( CARGOS ) do - if Cargo.CargoType == Task.CargoType then - if Cargo:IsStatusNone() then - HostMessage = HostMessage .. " - " .. Cargo.CargoName .. " - " .. Cargo.CargoType .. " (" .. Cargo.Weight .. "kg)" .. "\n" - IsCargo = true - end - end - end - - if not IsCargo then - HostMessage = "No Cargo Available." - end - - Client:Message( HostMessage, 20, SignalUnitTypeName .. ": Reporting Cargo", 10 ) - end -end - - -function CARGO_ZONE:Signal() - self:F() - - local Signalled = false - - if self.SignalType then - - if self.CargoHostName then - - -- A Host has been given, signal the host - - local SignalUnit = self:GetHostUnit() - - if SignalUnit then - - self:T( 'Signalling Unit' ) - local SignalVehicleVec3 = SignalUnit:GetVec3() - SignalVehicleVec3.y = SignalVehicleVec3.y + 2 - - if self.SignalType.ID == CARGO_ZONE.SIGNAL.TYPE.SMOKE.ID then - - trigger.action.smoke( SignalVehicleVec3, self.SignalColor.TRIGGERCOLOR ) - Signalled = true - - elseif self.SignalType.ID == CARGO_ZONE.SIGNAL.TYPE.FLARE.ID then - - trigger.action.signalFlare( SignalVehicleVec3, self.SignalColor.TRIGGERCOLOR , 0 ) - Signalled = false - - end - end - - else - - local ZoneVec3 = self:GetPointVec3( self.SignalHeight ) -- Get the zone position + the landheight + 2 meters - - if self.SignalType.ID == CARGO_ZONE.SIGNAL.TYPE.SMOKE.ID then - - trigger.action.smoke( ZoneVec3, self.SignalColor.TRIGGERCOLOR ) - Signalled = true - - elseif self.SignalType.ID == CARGO_ZONE.SIGNAL.TYPE.FLARE.ID then - trigger.action.signalFlare( ZoneVec3, self.SignalColor.TRIGGERCOLOR, 0 ) - Signalled = false - - end - end - end - - return Signalled - -end - -function CARGO_ZONE:WhiteSmoke( SignalHeight ) - self:F() - - self.SignalType = CARGO_ZONE.SIGNAL.TYPE.SMOKE - self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.WHITE - - if SignalHeight then - self.SignalHeight = SignalHeight - end - - return self -end - -function CARGO_ZONE:BlueSmoke( SignalHeight ) - self:F() - - self.SignalType = CARGO_ZONE.SIGNAL.TYPE.SMOKE - self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.BLUE - - if SignalHeight then - self.SignalHeight = SignalHeight - end - - return self -end - -function CARGO_ZONE:RedSmoke( SignalHeight ) - self:F() - - self.SignalType = CARGO_ZONE.SIGNAL.TYPE.SMOKE - self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.RED - - if SignalHeight then - self.SignalHeight = SignalHeight - end - - return self -end - -function CARGO_ZONE:OrangeSmoke( SignalHeight ) - self:F() - - self.SignalType = CARGO_ZONE.SIGNAL.TYPE.SMOKE - self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.ORANGE - - if SignalHeight then - self.SignalHeight = SignalHeight - end - - return self -end - -function CARGO_ZONE:GreenSmoke( SignalHeight ) - self:F() - - self.SignalType = CARGO_ZONE.SIGNAL.TYPE.SMOKE - self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.GREEN - - if SignalHeight then - self.SignalHeight = SignalHeight - end - - return self -end - - -function CARGO_ZONE:WhiteFlare( SignalHeight ) - self:F() - - self.SignalType = CARGO_ZONE.SIGNAL.TYPE.FLARE - self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.WHITE - - if SignalHeight then - self.SignalHeight = SignalHeight - end - - return self -end - -function CARGO_ZONE:RedFlare( SignalHeight ) - self:F() - - self.SignalType = CARGO_ZONE.SIGNAL.TYPE.FLARE - self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.RED - - if SignalHeight then - self.SignalHeight = SignalHeight - end - - return self -end - -function CARGO_ZONE:GreenFlare( SignalHeight ) - self:F() - - self.SignalType = CARGO_ZONE.SIGNAL.TYPE.FLARE - self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.GREEN - - if SignalHeight then - self.SignalHeight = SignalHeight - end - - return self -end - -function CARGO_ZONE:YellowFlare( SignalHeight ) - self:F() - - self.SignalType = CARGO_ZONE.SIGNAL.TYPE.FLARE - self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.YELLOW - - if SignalHeight then - self.SignalHeight = SignalHeight - end - - return self -end - - -function CARGO_ZONE:GetCargoHostUnit() - self:F( self ) - - if self.CargoHostSpawn then - local CargoHostGroup = self.CargoHostSpawn:GetGroupFromIndex(1) - if CargoHostGroup and CargoHostGroup:IsAlive() then - local CargoHostUnit = CargoHostGroup:GetUnit(1) - if CargoHostUnit and CargoHostUnit:IsAlive() then - return CargoHostUnit - end - end - end - - return nil -end - -function CARGO_ZONE:GetCargoZoneName() - self:F() - - return self.CargoZoneName -end - - - - - - - - ---- This module contains the MESSAGE class. --- --- 1) @{Message#MESSAGE} class, extends @{Base#BASE} --- ================================================= --- Message System to display Messages to Clients, Coalitions or All. --- Messages are shown on the display panel for an amount of seconds, and will then disappear. --- Messages can contain a category which is indicating the category of the message. --- --- 1.1) MESSAGE construction methods --- --------------------------------- --- Messages are created with @{Message#MESSAGE.New}. Note that when the MESSAGE object is created, no message is sent yet. --- To send messages, you need to use the To functions. --- --- 1.2) Send messages with MESSAGE To methods --- ------------------------------------------ --- Messages are sent to: --- --- * Clients with @{Message#MESSAGE.ToClient}. --- * Coalitions with @{Message#MESSAGE.ToCoalition}. --- * All Players with @{Message#MESSAGE.ToAll}. --- --- @module Message --- @author FlightControl - ---- The MESSAGE class --- @type MESSAGE --- @extends Base#BASE -MESSAGE = { - ClassName = "MESSAGE", - MessageCategory = 0, - MessageID = 0, -} - - ---- Creates a new MESSAGE object. Note that these MESSAGE objects are not yet displayed on the display panel. You must use the functions @{ToClient} or @{ToCoalition} or @{ToAll} to send these Messages to the respective recipients. --- @param self --- @param #string MessageText is the text of the Message. --- @param #number MessageDuration is a number in seconds of how long the MESSAGE should be shown on the display panel. --- @param #string MessageCategory (optional) is a string expressing the "category" of the Message. The category will be shown as the first text in the message followed by a ": ". --- @return #MESSAGE --- @usage --- -- Create a series of new Messages. --- -- MessageAll is meant to be sent to all players, for 25 seconds, and is classified as "Score". --- -- MessageRED is meant to be sent to the RED players only, for 10 seconds, and is classified as "End of Mission", with ID "Win". --- -- MessageClient1 is meant to be sent to a Client, for 25 seconds, and is classified as "Score", with ID "Score". --- -- MessageClient1 is meant to be sent to a Client, for 25 seconds, and is classified as "Score", with ID "Score". --- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", 25, "End of Mission" ) --- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", 25, "Penalty" ) --- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", 25, "Score" ) --- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", 25, "Score") -function MESSAGE:New( MessageText, MessageDuration, MessageCategory ) - local self = BASE:Inherit( self, BASE:New() ) - self:F( { MessageText, MessageDuration, MessageCategory } ) - - -- When no MessageCategory is given, we don't show it as a title... - if MessageCategory and MessageCategory ~= "" then - self.MessageCategory = MessageCategory .. ": " - else - self.MessageCategory = "" - end - - self.MessageDuration = MessageDuration or 5 - self.MessageTime = timer.getTime() - self.MessageText = MessageText - - self.MessageSent = false - self.MessageGroup = false - self.MessageCoalition = false - - return self -end - ---- Sends a MESSAGE to a Client Group. Note that the Group needs to be defined within the ME with the skillset "Client" or "Player". --- @param #MESSAGE self --- @param Client#CLIENT Client is the Group of the Client. --- @return #MESSAGE --- @usage --- -- Send the 2 messages created with the @{New} method to the Client Group. --- -- Note that the Message of MessageClient2 is overwriting the Message of MessageClient1. --- ClientGroup = Group.getByName( "ClientGroup" ) --- --- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ):ToClient( ClientGroup ) --- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ):ToClient( ClientGroup ) --- or --- MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ):ToClient( ClientGroup ) --- MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ):ToClient( ClientGroup ) --- or --- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ) --- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ) --- MessageClient1:ToClient( ClientGroup ) --- MessageClient2:ToClient( ClientGroup ) -function MESSAGE:ToClient( Client ) - self:F( Client ) - - if Client and Client:GetClientGroupID() then - - local ClientGroupID = Client:GetClientGroupID() - self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration ) - trigger.action.outTextForGroup( ClientGroupID, self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration ) - end - - return self -end - ---- Sends a MESSAGE to a Group. --- @param #MESSAGE self --- @param Group#GROUP Group is the Group. --- @return #MESSAGE -function MESSAGE:ToGroup( Group ) - self:F( Group.GroupName ) - - if Group then - - self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration ) - trigger.action.outTextForGroup( Group:GetID(), self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration ) - end - - return self -end ---- Sends a MESSAGE to the Blue coalition. --- @param #MESSAGE self --- @return #MESSAGE --- @usage --- -- Send a message created with the @{New} method to the BLUE coalition. --- MessageBLUE = MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToBlue() --- or --- MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToBlue() --- or --- MessageBLUE = MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ) --- MessageBLUE:ToBlue() -function MESSAGE:ToBlue() - self:F() - - self:ToCoalition( coalition.side.BLUE ) - - return self -end - ---- Sends a MESSAGE to the Red Coalition. --- @param #MESSAGE self --- @return #MESSAGE --- @usage --- -- Send a message created with the @{New} method to the RED coalition. --- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToRed() --- or --- MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToRed() --- or --- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ) --- MessageRED:ToRed() -function MESSAGE:ToRed( ) - self:F() - - self:ToCoalition( coalition.side.RED ) - - return self -end - ---- Sends a MESSAGE to a Coalition. --- @param #MESSAGE self --- @param CoalitionSide needs to be filled out by the defined structure of the standard scripting engine @{coalition.side}. --- @return #MESSAGE --- @usage --- -- Send a message created with the @{New} method to the RED coalition. --- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToCoalition( coalition.side.RED ) --- or --- MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToCoalition( coalition.side.RED ) --- or --- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ) --- MessageRED:ToCoalition( coalition.side.RED ) -function MESSAGE:ToCoalition( CoalitionSide ) - self:F( CoalitionSide ) - - if CoalitionSide then - self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration ) - trigger.action.outTextForCoalition( CoalitionSide, self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration ) - end - - return self -end - ---- Sends a MESSAGE to all players. --- @param #MESSAGE self --- @return #MESSAGE --- @usage --- -- Send a message created to all players. --- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ):ToAll() --- or --- MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ):ToAll() --- or --- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ) --- MessageAll:ToAll() -function MESSAGE:ToAll() - self:F() - - self:ToCoalition( coalition.side.RED ) - self:ToCoalition( coalition.side.BLUE ) - - return self -end - - - ------ The MESSAGEQUEUE class ----- @type MESSAGEQUEUE ---MESSAGEQUEUE = { --- ClientGroups = {}, --- CoalitionSides = {} ---} --- ---function MESSAGEQUEUE:New( RefreshInterval ) --- local self = BASE:Inherit( self, BASE:New() ) --- self:F( { RefreshInterval } ) --- --- self.RefreshInterval = RefreshInterval --- --- --self.DisplayFunction = routines.scheduleFunction( self._DisplayMessages, { self }, 0, RefreshInterval ) --- self.DisplayFunction = SCHEDULER:New( self, self._DisplayMessages, {}, 0, RefreshInterval ) --- --- return self ---end --- ------ This function is called automatically by the MESSAGEQUEUE scheduler. ---function MESSAGEQUEUE:_DisplayMessages() --- --- -- First we display all messages that a coalition needs to receive... Also those who are not in a client (CA module clients...). --- for CoalitionSideID, CoalitionSideData in pairs( self.CoalitionSides ) do --- for MessageID, MessageData in pairs( CoalitionSideData.Messages ) do --- if MessageData.MessageSent == false then --- --trigger.action.outTextForCoalition( CoalitionSideID, MessageData.MessageCategory .. '\n' .. MessageData.MessageText:gsub("\n$",""):gsub("\n$",""), MessageData.MessageDuration ) --- MessageData.MessageSent = true --- end --- local MessageTimeLeft = ( MessageData.MessageTime + MessageData.MessageDuration ) - timer.getTime() --- if MessageTimeLeft <= 0 then --- MessageData = nil --- end --- end --- end --- --- -- Then we send the messages for each individual client, but also to be included are those Coalition messages for the Clients who belong to a coalition. --- -- Because the Client messages will overwrite the Coalition messages (for that Client). --- for ClientGroupName, ClientGroupData in pairs( self.ClientGroups ) do --- for MessageID, MessageData in pairs( ClientGroupData.Messages ) do --- if MessageData.MessageGroup == false then --- trigger.action.outTextForGroup( Group.getByName(ClientGroupName):getID(), MessageData.MessageCategory .. '\n' .. MessageData.MessageText:gsub("\n$",""):gsub("\n$",""), MessageData.MessageDuration ) --- MessageData.MessageGroup = true --- end --- local MessageTimeLeft = ( MessageData.MessageTime + MessageData.MessageDuration ) - timer.getTime() --- if MessageTimeLeft <= 0 then --- MessageData = nil --- end --- end --- --- -- Now check if the Client also has messages that belong to the Coalition of the Client... --- for CoalitionSideID, CoalitionSideData in pairs( self.CoalitionSides ) do --- for MessageID, MessageData in pairs( CoalitionSideData.Messages ) do --- local CoalitionGroup = Group.getByName( ClientGroupName ) --- if CoalitionGroup and CoalitionGroup:getCoalition() == CoalitionSideID then --- if MessageData.MessageCoalition == false then --- trigger.action.outTextForGroup( Group.getByName(ClientGroupName):getID(), MessageData.MessageCategory .. '\n' .. MessageData.MessageText:gsub("\n$",""):gsub("\n$",""), MessageData.MessageDuration ) --- MessageData.MessageCoalition = true --- end --- end --- local MessageTimeLeft = ( MessageData.MessageTime + MessageData.MessageDuration ) - timer.getTime() --- if MessageTimeLeft <= 0 then --- MessageData = nil --- end --- end --- end --- end --- --- return true ---end --- ------ The _MessageQueue object is created when the MESSAGE class module is loaded. -----_MessageQueue = MESSAGEQUEUE:New( 0.5 ) --- ---- Stages within a @{TASK} within a @{MISSION}. All of the STAGE functionality is considered internally administered and not to be used by any Mission designer. --- @module STAGE --- @author Flightcontrol - - - - - - - ---- The STAGE class --- @type -STAGE = { - ClassName = "STAGE", - MSG = { ID = "None", TIME = 10 }, - FREQUENCY = { NONE = 0, ONCE = 1, REPEAT = -1 }, - - Name = "NoStage", - StageType = '', - WaitTime = 1, - Frequency = 1, - MessageCount = 0, - MessageInterval = 15, - MessageShown = {}, - MessageShow = false, - MessageFlash = false -} - - -function STAGE:New() - local self = BASE:Inherit( self, BASE:New() ) - self:F() - return self -end - -function STAGE:Execute( Mission, Client, Task ) - - local Valid = true - - return Valid -end - -function STAGE:Executing( Mission, Client, Task ) - -end - -function STAGE:Validate( Mission, Client, Task ) - local Valid = true - - return Valid -end - - -STAGEBRIEF = { - ClassName = "BRIEF", - MSG = { ID = "Brief", TIME = 1 }, - Name = "Brief", - StageBriefingTime = 0, - StageBriefingDuration = 1 -} - -function STAGEBRIEF:New() - local self = BASE:Inherit( self, STAGE:New() ) - self:F() - self.StageType = 'CLIENT' - return self -end - ---- Execute --- @param #STAGEBRIEF self --- @param Mission#MISSION Mission --- @param Client#CLIENT Client --- @param Task#TASK Task --- @return #boolean -function STAGEBRIEF:Execute( Mission, Client, Task ) - local Valid = BASE:Inherited(self):Execute( Mission, Client, Task ) - self:F() - Client:ShowMissionBriefing( Mission.MissionBriefing ) - self.StageBriefingTime = timer.getTime() - return Valid -end - -function STAGEBRIEF:Validate( Mission, Client, Task ) - local Valid = STAGE:Validate( Mission, Client, Task ) - self:T() - - if timer.getTime() - self.StageBriefingTime <= self.StageBriefingDuration then - return 0 - else - self.StageBriefingTime = timer.getTime() - return 1 - end - -end - - -STAGESTART = { - ClassName = "START", - MSG = { ID = "Start", TIME = 1 }, - Name = "Start", - StageStartTime = 0, - StageStartDuration = 1 -} - -function STAGESTART:New() - local self = BASE:Inherit( self, STAGE:New() ) - self:F() - self.StageType = 'CLIENT' - return self -end - -function STAGESTART:Execute( Mission, Client, Task ) - self:F() - local Valid = BASE:Inherited(self):Execute( Mission, Client, Task ) - if Task.TaskBriefing then - Client:Message( Task.TaskBriefing, 30, "Command" ) - else - Client:Message( 'Task ' .. Task.TaskNumber .. '.', 30, "Command" ) - end - self.StageStartTime = timer.getTime() - return Valid -end - -function STAGESTART:Validate( Mission, Client, Task ) - self:F() - local Valid = STAGE:Validate( Mission, Client, Task ) - - if timer.getTime() - self.StageStartTime <= self.StageStartDuration then - return 0 - else - self.StageStartTime = timer.getTime() - return 1 - end - - return 1 - -end - -STAGE_CARGO_LOAD = { - ClassName = "STAGE_CARGO_LOAD" -} - -function STAGE_CARGO_LOAD:New() - local self = BASE:Inherit( self, STAGE:New() ) - self:F() - self.StageType = 'CLIENT' - return self -end - -function STAGE_CARGO_LOAD:Execute( Mission, Client, Task ) - self:F() - local Valid = BASE:Inherited(self):Execute( Mission, Client, Task ) - - for LoadCargoID, LoadCargo in pairs( Task.Cargos.LoadCargos ) do - LoadCargo:Load( Client ) - end - - if Mission.MissionReportFlash and Client:IsTransport() then - Client:ShowCargo() - end - - return Valid -end - -function STAGE_CARGO_LOAD:Validate( Mission, Client, Task ) - self:F() - local Valid = STAGE:Validate( Mission, Client, Task ) - - return 1 -end - - -STAGE_CARGO_INIT = { - ClassName = "STAGE_CARGO_INIT" -} - -function STAGE_CARGO_INIT:New() - local self = BASE:Inherit( self, STAGE:New() ) - self:F() - self.StageType = 'CLIENT' - return self -end - -function STAGE_CARGO_INIT:Execute( Mission, Client, Task ) - self:F() - local Valid = BASE:Inherited(self):Execute( Mission, Client, Task ) - - for InitLandingZoneID, InitLandingZone in pairs( Task.LandingZones.LandingZones ) do - self:T( InitLandingZone ) - InitLandingZone:Spawn() - end - - - self:T( Task.Cargos.InitCargos ) - for InitCargoID, InitCargoData in pairs( Task.Cargos.InitCargos ) do - self:T( { InitCargoData } ) - InitCargoData:Spawn( Client ) - end - - return Valid -end - - -function STAGE_CARGO_INIT:Validate( Mission, Client, Task ) - self:F() - local Valid = STAGE:Validate( Mission, Client, Task ) - - return 1 -end - - - -STAGEROUTE = { - ClassName = "STAGEROUTE", - MSG = { ID = "Route", TIME = 5 }, - Frequency = STAGE.FREQUENCY.REPEAT, - Name = "Route" -} - -function STAGEROUTE:New() - local self = BASE:Inherit( self, STAGE:New() ) - self:F() - self.StageType = 'CLIENT' - self.MessageSwitch = true - return self -end - - ---- Execute the routing. --- @param #STAGEROUTE self --- @param Mission#MISSION Mission --- @param Client#CLIENT Client --- @param Task#TASK Task -function STAGEROUTE:Execute( Mission, Client, Task ) - self:F() - local Valid = BASE:Inherited(self):Execute( Mission, Client, Task ) - - local RouteMessage = "Fly to: " - self:T( Task.LandingZones ) - for LandingZoneID, LandingZoneName in pairs( Task.LandingZones.LandingZoneNames ) do - RouteMessage = RouteMessage .. "\n " .. LandingZoneName .. ' at ' .. routines.getBRStringZone( { zone = LandingZoneName, ref = Client:GetClientGroupDCSUnit():getPoint(), true, true } ) .. ' km.' - end - - if Client:IsMultiSeated() then - Client:Message( RouteMessage, self.MSG.TIME, "Co-Pilot", 20, "Route" ) - else - Client:Message( RouteMessage, self.MSG.TIME, "Command", 20, "Route" ) - end - - - if Mission.MissionReportFlash and Client:IsTransport() then - Client:ShowCargo() - end - - return Valid -end - -function STAGEROUTE:Validate( Mission, Client, Task ) - self:F() - local Valid = STAGE:Validate( Mission, Client, Task ) - - -- check if the Client is in the landing zone - self:T( Task.LandingZones.LandingZoneNames ) - Task.CurrentLandingZoneName = routines.IsUnitNearZonesRadius( Client:GetClientGroupDCSUnit(), Task.LandingZones.LandingZoneNames, 500 ) - - if Task.CurrentLandingZoneName then - - Task.CurrentLandingZone = Task.LandingZones.LandingZones[Task.CurrentLandingZoneName].CargoZone - Task.CurrentCargoZone = Task.LandingZones.LandingZones[Task.CurrentLandingZoneName] - - if Task.CurrentCargoZone then - if not Task.Signalled then - Task.Signalled = Task.CurrentCargoZone:Signal() - end - end - - self:T( 1 ) - return 1 - end - - self:T( 0 ) - return 0 -end - - - -STAGELANDING = { - ClassName = "STAGELANDING", - MSG = { ID = "Landing", TIME = 10 }, - Name = "Landing", - Signalled = false -} - -function STAGELANDING:New() - local self = BASE:Inherit( self, STAGE:New() ) - self:F() - self.StageType = 'CLIENT' - return self -end - ---- Execute the landing coordination. --- @param #STAGELANDING self --- @param Mission#MISSION Mission --- @param Client#CLIENT Client --- @param Task#TASK Task -function STAGELANDING:Execute( Mission, Client, Task ) - self:F() - - if Client:IsMultiSeated() then - Client:Message( "We have arrived at the landing zone.", self.MSG.TIME, "Co-Pilot" ) - else - Client:Message( "You have arrived at the landing zone.", self.MSG.TIME, "Command" ) - end - - Task.HostUnit = Task.CurrentCargoZone:GetHostUnit() - - self:T( { Task.HostUnit } ) - - if Task.HostUnit then - - Task.HostUnitName = Task.HostUnit:GetPrefix() - Task.HostUnitTypeName = Task.HostUnit:GetTypeName() - - local HostMessage = "" - Task.CargoNames = "" - - local IsFirst = true - - for CargoID, Cargo in pairs( CARGOS ) do - if Cargo.CargoType == Task.CargoType then - - if Cargo:IsLandingRequired() then - self:T( "Task for cargo " .. Cargo.CargoType .. " requires landing.") - Task.IsLandingRequired = true - end - - if Cargo:IsSlingLoad() then - self:T( "Task for cargo " .. Cargo.CargoType .. " is a slingload.") - Task.IsSlingLoad = true - end - - if IsFirst then - IsFirst = false - Task.CargoNames = Task.CargoNames .. Cargo.CargoName .. "( " .. Cargo.CargoWeight .. " )" - else - Task.CargoNames = Task.CargoNames .. "; " .. Cargo.CargoName .. "( " .. Cargo.CargoWeight .. " )" - end - end - end - - if Task.IsLandingRequired then - HostMessage = "Land the helicopter to " .. Task.TEXT[1] .. " " .. Task.CargoNames .. "." - else - HostMessage = "Use the Radio menu and F6 to find the cargo, then fly or land near the cargo and " .. Task.TEXT[1] .. " " .. Task.CargoNames .. "." - end - - local Host = "Command" - if Task.HostUnitName then - Host = Task.HostUnitName .. " (" .. Task.HostUnitTypeName .. ")" - else - if Client:IsMultiSeated() then - Host = "Co-Pilot" - end - end - - Client:Message( HostMessage, self.MSG.TIME, Host ) - - end -end - -function STAGELANDING:Validate( Mission, Client, Task ) - self:F() - - Task.CurrentLandingZoneName = routines.IsUnitNearZonesRadius( Client:GetClientGroupDCSUnit(), Task.LandingZones.LandingZoneNames, 500 ) - if Task.CurrentLandingZoneName then - - -- Client is in de landing zone. - self:T( Task.CurrentLandingZoneName ) - - Task.CurrentLandingZone = Task.LandingZones.LandingZones[Task.CurrentLandingZoneName].CargoZone - Task.CurrentCargoZone = Task.LandingZones.LandingZones[Task.CurrentLandingZoneName] - - if Task.CurrentCargoZone then - if not Task.Signalled then - Task.Signalled = Task.CurrentCargoZone:Signal() - end - end - else - if Task.CurrentLandingZone then - Task.CurrentLandingZone = nil - end - if Task.CurrentCargoZone then - Task.CurrentCargoZone = nil - end - Task.Signalled = false - Task:RemoveCargoMenus( Client ) - self:T( -1 ) - return -1 - end - - - local DCSUnitVelocityVec3 = Client:GetClientGroupDCSUnit():getVelocity() - local DCSUnitVelocity = ( DCSUnitVelocityVec3.x ^2 + DCSUnitVelocityVec3.y ^2 + DCSUnitVelocityVec3.z ^2 ) ^ 0.5 - - local DCSUnitPointVec3 = Client:GetClientGroupDCSUnit():getPoint() - local LandHeight = land.getHeight( { x = DCSUnitPointVec3.x, y = DCSUnitPointVec3.z } ) - local DCSUnitHeight = DCSUnitPointVec3.y - LandHeight - - self:T( { Task.IsLandingRequired, Client:GetClientGroupDCSUnit():inAir() } ) - if Task.IsLandingRequired and not Client:GetClientGroupDCSUnit():inAir() then - self:T( 1 ) - Task.IsInAirTestRequired = true - return 1 - end - - self:T( { DCSUnitVelocity, DCSUnitHeight, LandHeight, Task.CurrentCargoZone.SignalHeight } ) - if Task.IsLandingRequired and DCSUnitVelocity <= 0.05 and DCSUnitHeight <= Task.CurrentCargoZone.SignalHeight then - self:T( 1 ) - Task.IsInAirTestRequired = false - return 1 - end - - self:T( 0 ) - return 0 -end - -STAGELANDED = { - ClassName = "STAGELANDED", - MSG = { ID = "Land", TIME = 10 }, - Name = "Landed", - MenusAdded = false -} - -function STAGELANDED:New() - local self = BASE:Inherit( self, STAGE:New() ) - self:F() - self.StageType = 'CLIENT' - return self -end - -function STAGELANDED:Execute( Mission, Client, Task ) - self:F() - - if Task.IsLandingRequired then - - local Host = "Command" - if Task.HostUnitName then - Host = Task.HostUnitName .. " (" .. Task.HostUnitTypeName .. ")" - else - if Client:IsMultiSeated() then - Host = "Co-Pilot" - end - end - - Client:Message( 'You have landed within the landing zone. Use the radio menu (F10) to ' .. Task.TEXT[1] .. ' the ' .. Task.CargoType .. '.', - self.MSG.TIME, Host ) - - if not self.MenusAdded then - Task.Cargo = nil - Task:RemoveCargoMenus( Client ) - Task:AddCargoMenus( Client, CARGOS, 250 ) - end - end -end - - - -function STAGELANDED:Validate( Mission, Client, Task ) - self:F() - - if not routines.IsUnitNearZonesRadius( Client:GetClientGroupDCSUnit(), Task.CurrentLandingZoneName, 500 ) then - self:T( "Client is not anymore in the landing zone, go back to stage Route, and remove cargo menus." ) - Task.Signalled = false - Task:RemoveCargoMenus( Client ) - self:T( -2 ) - return -2 - end - - local DCSUnitVelocityVec3 = Client:GetClientGroupDCSUnit():getVelocity() - local DCSUnitVelocity = ( DCSUnitVelocityVec3.x ^2 + DCSUnitVelocityVec3.y ^2 + DCSUnitVelocityVec3.z ^2 ) ^ 0.5 - - local DCSUnitPointVec3 = Client:GetClientGroupDCSUnit():getPoint() - local LandHeight = land.getHeight( { x = DCSUnitPointVec3.x, y = DCSUnitPointVec3.z } ) - local DCSUnitHeight = DCSUnitPointVec3.y - LandHeight - - self:T( { Task.IsLandingRequired, Client:GetClientGroupDCSUnit():inAir() } ) - if Task.IsLandingRequired and Task.IsInAirTestRequired == true and Client:GetClientGroupDCSUnit():inAir() then - self:T( "Client went back in the air. Go back to stage Landing." ) - self:T( -1 ) - return -1 - end - - self:T( { DCSUnitVelocity, DCSUnitHeight, LandHeight, Task.CurrentCargoZone.SignalHeight } ) - if Task.IsLandingRequired and Task.IsInAirTestRequired == false and DCSUnitVelocity >= 2 and DCSUnitHeight >= Task.CurrentCargoZone.SignalHeight then - self:T( "It seems the Client went back in the air and over the boundary limits. Go back to stage Landing." ) - self:T( -1 ) - return -1 - end - - -- Wait until cargo is selected from the menu. - if Task.IsLandingRequired then - if not Task.Cargo then - self:T( 0 ) - return 0 - end - end - - self:T( 1 ) - return 1 -end - -STAGEUNLOAD = { - ClassName = "STAGEUNLOAD", - MSG = { ID = "Unload", TIME = 10 }, - Name = "Unload" -} - -function STAGEUNLOAD:New() - local self = BASE:Inherit( self, STAGE:New() ) - self:F() - self.StageType = 'CLIENT' - return self -end - ---- Coordinate UnLoading --- @param #STAGEUNLOAD self --- @param Mission#MISSION Mission --- @param Client#CLIENT Client --- @param Task#TASK Task -function STAGEUNLOAD:Execute( Mission, Client, Task ) - self:F() - - if Client:IsMultiSeated() then - Client:Message( 'The ' .. Task.CargoType .. ' are being ' .. Task.TEXT[2] .. ' within the landing zone. Wait until the helicopter is ' .. Task.TEXT[3] .. '.', - "Co-Pilot" ) - else - Client:Message( 'You are unloading the ' .. Task.CargoType .. ' ' .. Task.TEXT[2] .. ' within the landing zone. Wait until the helicopter is ' .. Task.TEXT[3] .. '.', - "Command" ) - end - Task:RemoveCargoMenus( Client ) -end - -function STAGEUNLOAD:Executing( Mission, Client, Task ) - self:F() - env.info( 'STAGEUNLOAD:Executing() Task.Cargo.CargoName = ' .. Task.Cargo.CargoName ) - - local TargetZoneName - - if Task.TargetZoneName then - TargetZoneName = Task.TargetZoneName - else - TargetZoneName = Task.CurrentLandingZoneName - end - - if Task.Cargo:UnLoad( Client, TargetZoneName ) then - Task.ExecuteStage = _TransportExecuteStage.SUCCESS - if Mission.MissionReportFlash then - Client:ShowCargo() - end - end -end - ---- Validate UnLoading --- @param #STAGEUNLOAD self --- @param Mission#MISSION Mission --- @param Client#CLIENT Client --- @param Task#TASK Task -function STAGEUNLOAD:Validate( Mission, Client, Task ) - self:F() - env.info( 'STAGEUNLOAD:Validate()' ) - - if routines.IsUnitNearZonesRadius( Client:GetClientGroupDCSUnit(), Task.CurrentLandingZoneName, 500 ) then - else - Task.ExecuteStage = _TransportExecuteStage.FAILED - Task:RemoveCargoMenus( Client ) - if Client:IsMultiSeated() then - Client:Message( 'The ' .. Task.CargoType .. " haven't been successfully " .. Task.TEXT[3] .. ' within the landing zone. Task and mission has failed.', - _TransportStageMsgTime.DONE, "Co-Pilot" ) - else - Client:Message( 'The ' .. Task.CargoType .. " haven't been successfully " .. Task.TEXT[3] .. ' within the landing zone. Task and mission has failed.', - _TransportStageMsgTime.DONE, "Command" ) - end - return 1 - end - - if not Client:GetClientGroupDCSUnit():inAir() then - else - Task.ExecuteStage = _TransportExecuteStage.FAILED - Task:RemoveCargoMenus( Client ) - if Client:IsMultiSeated() then - Client:Message( 'The ' .. Task.CargoType .. " haven't been successfully " .. Task.TEXT[3] .. ' within the landing zone. Task and mission has failed.', - _TransportStageMsgTime.DONE, "Co-Pilot" ) - else - Client:Message( 'The ' .. Task.CargoType .. " haven't been successfully " .. Task.TEXT[3] .. ' within the landing zone. Task and mission has failed.', - _TransportStageMsgTime.DONE, "Command" ) - end - return 1 - end - - if Task.ExecuteStage == _TransportExecuteStage.SUCCESS then - if Client:IsMultiSeated() then - Client:Message( 'The ' .. Task.CargoType .. ' have been sucessfully ' .. Task.TEXT[3] .. ' within the landing zone.', _TransportStageMsgTime.DONE, "Co-Pilot" ) - else - Client:Message( 'The ' .. Task.CargoType .. ' have been sucessfully ' .. Task.TEXT[3] .. ' within the landing zone.', _TransportStageMsgTime.DONE, "Command" ) - end - Task:RemoveCargoMenus( Client ) - Task.MissionTask:AddGoalCompletion( Task.MissionTask.GoalVerb, Task.CargoName, 1 ) -- We set the cargo as one more goal completed in the mission. - return 1 - end - - return 1 -end - -STAGELOAD = { - ClassName = "STAGELOAD", - MSG = { ID = "Load", TIME = 10 }, - Name = "Load" -} - -function STAGELOAD:New() - local self = BASE:Inherit( self, STAGE:New() ) - self:F() - self.StageType = 'CLIENT' - return self -end - -function STAGELOAD:Execute( Mission, Client, Task ) - self:F() - - if not Task.IsSlingLoad then - - local Host = "Command" - if Task.HostUnitName then - Host = Task.HostUnitName .. " (" .. Task.HostUnitTypeName .. ")" - else - if Client:IsMultiSeated() then - Host = "Co-Pilot" - end - end - - Client:Message( 'The ' .. Task.CargoType .. ' are being ' .. Task.TEXT[2] .. ' within the landing zone. Wait until the helicopter is ' .. Task.TEXT[3] .. '.', - _TransportStageMsgTime.EXECUTING, Host ) - - -- Route the cargo to the Carrier - - Task.Cargo:OnBoard( Client, Task.CurrentCargoZone, Task.OnBoardSide ) - Task.ExecuteStage = _TransportExecuteStage.EXECUTING - else - Task.ExecuteStage = _TransportExecuteStage.EXECUTING - end -end - -function STAGELOAD:Executing( Mission, Client, Task ) - self:F() - - -- If the Cargo is ready to be loaded, load it into the Client. - - local Host = "Command" - if Task.HostUnitName then - Host = Task.HostUnitName .. " (" .. Task.HostUnitTypeName .. ")" - else - if Client:IsMultiSeated() then - Host = "Co-Pilot" - end - end - - if not Task.IsSlingLoad then - self:T( Task.Cargo.CargoName) - - if Task.Cargo:OnBoarded( Client, Task.CurrentCargoZone ) then - - -- Load the Cargo onto the Client - Task.Cargo:Load( Client ) - - -- Message to the pilot that cargo has been loaded. - Client:Message( "The cargo " .. Task.Cargo.CargoName .. " has been loaded in our helicopter.", - 20, Host ) - Task.ExecuteStage = _TransportExecuteStage.SUCCESS - - Client:ShowCargo() - end - else - Client:Message( "Hook the " .. Task.CargoNames .. " onto the helicopter " .. Task.TEXT[3] .. " within the landing zone.", - _TransportStageMsgTime.EXECUTING, Host ) - for CargoID, Cargo in pairs( CARGOS ) do - self:T( "Cargo.CargoName = " .. Cargo.CargoName ) - - if Cargo:IsSlingLoad() then - local CargoStatic = StaticObject.getByName( Cargo.CargoStaticName ) - if CargoStatic then - self:T( "Cargo is found in the DCS simulator.") - local CargoStaticPosition = CargoStatic:getPosition().p - self:T( "Cargo Position x = " .. CargoStaticPosition.x .. ", y = " .. CargoStaticPosition.y .. ", z = " .. CargoStaticPosition.z ) - local CargoStaticHeight = routines.GetUnitHeight( CargoStatic ) - if CargoStaticHeight > 5 then - self:T( "Cargo is airborne.") - Cargo:StatusLoaded() - Task.Cargo = Cargo - Client:Message( 'The Cargo has been successfully hooked onto the helicopter and is now being sling loaded. Fly outside the landing zone.', - self.MSG.TIME, Host ) - Task.ExecuteStage = _TransportExecuteStage.SUCCESS - break - end - else - self:T( "Cargo not found in the DCS simulator." ) - end - end - end - end - -end - -function STAGELOAD:Validate( Mission, Client, Task ) - self:F() - - self:T( "Task.CurrentLandingZoneName = " .. Task.CurrentLandingZoneName ) - - local Host = "Command" - if Task.HostUnitName then - Host = Task.HostUnitName .. " (" .. Task.HostUnitTypeName .. ")" - else - if Client:IsMultiSeated() then - Host = "Co-Pilot" - end - end - - if not Task.IsSlingLoad then - if not routines.IsUnitNearZonesRadius( Client:GetClientGroupDCSUnit(), Task.CurrentLandingZoneName, 500 ) then - Task:RemoveCargoMenus( Client ) - Task.ExecuteStage = _TransportExecuteStage.FAILED - Task.CargoName = nil - Client:Message( "The " .. Task.CargoType .. " loading has been aborted. You flew outside the pick-up zone while loading. ", - self.MSG.TIME, Host ) - self:T( -1 ) - return -1 - end - - local DCSUnitVelocityVec3 = Client:GetClientGroupDCSUnit():getVelocity() - local DCSUnitVelocity = ( DCSUnitVelocityVec3.x ^2 + DCSUnitVelocityVec3.y ^2 + DCSUnitVelocityVec3.z ^2 ) ^ 0.5 - - local DCSUnitPointVec3 = Client:GetClientGroupDCSUnit():getPoint() - local LandHeight = land.getHeight( { x = DCSUnitPointVec3.x, y = DCSUnitPointVec3.z } ) - local DCSUnitHeight = DCSUnitPointVec3.y - LandHeight - - self:T( { Task.IsLandingRequired, Client:GetClientGroupDCSUnit():inAir() } ) - if Task.IsLandingRequired and Task.IsInAirTestRequired == true and Client:GetClientGroupDCSUnit():inAir() then - Task:RemoveCargoMenus( Client ) - Task.ExecuteStage = _TransportExecuteStage.FAILED - Task.CargoName = nil - Client:Message( "The " .. Task.CargoType .. " loading has been aborted. Re-start the " .. Task.TEXT[3] .. " process. Don't fly outside the pick-up zone.", - self.MSG.TIME, Host ) - self:T( -1 ) - return -1 - end - - self:T( { DCSUnitVelocity, DCSUnitHeight, LandHeight, Task.CurrentCargoZone.SignalHeight } ) - if Task.IsLandingRequired and Task.IsInAirTestRequired == false and DCSUnitVelocity >= 2 and DCSUnitHeight >= Task.CurrentCargoZone.SignalHeight then - Task:RemoveCargoMenus( Client ) - Task.ExecuteStage = _TransportExecuteStage.FAILED - Task.CargoName = nil - Client:Message( "The " .. Task.CargoType .. " loading has been aborted. Re-start the " .. Task.TEXT[3] .. " process. Don't fly outside the pick-up zone.", - self.MSG.TIME, Host ) - self:T( -1 ) - return -1 - end - - if Task.ExecuteStage == _TransportExecuteStage.SUCCESS then - Task:RemoveCargoMenus( Client ) - Client:Message( "Good Job. The " .. Task.CargoType .. " has been sucessfully " .. Task.TEXT[3] .. " within the landing zone.", - self.MSG.TIME, Host ) - Task.MissionTask:AddGoalCompletion( Task.MissionTask.GoalVerb, Task.CargoName, 1 ) - self:T( 1 ) - return 1 - end - - else - if Task.ExecuteStage == _TransportExecuteStage.SUCCESS then - CargoStatic = StaticObject.getByName( Task.Cargo.CargoStaticName ) - if CargoStatic and not routines.IsStaticInZones( CargoStatic, Task.CurrentLandingZoneName ) then - Client:Message( "Good Job. The " .. Task.CargoType .. " has been sucessfully " .. Task.TEXT[3] .. " and flown outside of the landing zone.", - self.MSG.TIME, Host ) - Task.MissionTask:AddGoalCompletion( Task.MissionTask.GoalVerb, Task.Cargo.CargoName, 1 ) - self:T( 1 ) - return 1 - end - end - - end - - - self:T( 0 ) - return 0 -end - - -STAGEDONE = { - ClassName = "STAGEDONE", - MSG = { ID = "Done", TIME = 10 }, - Name = "Done" -} - -function STAGEDONE:New() - local self = BASE:Inherit( self, STAGE:New() ) - self:F() - self.StageType = 'AI' - return self -end - -function STAGEDONE:Execute( Mission, Client, Task ) - self:F() - -end - -function STAGEDONE:Validate( Mission, Client, Task ) - self:F() - - Task:Done() - - return 0 -end - -STAGEARRIVE = { - ClassName = "STAGEARRIVE", - MSG = { ID = "Arrive", TIME = 10 }, - Name = "Arrive" -} - -function STAGEARRIVE:New() - local self = BASE:Inherit( self, STAGE:New() ) - self:F() - self.StageType = 'CLIENT' - return self -end - - ---- Execute Arrival --- @param #STAGEARRIVE self --- @param Mission#MISSION Mission --- @param Client#CLIENT Client --- @param Task#TASK Task -function STAGEARRIVE:Execute( Mission, Client, Task ) - self:F() - - if Client:IsMultiSeated() then - Client:Message( 'We have arrived at ' .. Task.CurrentLandingZoneName .. ".", self.MSG.TIME, "Co-Pilot" ) - else - Client:Message( 'We have arrived at ' .. Task.CurrentLandingZoneName .. ".", self.MSG.TIME, "Command" ) - end - -end - -function STAGEARRIVE:Validate( Mission, Client, Task ) - self:F() - - Task.CurrentLandingZoneID = routines.IsUnitInZones( Client:GetClientGroupDCSUnit(), Task.LandingZones ) - if ( Task.CurrentLandingZoneID ) then - else - return -1 - end - - return 1 -end - -STAGEGROUPSDESTROYED = { - ClassName = "STAGEGROUPSDESTROYED", - DestroyGroupSize = -1, - Frequency = STAGE.FREQUENCY.REPEAT, - MSG = { ID = "DestroyGroup", TIME = 10 }, - Name = "GroupsDestroyed" -} - -function STAGEGROUPSDESTROYED:New() - local self = BASE:Inherit( self, STAGE:New() ) - self:F() - self.StageType = 'AI' - return self -end - ---function STAGEGROUPSDESTROYED:Execute( Mission, Client, Task ) --- --- Client:Message( 'Task: Still ' .. DestroyGroupSize .. " of " .. Task.DestroyGroupCount .. " " .. Task.DestroyGroupType .. " to be destroyed!", self.MSG.TIME, Mission.Name .. "/Stage" ) --- ---end - -function STAGEGROUPSDESTROYED:Validate( Mission, Client, Task ) - self:F() - - if Task.MissionTask:IsGoalReached() then - return 1 - else - return 0 - end -end - -function STAGEGROUPSDESTROYED:Execute( Mission, Client, Task ) - self:F() - self:T( { Task.ClassName, Task.Destroyed } ) - --env.info( 'Event Table Task = ' .. tostring(Task) ) - -end - - - - - - - - - - - - - ---[[ - _TransportStage: Defines the different stages of which of transport missions can be in. This table is internal and is used to control the sequence of messages, actions and flow. - - - _TransportStage.START - - _TransportStage.ROUTE - - _TransportStage.LAND - - _TransportStage.EXECUTE - - _TransportStage.DONE - - _TransportStage.REMOVE ---]] -_TransportStage = { - HOLD = "HOLD", - START = "START", - ROUTE = "ROUTE", - LANDING = "LANDING", - LANDED = "LANDED", - EXECUTING = "EXECUTING", - LOAD = "LOAD", - UNLOAD = "UNLOAD", - DONE = "DONE", - NEXT = "NEXT" -} - -_TransportStageMsgTime = { - HOLD = 10, - START = 60, - ROUTE = 5, - LANDING = 10, - LANDED = 30, - EXECUTING = 30, - LOAD = 30, - UNLOAD = 30, - DONE = 30, - NEXT = 0 -} - -_TransportStageTime = { - HOLD = 10, - START = 5, - ROUTE = 5, - LANDING = 1, - LANDED = 1, - EXECUTING = 5, - LOAD = 5, - UNLOAD = 5, - DONE = 1, - NEXT = 0 -} - -_TransportStageAction = { - REPEAT = -1, - NONE = 0, - ONCE = 1 -} ---- This module contains the TASK_BASE class. --- --- 1) @{#TASK_BASE} class, extends @{Base#BASE} --- ============================================ --- 1.1) The @{#TASK_BASE} class implements the methods for task orchestration within MOOSE. --- ---------------------------------------------------------------------------------------- --- The class provides a couple of methods to: --- --- * @{#TASK_BASE.AssignToGroup}():Assign a task to a group (of players). --- * @{#TASK_BASE.AddProcess}():Add a @{Process} to a task. --- * @{#TASK_BASE.RemoveProcesses}():Remove a running @{Process} from a running task. --- * @{#TASK_BASE.AddStateMachine}():Add a @{StateMachine} to a task. --- * @{#TASK_BASE.RemoveStateMachines}():Remove @{StateMachine}s from a task. --- * @{#TASK_BASE.HasStateMachine}():Enquire if the task has a @{StateMachine} --- * @{#TASK_BASE.AssignToUnit}(): Assign a task to a unit. (Needs to be implemented in the derived classes from @{#TASK_BASE}. --- * @{#TASK_BASE.UnAssignFromUnit}(): Unassign the task from a unit. --- --- 1.2) Set and enquire task status (beyond the task state machine processing). --- ---------------------------------------------------------------------------- --- A task needs to implement as a minimum the following task states: --- --- * **Success**: Expresses the successful execution and finalization of the task. --- * **Failed**: Expresses the failure of a task. --- * **Planned**: Expresses that the task is created, but not yet in execution and is not assigned yet. --- * **Assigned**: Expresses that the task is assigned to a Group of players, and that the task is in execution mode. --- --- A task may also implement the following task states: --- --- * **Rejected**: Expresses that the task is rejected by a player, who was requested to accept the task. --- * **Cancelled**: Expresses that the task is cancelled by HQ or through a logical situation where a cancellation of the task is required. --- --- A task can implement more statusses than the ones outlined above. Please consult the documentation of the specific tasks to understand the different status modelled. --- --- The status of tasks can be set by the methods **State** followed by the task status. An example is `StateAssigned()`. --- The status of tasks can be enquired by the methods **IsState** followed by the task status name. An example is `if IsStateAssigned() then`. --- --- 1.3) Add scoring when reaching a certain task status: --- ----------------------------------------------------- --- Upon reaching a certain task status in a task, additional scoring can be given. If the Mission has a scoring system attached, the scores will be added to the mission scoring. --- Use the method @{#TASK_BASE.AddScore}() to add scores when a status is reached. --- --- 1.4) Task briefing: --- ------------------- --- A task briefing can be given that is shown to the player when he is assigned to the task. --- --- === --- --- ### Authors: FlightControl - Design and Programming --- --- @module Task - ---- The TASK_BASE class --- @type TASK_BASE --- @field Scheduler#SCHEDULER TaskScheduler --- @field Mission#MISSION Mission --- @field StateMachine#STATEMACHINE Fsm --- @field Set#SET_GROUP SetGroup The Set of Groups assigned to the Task --- @extends Base#BASE -TASK_BASE = { - ClassName = "TASK_BASE", - TaskScheduler = nil, - Processes = {}, - Players = nil, - Scores = {}, - Menu = {}, - SetGroup = nil, -} - - ---- Instantiates a new TASK_BASE. Should never be used. Interface Class. --- @param #TASK_BASE self --- @param Mission#MISSION The mission wherein the Task is registered. --- @param Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. --- @param #string TaskName The name of the Task --- @param #string TaskType The type of the Task --- @param #string TaskCategory The category of the Task (A2G, A2A, Transport, ... ) --- @return #TASK_BASE self -function TASK_BASE:New( Mission, SetGroup, TaskName, TaskType, TaskCategory ) - - local self = BASE:Inherit( self, BASE:New() ) - self:E( "New TASK " .. TaskName ) - - self.Processes = {} - self.Fsm = {} - - self.Mission = Mission - self.SetGroup = SetGroup - - self:SetCategory( TaskCategory ) - self:SetType( TaskType ) - self:SetName( TaskName ) - self:SetID( Mission:GetNextTaskID( self ) ) -- The Mission orchestrates the task sequences .. - - self.TaskBriefing = "You are assigned to the task: " .. self.TaskName .. "." - - return self -end - ---- Cleans all references of a TASK_BASE. --- @param #TASK_BASE self --- @return #nil -function TASK_BASE:CleanUp() - - _EVENTDISPATCHER:OnPlayerLeaveRemove( self ) - _EVENTDISPATCHER:OnDeadRemove( self ) - _EVENTDISPATCHER:OnCrashRemove( self ) - _EVENTDISPATCHER:OnPilotDeadRemove( self ) - - return nil -end - - ---- Assign the @{Task}to a @{Group}. --- @param #TASK_BASE self --- @param Group#GROUP TaskGroup -function TASK_BASE:AssignToGroup( TaskGroup ) - self:F2( TaskGroup:GetName() ) - - local TaskGroupName = TaskGroup:GetName() - - TaskGroup:SetState( TaskGroup, "Assigned", self ) - - self:RemoveMenuForGroup( TaskGroup ) - self:SetAssignedMenuForGroup( TaskGroup ) - - local TaskUnits = TaskGroup:GetUnits() - for UnitID, UnitData in pairs( TaskUnits ) do - local TaskUnit = UnitData -- Unit#UNIT - local PlayerName = TaskUnit:GetPlayerName() - if PlayerName ~= nil or PlayerName ~= "" then - self:AssignToUnit( TaskUnit ) - end - end -end - ---- Send the briefng message of the @{Task} to the assigned @{Group}s. --- @param #TASK_BASE self -function TASK_BASE:SendBriefingToAssignedGroups() - self:F2() - - for TaskGroupName, TaskGroup in pairs( self.SetGroup:GetSet() ) do - - if self:IsAssignedToGroup( TaskGroup ) then - TaskGroup:Message( self.TaskBriefing, 60 ) - end - end -end - - ---- Assign the @{Task} from the @{Group}s. --- @param #TASK_BASE self -function TASK_BASE:UnAssignFromGroups() - self:F2() - - for TaskGroupName, TaskGroup in pairs( self.SetGroup:GetSet() ) do - - TaskGroup:SetState( TaskGroup, "Assigned", nil ) - local TaskUnits = TaskGroup:GetUnits() - for UnitID, UnitData in pairs( TaskUnits ) do - local TaskUnit = UnitData -- Unit#UNIT - local PlayerName = TaskUnit:GetPlayerName() - if PlayerName ~= nil or PlayerName ~= "" then - self:UnAssignFromUnit( TaskUnit ) - end - end - end -end - ---- Returns if the @{Task} is assigned to the Group. --- @param #TASK_BASE self --- @param Group#GROUP TaskGroup --- @return #boolean -function TASK_BASE:IsAssignedToGroup( TaskGroup ) - - local TaskGroupName = TaskGroup:GetName() - - if self:IsStateAssigned() then - if TaskGroup:GetState( TaskGroup, "Assigned" ) == self then - return true - end - end - - return false -end - ---- Assign the @{Task}to an alive @{Unit}. --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @return #TASK_BASE self -function TASK_BASE:AssignToUnit( TaskUnit ) - self:F( TaskUnit:GetName() ) - - return nil -end - ---- UnAssign the @{Task} from an alive @{Unit}. --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @return #TASK_BASE self -function TASK_BASE:UnAssignFromUnit( TaskUnitName ) - self:F( TaskUnitName ) - - if self:HasStateMachine( TaskUnitName ) == true then - self:RemoveStateMachines( TaskUnitName ) - self:RemoveProcesses( TaskUnitName ) - end - - return self -end - ---- Set the menu options of the @{Task} to all the groups in the SetGroup. --- @param #TASK_BASE self --- @return #TASK_BASE self -function TASK_BASE:SetPlannedMenu() - - local MenuText = self:GetPlannedMenuText() - for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - if not self:IsAssignedToGroup( TaskGroup ) then - self:SetPlannedMenuForGroup( TaskGroup, MenuText ) - end - end -end - ---- Set the menu options of the @{Task} to all the groups in the SetGroup. --- @param #TASK_BASE self --- @return #TASK_BASE self -function TASK_BASE:SetAssignedMenu() - - for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - if self:IsAssignedToGroup( TaskGroup ) then - self:SetAssignedMenuForGroup( TaskGroup ) - end - end -end - ---- Remove the menu options of the @{Task} to all the groups in the SetGroup. --- @param #TASK_BASE self --- @return #TASK_BASE self -function TASK_BASE:RemoveMenu() - - for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - self:RemoveMenuForGroup( TaskGroup ) - end -end - ---- Set the planned menu option of the @{Task}. --- @param #TASK_BASE self --- @param Group#GROUP TaskGroup --- @param #string MenuText The menu text. --- @return #TASK_BASE self -function TASK_BASE:SetPlannedMenuForGroup( TaskGroup, MenuText ) - self:E( TaskGroup:GetName() ) - - local TaskMission = self.Mission:GetName() - local TaskCategory = self:GetCategory() - local TaskType = self:GetType() - - local Mission = self.Mission - - Mission.MenuMission = Mission.MenuMission or {} - local MenuMission = Mission.MenuMission - - Mission.MenuCategory = Mission.MenuCategory or {} - local MenuCategory = Mission.MenuCategory - - Mission.MenuType = Mission.MenuType or {} - local MenuType = Mission.MenuType - - self.Menu = self.Menu or {} - local Menu = self.Menu - - local TaskGroupName = TaskGroup:GetName() - MenuMission[TaskGroupName] = MenuMission[TaskGroupName] or MENU_GROUP:New( TaskGroup, TaskMission, nil ) - - MenuCategory[TaskGroupName] = MenuCategory[TaskGroupName] or {} - MenuCategory[TaskGroupName][TaskCategory] = MenuCategory[TaskGroupName][TaskCategory] or MENU_GROUP:New( TaskGroup, TaskCategory, MenuMission[TaskGroupName] ) - - MenuType[TaskGroupName] = MenuType[TaskGroupName] or {} - MenuType[TaskGroupName][TaskType] = MenuType[TaskGroupName][TaskType] or MENU_GROUP:New( TaskGroup, TaskType, MenuCategory[TaskGroupName][TaskCategory] ) - - if Menu[TaskGroupName] then - Menu[TaskGroupName]:Remove() - end - Menu[TaskGroupName] = MENU_GROUP_COMMAND:New( TaskGroup, MenuText, MenuType[TaskGroupName][TaskType], self.MenuAssignToGroup, { self = self, TaskGroup = TaskGroup } ) - - return self -end - ---- Set the assigned menu options of the @{Task}. --- @param #TASK_BASE self --- @param Group#GROUP TaskGroup --- @return #TASK_BASE self -function TASK_BASE:SetAssignedMenuForGroup( TaskGroup ) - self:E( TaskGroup:GetName() ) - - local TaskMission = self.Mission:GetName() - - local Mission = self.Mission - - Mission.MenuMission = Mission.MenuMission or {} - local MenuMission = Mission.MenuMission - - self.MenuStatus = self.MenuStatus or {} - local MenuStatus = self.MenuStatus - - - self.MenuAbort = self.MenuAbort or {} - local MenuAbort = self.MenuAbort - - local TaskGroupName = TaskGroup:GetName() - MenuMission[TaskGroupName] = MenuMission[TaskGroupName] or MENU_GROUP:New( TaskGroup, TaskMission, nil ) - MenuStatus[TaskGroupName] = MENU_GROUP_COMMAND:New( TaskGroup, "Task Status", MenuMission[TaskGroupName], self.MenuTaskStatus, { self = self, TaskGroup = TaskGroup } ) - MenuAbort[TaskGroupName] = MENU_GROUP_COMMAND:New( TaskGroup, "Abort Task", MenuMission[TaskGroupName], self.MenuTaskAbort, { self = self, TaskGroup = TaskGroup } ) - - return self -end - ---- Remove the menu option of the @{Task} for a @{Group}. --- @param #TASK_BASE self --- @param Group#GROUP TaskGroup --- @return #TASK_BASE self -function TASK_BASE:RemoveMenuForGroup( TaskGroup ) - - local TaskGroupName = TaskGroup:GetName() - - local Mission = self.Mission - local MenuMission = Mission.MenuMission - local MenuCategory = Mission.MenuCategory - local MenuType = Mission.MenuType - local MenuStatus = self.MenuStatus - local MenuAbort = self.MenuAbort - local Menu = self.Menu - - Menu = Menu or {} - if Menu[TaskGroupName] then - Menu[TaskGroupName]:Remove() - Menu[TaskGroupName] = nil - end - - MenuType = MenuType or {} - if MenuType[TaskGroupName] then - for _, Menu in pairs( MenuType[TaskGroupName] ) do - Menu:Remove() - end - MenuType[TaskGroupName] = nil - end - - MenuCategory = MenuCategory or {} - if MenuCategory[TaskGroupName] then - for _, Menu in pairs( MenuCategory[TaskGroupName] ) do - Menu:Remove() - end - MenuCategory[TaskGroupName] = nil - end - - MenuStatus = MenuStatus or {} - if MenuStatus[TaskGroupName] then - MenuStatus[TaskGroupName]:Remove() - MenuStatus[TaskGroupName] = nil - end - - MenuAbort = MenuAbort or {} - if MenuAbort[TaskGroupName] then - MenuAbort[TaskGroupName]:Remove() - MenuAbort[TaskGroupName] = nil - end - -end - -function TASK_BASE.MenuAssignToGroup( MenuParam ) - - local self = MenuParam.self - local TaskGroup = MenuParam.TaskGroup - - self:AssignToGroup( TaskGroup ) -end - -function TASK_BASE.MenuTaskStatus( MenuParam ) - - local self = MenuParam.self - local TaskGroup = MenuParam.TaskGroup - - --self:AssignToGroup( TaskGroup ) -end - -function TASK_BASE.MenuTaskAbort( MenuParam ) - - local self = MenuParam.self - local TaskGroup = MenuParam.TaskGroup - - --self:AssignToGroup( TaskGroup ) -end - - - ---- Returns the @{Task} name. --- @param #TASK_BASE self --- @return #string TaskName -function TASK_BASE:GetTaskName() - return self.TaskName -end - - ---- Add Process to @{Task} with key @{Unit}. --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @return #TASK_BASE self -function TASK_BASE:AddProcess( TaskUnit, Process ) - local TaskUnitName = TaskUnit:GetName() - self.Processes = self.Processes or {} - self.Processes[TaskUnitName] = self.Processes[TaskUnitName] or {} - self.Processes[TaskUnitName][#self.Processes[TaskUnitName]+1] = Process - return Process -end - - ---- Remove Processes from @{Task} with key @{Unit} --- @param #TASK_BASE self --- @param #string TaskUnitName --- @return #TASK_BASE self -function TASK_BASE:RemoveProcesses( TaskUnitName ) - - for ProcessID, ProcessData in pairs( self.Processes[TaskUnitName] ) do - local Process = ProcessData -- Process#PROCESS - Process:StopEvents() - Process = nil - self.Processes[TaskUnitName][ProcessID] = nil - self:E( self.Processes[TaskUnitName][ProcessID] ) - end - self.Processes[TaskUnitName] = nil -end - ---- Fail processes from @{Task} with key @{Unit} --- @param #TASK_BASE self --- @param #string TaskUnitName --- @return #TASK_BASE self -function TASK_BASE:FailProcesses( TaskUnitName ) - - for ProcessID, ProcessData in pairs( self.Processes[TaskUnitName] ) do - local Process = ProcessData -- Process#PROCESS - Process.Fsm:Fail() - end -end - ---- Add a FiniteStateMachine to @{Task} with key @{Unit} --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @return #TASK_BASE self -function TASK_BASE:AddStateMachine( TaskUnit, Fsm ) - local TaskUnitName = TaskUnit:GetName() - self.Fsm[TaskUnitName] = self.Fsm[TaskUnitName] or {} - self.Fsm[TaskUnitName][#self.Fsm[TaskUnitName]+1] = Fsm - return Fsm -end - ---- Remove FiniteStateMachines from @{Task} with key @{Unit} --- @param #TASK_BASE self --- @param #string TaskUnitName --- @return #TASK_BASE self -function TASK_BASE:RemoveStateMachines( TaskUnitName ) - - for _, Fsm in pairs( self.Fsm[TaskUnitName] ) do - Fsm = nil - self.Fsm[TaskUnitName][_] = nil - self:E( self.Fsm[TaskUnitName][_] ) - end - self.Fsm[TaskUnitName] = nil -end - ---- Checks if there is a FiniteStateMachine assigned to @{Unit} for @{Task} --- @param #TASK_BASE self --- @param #string TaskUnitName --- @return #TASK_BASE self -function TASK_BASE:HasStateMachine( TaskUnitName ) - - self:F( { TaskUnitName, self.Fsm[TaskUnitName] ~= nil } ) - return ( self.Fsm[TaskUnitName] ~= nil ) -end - - - - - ---- Register a potential new assignment for a new spawned @{Unit}. --- Tasks only get assigned if there are players in it. --- @param #TASK_BASE self --- @param Event#EVENTDATA Event --- @return #TASK_BASE self -function TASK_BASE:_EventAssignUnit( Event ) - if Event.IniUnit then - self:F( Event ) - local TaskUnit = Event.IniUnit - if TaskUnit:IsAlive() then - local TaskPlayerName = TaskUnit:GetPlayerName() - if TaskPlayerName ~= nil then - if not self:HasStateMachine( TaskUnit ) then - -- Check if the task was assigned to the group, if it was assigned to the group, assign to the unit just spawned and initiate the processes. - local TaskGroup = TaskUnit:GetGroup() - if self:IsAssignedToGroup( TaskGroup ) then - self:AssignToUnit( TaskUnit ) - end - end - end - end - end - return nil -end - ---- Catches the "player leave unit" event for a @{Unit} .... --- When a player is an air unit, and leaves the unit: --- --- * and he is not at an airbase runway on the ground, he will fail its task. --- * and he is on an airbase and on the ground, the process for him will just continue to work, he can switch airplanes, and take-off again. --- This is important to model the change from plane types for a player during mission assignment. --- @param #TASK_BASE self --- @param Event#EVENTDATA Event --- @return #TASK_BASE self -function TASK_BASE:_EventPlayerLeaveUnit( Event ) - self:F( Event ) - if Event.IniUnit then - local TaskUnit = Event.IniUnit - local TaskUnitName = Event.IniUnitName - - -- Check if for this unit in the task there is a process ongoing. - if self:HasStateMachine( TaskUnitName ) then - if TaskUnit:IsAir() then - if TaskUnit:IsAboveRunway() then - -- do nothing - else - self:E( "IsNotAboveRunway" ) - -- Player left airplane during an assigned task and was not at an airbase. - self:FailProcesses( TaskUnitName ) - self:UnAssignFromUnit( TaskUnitName ) - end - end - end - - end - return nil -end - ---- UnAssigns a @{Unit} that is left by a player, crashed, dead, .... --- There are only assignments if there are players in it. --- @param #TASK_BASE self --- @param Event#EVENTDATA Event --- @return #TASK_BASE self -function TASK_BASE:_EventDead( Event ) - self:F( Event ) - if Event.IniUnit then - local TaskUnit = Event.IniUnit - local TaskUnitName = Event.IniUnitName - - -- Check if for this unit in the task there is a process ongoing. - if self:HasStateMachine( TaskUnitName ) then - self:FailProcesses( TaskUnitName ) - self:UnAssignFromUnit( TaskUnitName ) - end - - local TaskGroup = Event.IniUnit:GetGroup() - TaskGroup:SetState( TaskGroup, "Assigned", nil ) - end - return nil -end - ---- Gets the Scoring of the task --- @param #TASK_BASE self --- @return Scoring#SCORING Scoring -function TASK_BASE:GetScoring() - return self.Mission:GetScoring() -end - - ---- Gets the Task Index, which is a combination of the Task category, the Task type, the Task name. --- @param #TASK_BASE self --- @return #string The Task ID -function TASK_BASE:GetTaskIndex() - - local TaskCategory = self:GetCategory() - local TaskType = self:GetType() - local TaskName = self:GetName() - - return TaskCategory .. "." ..TaskType .. "." .. TaskName -end - ---- Sets the Name of the Task --- @param #TASK_BASE self --- @param #string TaskName -function TASK_BASE:SetName( TaskName ) - self.TaskName = TaskName -end - ---- Gets the Name of the Task --- @param #TASK_BASE self --- @return #string The Task Name -function TASK_BASE:GetName() - return self.TaskName -end - ---- Sets the Type of the Task --- @param #TASK_BASE self --- @param #string TaskType -function TASK_BASE:SetType( TaskType ) - self.TaskType = TaskType -end - ---- Gets the Type of the Task --- @param #TASK_BASE self --- @return #string TaskType -function TASK_BASE:GetType() - return self.TaskType -end - ---- Sets the Category of the Task --- @param #TASK_BASE self --- @param #string TaskCategory -function TASK_BASE:SetCategory( TaskCategory ) - self.TaskCategory = TaskCategory -end - ---- Gets the Category of the Task --- @param #TASK_BASE self --- @return #string TaskCategory -function TASK_BASE:GetCategory() - return self.TaskCategory -end - ---- Sets the ID of the Task --- @param #TASK_BASE self --- @param #string TaskID -function TASK_BASE:SetID( TaskID ) - self.TaskID = TaskID -end - ---- Gets the ID of the Task --- @param #TASK_BASE self --- @return #string TaskID -function TASK_BASE:GetID() - return self.TaskID -end - - ---- Sets a @{Task} to status **Success**. --- @param #TASK_BASE self -function TASK_BASE:StateSuccess() - self:SetState( self, "State", "Success" ) - return self -end - ---- Is the @{Task} status **Success**. --- @param #TASK_BASE self -function TASK_BASE:IsStateSuccess() - return self:GetStateString() == "Success" -end - ---- Sets a @{Task} to status **Failed**. --- @param #TASK_BASE self -function TASK_BASE:StateFailed() - self:SetState( self, "State", "Failed" ) - return self -end - ---- Is the @{Task} status **Failed**. --- @param #TASK_BASE self -function TASK_BASE:IsStateFailed() - return self:GetStateString() == "Failed" -end - ---- Sets a @{Task} to status **Planned**. --- @param #TASK_BASE self -function TASK_BASE:StatePlanned() - self:SetState( self, "State", "Planned" ) - return self -end - ---- Is the @{Task} status **Planned**. --- @param #TASK_BASE self -function TASK_BASE:IsStatePlanned() - return self:GetStateString() == "Planned" -end - ---- Sets a @{Task} to status **Assigned**. --- @param #TASK_BASE self -function TASK_BASE:StateAssigned() - self:SetState( self, "State", "Assigned" ) - return self -end - ---- Is the @{Task} status **Assigned**. --- @param #TASK_BASE self -function TASK_BASE:IsStateAssigned() - return self:GetStateString() == "Assigned" -end - ---- Sets a @{Task} to status **Hold**. --- @param #TASK_BASE self -function TASK_BASE:StateHold() - self:SetState( self, "State", "Hold" ) - return self -end - ---- Is the @{Task} status **Hold**. --- @param #TASK_BASE self -function TASK_BASE:IsStateHold() - return self:GetStateString() == "Hold" -end - ---- Sets a @{Task} to status **Replanned**. --- @param #TASK_BASE self -function TASK_BASE:StateReplanned() - self:SetState( self, "State", "Replanned" ) - return self -end - ---- Is the @{Task} status **Replanned**. --- @param #TASK_BASE self -function TASK_BASE:IsStateReplanned() - return self:GetStateString() == "Replanned" -end - ---- Gets the @{Task} status. --- @param #TASK_BASE self -function TASK_BASE:GetStateString() - return self:GetState( self, "State" ) -end - ---- Sets a @{Task} briefing. --- @param #TASK_BASE self --- @param #string TaskBriefing --- @return #TASK_BASE self -function TASK_BASE:SetBriefing( TaskBriefing ) - self.TaskBriefing = TaskBriefing - return self -end - - - ---- Adds a score for the TASK to be achieved. --- @param #TASK_BASE self --- @param #string TaskStatus is the status of the TASK when the score needs to be given. --- @param #string ScoreText is a text describing the score that is given according the status. --- @param #number Score is a number providing the score of the status. --- @return #TASK_BASE self -function TASK_BASE:AddScore( TaskStatus, ScoreText, Score ) - self:F2( { TaskStatus, ScoreText, Score } ) - - self.Scores[TaskStatus] = self.Scores[TaskStatus] or {} - self.Scores[TaskStatus].ScoreText = ScoreText - self.Scores[TaskStatus].Score = Score - return self -end - ---- StateMachine callback function for a TASK --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @param StateMachine#STATEMACHINE_TASK Fsm --- @param #string Event --- @param #string From --- @param #string To --- @param Event#EVENTDATA Event -function TASK_BASE:OnAssigned( TaskUnit, Fsm, Event, From, To ) - - self:E("Assigned") - - local TaskGroup = TaskUnit:GetGroup() - - TaskGroup:Message( self.TaskBriefing, 20 ) - - self:RemoveMenuForGroup( TaskGroup ) - self:SetAssignedMenuForGroup( TaskGroup ) - -end - - ---- StateMachine callback function for a TASK --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @param StateMachine#STATEMACHINE_TASK Fsm --- @param #string Event --- @param #string From --- @param #string To --- @param Event#EVENTDATA Event -function TASK_BASE:OnSuccess( TaskUnit, Fsm, Event, From, To ) - - self:E("Success") - - self:UnAssignFromGroups() - - local TaskGroup = TaskUnit:GetGroup() - self.Mission:SetPlannedMenu() - - self:StateSuccess() - - -- The task has become successful, the event catchers can be cleaned. - self:CleanUp() - -end - ---- StateMachine callback function for a TASK --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @param StateMachine#STATEMACHINE_TASK Fsm --- @param #string Event --- @param #string From --- @param #string To --- @param Event#EVENTDATA Event -function TASK_BASE:OnFailed( TaskUnit, Fsm, Event, From, To ) - - self:E( { "Failed for unit ", TaskUnit:GetName(), TaskUnit:GetPlayerName() } ) - - -- A task cannot be "failed", so a task will always be there waiting for players to join. - -- When the player leaves its unit, we will need to check whether he was on the ground or not at an airbase. - -- When the player crashes, we will need to check whether in the group there are other players still active. It not, we reset the task from Assigned to Planned, otherwise, we just leave as Assigned. - - self:UnAssignFromGroups() - self:StatePlanned() - -end - ---- StateMachine callback function for a TASK --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @param StateMachine#STATEMACHINE_TASK Fsm --- @param #string Event --- @param #string From --- @param #string To --- @param Event#EVENTDATA Event -function TASK_BASE:OnStateChange( TaskUnit, Fsm, Event, From, To ) - - if self:IsTrace() then - MESSAGE:New( "Task " .. self.TaskName .. " : " .. Event .. " changed to state " .. To, 15 ):ToAll() - end - - self:E( { Event, From, To } ) - self:SetState( self, "State", To ) - - if self.Scores[To] then - local Scoring = self:GetScoring() - if Scoring then - Scoring:_AddMissionScore( self.Mission, self.Scores[To].ScoreText, self.Scores[To].Score ) - end - end - -end - - ---- @param #TASK_BASE self -function TASK_BASE:_Schedule() - self:F2() - - self.TaskScheduler = SCHEDULER:New( self, _Scheduler, {}, 15, 15 ) - return self -end - - ---- @param #TASK_BASE self -function TASK_BASE._Scheduler() - self:F2() - - return true -end - - - - ---- A GOHOMETASK orchestrates the travel back to the home base, which is a specific zone defined within the ME. --- @module GOHOMETASK - ---- The GOHOMETASK class --- @type -GOHOMETASK = { - ClassName = "GOHOMETASK", -} - ---- Creates a new GOHOMETASK. --- @param table{string,...}|string LandingZones Table of Landing Zone names where Home(s) are located. --- @return GOHOMETASK -function GOHOMETASK:New( LandingZones ) - local self = BASE:Inherit( self, TASK:New() ) - self:F( { LandingZones } ) - local Valid = true - - Valid = routines.ValidateZone( LandingZones, "LandingZones", Valid ) - - if Valid then - self.Name = 'Fly Home' - self.TaskBriefing = "Task: Fly back to your home base. Your co-pilot will provide you with the directions (required flight angle in degrees) and the distance (in km) to your home base." - if type( LandingZones ) == "table" then - self.LandingZones = LandingZones - else - self.LandingZones = { LandingZones } - end - self.Stages = { STAGEBRIEF:New(), STAGESTART:New(), STAGEROUTE:New(), STAGEARRIVE:New(), STAGEDONE:New() } - self.SetStage( self, 1 ) - end - - return self -end ---- A DESTROYBASETASK will monitor the destruction of Groups and Units. This is a BASE class, other classes are derived from this class. --- @module DESTROYBASETASK --- @see DESTROYGROUPSTASK --- @see DESTROYUNITTYPESTASK --- @see DESTROY_RADARS_TASK - - - ---- The DESTROYBASETASK class --- @type DESTROYBASETASK -DESTROYBASETASK = { - ClassName = "DESTROYBASETASK", - Destroyed = 0, - GoalVerb = "Destroy", - DestroyPercentage = 100, -} - ---- Creates a new DESTROYBASETASK. --- @param #DESTROYBASETASK self --- @param #string DestroyGroupType Text describing the group to be destroyed. f.e. "Radar Installations", "Ships", "Vehicles", "Command Centers". --- @param #string DestroyUnitType Text describing the unit types to be destroyed. f.e. "SA-6", "Row Boats", "Tanks", "Tents". --- @param #list<#string> DestroyGroupPrefixes Table of Prefixes of the Groups to be destroyed before task is completed. --- @param #number DestroyPercentage defines the %-tage that needs to be destroyed to achieve mission success. eg. If in the Group there are 10 units, then a value of 75 would require 8 units to be destroyed from the Group to complete the @{TASK}. --- @return DESTROYBASETASK -function DESTROYBASETASK:New( DestroyGroupType, DestroyUnitType, DestroyGroupPrefixes, DestroyPercentage ) - local self = BASE:Inherit( self, TASK:New() ) - self:F() - - self.Name = 'Destroy' - self.Destroyed = 0 - self.DestroyGroupPrefixes = DestroyGroupPrefixes - self.DestroyGroupType = DestroyGroupType - self.DestroyUnitType = DestroyUnitType - if DestroyPercentage then - self.DestroyPercentage = DestroyPercentage - end - self.TaskBriefing = "Task: Destroy " .. DestroyGroupType .. "." - self.Stages = { STAGEBRIEF:New(), STAGESTART:New(), STAGEGROUPSDESTROYED:New(), STAGEDONE:New() } - self.SetStage( self, 1 ) - - return self -end - ---- Handle the S_EVENT_DEAD events to validate the destruction of units for the task monitoring. --- @param #DESTROYBASETASK self --- @param Event#EVENTDATA Event structure of MOOSE. -function DESTROYBASETASK:EventDead( Event ) - self:F( { Event } ) - - if Event.IniDCSUnit then - local DestroyUnit = Event.IniDCSUnit - local DestroyUnitName = Event.IniDCSUnitName - local DestroyGroup = Event.IniDCSGroup - local DestroyGroupName = Event.IniDCSGroupName - - --TODO: I need to fix here if 2 groups in the mission have a similar name with GroupPrefix equal, then i should differentiate for which group the goal was reached! - --I may need to test if for the goalverb that group goal was reached or something. Need to think about it a bit more ... - local UnitsDestroyed = 0 - for DestroyGroupPrefixID, DestroyGroupPrefix in pairs( self.DestroyGroupPrefixes ) do - self:T( DestroyGroupPrefix ) - if string.find( DestroyGroupName, DestroyGroupPrefix, 1, true ) then - self:T( BASE:Inherited(self).ClassName ) - UnitsDestroyed = self:ReportGoalProgress( DestroyGroup, DestroyUnit ) - self:T( UnitsDestroyed ) - end - end - - self:T( { UnitsDestroyed } ) - self:IncreaseGoalCount( UnitsDestroyed, self.GoalVerb ) - end - -end - ---- Validate task completeness of DESTROYBASETASK. --- @param DestroyGroup Group structure describing the group to be evaluated. --- @param DestroyUnit Unit structure describing the Unit to be evaluated. -function DESTROYBASETASK:ReportGoalProgress( DestroyGroup, DestroyUnit ) - self:F() - - return 0 -end ---- DESTROYGROUPSTASK --- @module DESTROYGROUPSTASK - - - ---- The DESTROYGROUPSTASK class --- @type -DESTROYGROUPSTASK = { - ClassName = "DESTROYGROUPSTASK", - GoalVerb = "Destroy Groups", -} - ---- Creates a new DESTROYGROUPSTASK. --- @param #DESTROYGROUPSTASK self --- @param #string DestroyGroupType String describing the group to be destroyed. --- @param #string DestroyUnitType String describing the unit to be destroyed. --- @param #list<#string> DestroyGroupNames Table of string containing the name of the groups to be destroyed before task is completed. --- @param #number DestroyPercentage defines the %-tage that needs to be destroyed to achieve mission success. eg. If in the Group there are 10 units, then a value of 75 would require 8 units to be destroyed from the Group to complete the @{TASK}. ----@return DESTROYGROUPSTASK -function DESTROYGROUPSTASK:New( DestroyGroupType, DestroyUnitType, DestroyGroupNames, DestroyPercentage ) - local self = BASE:Inherit( self, DESTROYBASETASK:New( DestroyGroupType, DestroyUnitType, DestroyGroupNames, DestroyPercentage ) ) - self:F() - - self.Name = 'Destroy Groups' - self.GoalVerb = "Destroy " .. DestroyGroupType - - _EVENTDISPATCHER:OnDead( self.EventDead , self ) - _EVENTDISPATCHER:OnCrash( self.EventDead , self ) - - return self -end - ---- Report Goal Progress. --- @param #DESTROYGROUPSTASK self --- @param DCSGroup#Group DestroyGroup Group structure describing the group to be evaluated. --- @param DCSUnit#Unit DestroyUnit Unit structure describing the Unit to be evaluated. --- @return #number The DestroyCount reflecting the amount of units destroyed within the group. -function DESTROYGROUPSTASK:ReportGoalProgress( DestroyGroup, DestroyUnit ) - self:F( { DestroyGroup, DestroyUnit, self.DestroyPercentage } ) - - local DestroyGroupSize = DestroyGroup:getSize() - 1 -- When a DEAD event occurs, the getSize is still one larger than the destroyed unit. - local DestroyGroupInitialSize = DestroyGroup:getInitialSize() - self:T( { DestroyGroupSize, DestroyGroupInitialSize - ( DestroyGroupInitialSize * self.DestroyPercentage / 100 ) } ) - - local DestroyCount = 0 - if DestroyGroup then - if DestroyGroupSize <= DestroyGroupInitialSize - ( DestroyGroupInitialSize * self.DestroyPercentage / 100 ) then - DestroyCount = 1 - end - else - DestroyCount = 1 - end - - self:T( DestroyCount ) - - return DestroyCount -end ---- Task class to destroy radar installations. --- @module DESTROYRADARSTASK - - - ---- The DESTROYRADARS class --- @type -DESTROYRADARSTASK = { - ClassName = "DESTROYRADARSTASK", - GoalVerb = "Destroy Radars" -} - ---- Creates a new DESTROYRADARSTASK. --- @param table{string,...} DestroyGroupNames Table of string containing the group names of which the radars are be destroyed. --- @return DESTROYRADARSTASK -function DESTROYRADARSTASK:New( DestroyGroupNames ) - local self = BASE:Inherit( self, DESTROYGROUPSTASK:New( 'radar installations', 'radars', DestroyGroupNames ) ) - self:F() - - self.Name = 'Destroy Radars' - - _EVENTDISPATCHER:OnDead( self.EventDead , self ) - - return self -end - ---- Report Goal Progress. --- @param Group DestroyGroup Group structure describing the group to be evaluated. --- @param Unit DestroyUnit Unit structure describing the Unit to be evaluated. -function DESTROYRADARSTASK:ReportGoalProgress( DestroyGroup, DestroyUnit ) - self:F( { DestroyGroup, DestroyUnit } ) - - local DestroyCount = 0 - if DestroyUnit and DestroyUnit:hasSensors( Unit.SensorType.RADAR, Unit.RadarType.AS ) then - if DestroyUnit and DestroyUnit:getLife() <= 1.0 then - self:T( 'Destroyed a radar' ) - DestroyCount = 1 - end - end - return DestroyCount -end ---- Set TASK to destroy certain unit types. --- @module DESTROYUNITTYPESTASK - - - ---- The DESTROYUNITTYPESTASK class --- @type -DESTROYUNITTYPESTASK = { - ClassName = "DESTROYUNITTYPESTASK", - GoalVerb = "Destroy", -} - ---- Creates a new DESTROYUNITTYPESTASK. --- @param string DestroyGroupType String describing the group to be destroyed. f.e. "Radar Installations", "Fleet", "Batallion", "Command Centers". --- @param string DestroyUnitType String describing the unit to be destroyed. f.e. "radars", "ships", "tanks", "centers". --- @param table{string,...} DestroyGroupNames Table of string containing the group names of which the radars are be destroyed. --- @param string DestroyUnitTypes Table of string containing the type names of the units to achieve mission success. --- @return DESTROYUNITTYPESTASK -function DESTROYUNITTYPESTASK:New( DestroyGroupType, DestroyUnitType, DestroyGroupNames, DestroyUnitTypes ) - local self = BASE:Inherit( self, DESTROYBASETASK:New( DestroyGroupType, DestroyUnitType, DestroyGroupNames ) ) - self:F( { DestroyGroupType, DestroyUnitType, DestroyGroupNames, DestroyUnitTypes } ) - - if type(DestroyUnitTypes) == 'table' then - self.DestroyUnitTypes = DestroyUnitTypes - else - self.DestroyUnitTypes = { DestroyUnitTypes } - end - - self.Name = 'Destroy Unit Types' - self.GoalVerb = "Destroy " .. DestroyGroupType - - _EVENTDISPATCHER:OnDead( self.EventDead , self ) - - return self -end - ---- Report Goal Progress. --- @param Group DestroyGroup Group structure describing the group to be evaluated. --- @param Unit DestroyUnit Unit structure describing the Unit to be evaluated. -function DESTROYUNITTYPESTASK:ReportGoalProgress( DestroyGroup, DestroyUnit ) - self:F( { DestroyGroup, DestroyUnit } ) - - local DestroyCount = 0 - for UnitTypeID, UnitType in pairs( self.DestroyUnitTypes ) do - if DestroyUnit and DestroyUnit:getTypeName() == UnitType then - if DestroyUnit and DestroyUnit:getLife() <= 1.0 then - DestroyCount = DestroyCount + 1 - end - end - end - return DestroyCount -end ---- A PICKUPTASK orchestrates the loading of CARGO at a specific landing zone. --- @module PICKUPTASK --- @parent TASK - ---- The PICKUPTASK class --- @type -PICKUPTASK = { - ClassName = "PICKUPTASK", - TEXT = { "Pick-Up", "picked-up", "loaded" }, - GoalVerb = "Pick-Up" -} - ---- Creates a new PICKUPTASK. --- @param table{string,...}|string LandingZones Table of Zone names where Cargo is to be loaded. --- @param CARGO_TYPE CargoType Type of the Cargo. The type must be of the following Enumeration:.. --- @param number OnBoardSide Reflects from which side the cargo Group will be on-boarded on the Carrier. -function PICKUPTASK:New( CargoType, OnBoardSide ) - local self = BASE:Inherit( self, TASK:New() ) - self:F() - - -- self holds the inherited instance of the PICKUPTASK Class to the BASE class. - - local Valid = true - - if Valid then - self.Name = 'Pickup Cargo' - self.TaskBriefing = "Task: Fly to the indicated landing zones and pickup " .. CargoType .. ". Your co-pilot will provide you with the directions (required flight angle in degrees) and the distance (in km) to the pickup zone." - self.CargoType = CargoType - self.GoalVerb = CargoType .. " " .. self.GoalVerb - self.OnBoardSide = OnBoardSide - self.IsLandingRequired = true -- required to decide whether the client needs to land or not - self.IsSlingLoad = false -- Indicates whether the cargo is a sling load cargo - self.Stages = { STAGE_CARGO_INIT:New(), STAGE_CARGO_LOAD:New(), STAGEBRIEF:New(), STAGESTART:New(), STAGEROUTE:New(), STAGELANDING:New(), STAGELANDED:New(), STAGELOAD:New(), STAGEDONE:New() } - self.SetStage( self, 1 ) - end - - return self -end - -function PICKUPTASK:FromZone( LandingZone ) - self:F() - - self.LandingZones.LandingZoneNames[LandingZone.CargoZoneName] = LandingZone.CargoZoneName - self.LandingZones.LandingZones[LandingZone.CargoZoneName] = LandingZone - - return self -end - -function PICKUPTASK:InitCargo( InitCargos ) - self:F( { InitCargos } ) - - if type( InitCargos ) == "table" then - self.Cargos.InitCargos = InitCargos - else - self.Cargos.InitCargos = { InitCargos } - end - - return self -end - -function PICKUPTASK:LoadCargo( LoadCargos ) - self:F( { LoadCargos } ) - - if type( LoadCargos ) == "table" then - self.Cargos.LoadCargos = LoadCargos - else - self.Cargos.LoadCargos = { LoadCargos } - end - - return self -end - -function PICKUPTASK:AddCargoMenus( Client, Cargos, TransportRadius ) - self:F() - - for CargoID, Cargo in pairs( Cargos ) do - - self:T( { Cargo.ClassName, Cargo.CargoName, Cargo.CargoType, Cargo:IsStatusNone(), Cargo:IsStatusLoaded(), Cargo:IsStatusLoading(), Cargo:IsStatusUnLoaded() } ) - - -- If the Cargo has no status, allow the menu option. - if Cargo:IsStatusNone() or ( Cargo:IsStatusLoading() and Client == Cargo:IsLoadingToClient() ) then - - local MenuAdd = false - if Cargo:IsNear( Client, self.CurrentCargoZone ) then - MenuAdd = true - end - - if MenuAdd then - if Client._Menus[Cargo.CargoType] == nil then - Client._Menus[Cargo.CargoType] = {} - end - - if not Client._Menus[Cargo.CargoType].PickupMenu then - Client._Menus[Cargo.CargoType].PickupMenu = missionCommands.addSubMenuForGroup( - Client:GetClientGroupID(), - self.TEXT[1] .. " " .. Cargo.CargoType, - nil - ) - self:T( 'Added PickupMenu: ' .. self.TEXT[1] .. " " .. Cargo.CargoType ) - end - - if Client._Menus[Cargo.CargoType].PickupSubMenus == nil then - Client._Menus[Cargo.CargoType].PickupSubMenus = {} - end - - Client._Menus[Cargo.CargoType].PickupSubMenus[ #Client._Menus[Cargo.CargoType].PickupSubMenus + 1 ] = missionCommands.addCommandForGroup( - Client:GetClientGroupID(), - Cargo.CargoName .. " ( " .. Cargo.CargoWeight .. "kg )", - Client._Menus[Cargo.CargoType].PickupMenu, - self.MenuAction, - { ReferenceTask = self, CargoTask = Cargo } - ) - self:T( 'Added PickupSubMenu' .. Cargo.CargoType .. ":" .. Cargo.CargoName .. " ( " .. Cargo.CargoWeight .. "kg )" ) - end - end - end - -end - -function PICKUPTASK:RemoveCargoMenus( Client ) - self:F() - - for MenuID, MenuData in pairs( Client._Menus ) do - for SubMenuID, SubMenuData in pairs( MenuData.PickupSubMenus ) do - missionCommands.removeItemForGroup( Client:GetClientGroupID(), SubMenuData ) - self:T( "Removed PickupSubMenu " ) - SubMenuData = nil - end - if MenuData.PickupMenu then - missionCommands.removeItemForGroup( Client:GetClientGroupID(), MenuData.PickupMenu ) - self:T( "Removed PickupMenu " ) - MenuData.PickupMenu = nil - end - end - - for CargoID, Cargo in pairs( CARGOS ) do - self:T( { Cargo.ClassName, Cargo.CargoName, Cargo.CargoType, Cargo:IsStatusNone(), Cargo:IsStatusLoaded(), Cargo:IsStatusLoading(), Cargo:IsStatusUnLoaded() } ) - if Cargo:IsStatusLoading() and Client == Cargo:IsLoadingToClient() then - Cargo:StatusNone() - end - end - -end - - - -function PICKUPTASK:HasFailed( ClientDead ) - self:F() - - local TaskHasFailed = self.TaskFailed - return TaskHasFailed -end - ---- A DEPLOYTASK orchestrates the deployment of CARGO within a specific landing zone. --- @module DEPLOYTASK - - - ---- A DeployTask --- @type DEPLOYTASK -DEPLOYTASK = { - ClassName = "DEPLOYTASK", - TEXT = { "Deploy", "deployed", "unloaded" }, - GoalVerb = "Deployment" -} - - ---- Creates a new DEPLOYTASK object, which models the sequence of STAGEs to unload a cargo. --- @function [parent=#DEPLOYTASK] New --- @param #string CargoType Type of the Cargo. --- @return #DEPLOYTASK The created DeployTask -function DEPLOYTASK:New( CargoType ) - local self = BASE:Inherit( self, TASK:New() ) - self:F() - - local Valid = true - - if Valid then - self.Name = 'Deploy Cargo' - self.TaskBriefing = "Fly to one of the indicated landing zones and deploy " .. CargoType .. ". Your co-pilot will provide you with the directions (required flight angle in degrees) and the distance (in km) to the deployment zone." - self.CargoType = CargoType - self.GoalVerb = CargoType .. " " .. self.GoalVerb - self.Stages = { STAGE_CARGO_INIT:New(), STAGE_CARGO_LOAD:New(), STAGEBRIEF:New(), STAGESTART:New(), STAGEROUTE:New(), STAGELANDING:New(), STAGELANDED:New(), STAGEUNLOAD:New(), STAGEDONE:New() } - self.SetStage( self, 1 ) - end - - return self -end - -function DEPLOYTASK:ToZone( LandingZone ) - self:F() - - self.LandingZones.LandingZoneNames[LandingZone.CargoZoneName] = LandingZone.CargoZoneName - self.LandingZones.LandingZones[LandingZone.CargoZoneName] = LandingZone - - return self -end - - -function DEPLOYTASK:InitCargo( InitCargos ) - self:F( { InitCargos } ) - - if type( InitCargos ) == "table" then - self.Cargos.InitCargos = InitCargos - else - self.Cargos.InitCargos = { InitCargos } - end - - return self -end - - -function DEPLOYTASK:LoadCargo( LoadCargos ) - self:F( { LoadCargos } ) - - if type( LoadCargos ) == "table" then - self.Cargos.LoadCargos = LoadCargos - else - self.Cargos.LoadCargos = { LoadCargos } - end - - return self -end - - ---- When the cargo is unloaded, it will move to the target zone name. --- @param string TargetZoneName Name of the Zone to where the Cargo should move after unloading. -function DEPLOYTASK:SetCargoTargetZoneName( TargetZoneName ) - self:F() - - local Valid = true - - Valid = routines.ValidateString( TargetZoneName, "TargetZoneName", Valid ) - - if Valid then - self.TargetZoneName = TargetZoneName - end - - return Valid - -end - -function DEPLOYTASK:AddCargoMenus( Client, Cargos, TransportRadius ) - self:F() - - local ClientGroupID = Client:GetClientGroupID() - - self:T( ClientGroupID ) - - for CargoID, Cargo in pairs( Cargos ) do - - self:T( { Cargo.ClassName, Cargo.CargoName, Cargo.CargoType, Cargo.CargoWeight } ) - - if Cargo:IsStatusLoaded() and Client == Cargo:IsLoadedInClient() then - - if Client._Menus[Cargo.CargoType] == nil then - Client._Menus[Cargo.CargoType] = {} - end - - if not Client._Menus[Cargo.CargoType].DeployMenu then - Client._Menus[Cargo.CargoType].DeployMenu = missionCommands.addSubMenuForGroup( - ClientGroupID, - self.TEXT[1] .. " " .. Cargo.CargoType, - nil - ) - self:T( 'Added DeployMenu ' .. self.TEXT[1] ) - end - - if Client._Menus[Cargo.CargoType].DeploySubMenus == nil then - Client._Menus[Cargo.CargoType].DeploySubMenus = {} - end - - if Client._Menus[Cargo.CargoType].DeployMenu == nil then - self:T( 'deploymenu is nil' ) - end - - Client._Menus[Cargo.CargoType].DeploySubMenus[ #Client._Menus[Cargo.CargoType].DeploySubMenus + 1 ] = missionCommands.addCommandForGroup( - ClientGroupID, - Cargo.CargoName .. " ( " .. Cargo.CargoWeight .. "kg )", - Client._Menus[Cargo.CargoType].DeployMenu, - self.MenuAction, - { ReferenceTask = self, CargoTask = Cargo } - ) - self:T( 'Added DeploySubMenu ' .. Cargo.CargoType .. ":" .. Cargo.CargoName .. " ( " .. Cargo.CargoWeight .. "kg )" ) - end - end - -end - -function DEPLOYTASK:RemoveCargoMenus( Client ) - self:F() - - local ClientGroupID = Client:GetClientGroupID() - self:T( ClientGroupID ) - - for MenuID, MenuData in pairs( Client._Menus ) do - if MenuData.DeploySubMenus ~= nil then - for SubMenuID, SubMenuData in pairs( MenuData.DeploySubMenus ) do - missionCommands.removeItemForGroup( ClientGroupID, SubMenuData ) - self:T( "Removed DeploySubMenu " ) - SubMenuData = nil - end - end - if MenuData.DeployMenu then - missionCommands.removeItemForGroup( ClientGroupID, MenuData.DeployMenu ) - self:T( "Removed DeployMenu " ) - MenuData.DeployMenu = nil - end - end - -end ---- A NOTASK is a dummy activity... But it will show a Mission Briefing... --- @module NOTASK - ---- The NOTASK class --- @type -NOTASK = { - ClassName = "NOTASK", -} - ---- Creates a new NOTASK. -function NOTASK:New() - local self = BASE:Inherit( self, TASK:New() ) - self:F() - - local Valid = true - - if Valid then - self.Name = 'Nothing' - self.TaskBriefing = "Task: Execute your mission." - self.Stages = { STAGEBRIEF:New(), STAGESTART:New(), STAGEDONE:New() } - self.SetStage( self, 1 ) - end - - return self -end ---- A ROUTETASK orchestrates the travel to a specific zone defined within the ME. --- @module ROUTETASK - ---- The ROUTETASK class --- @type -ROUTETASK = { - ClassName = "ROUTETASK", - GoalVerb = "Route", -} - ---- Creates a new ROUTETASK. --- @param table{sring,...}|string LandingZones Table of Zone Names where the target is located. --- @param string TaskBriefing (optional) Defines a text describing the briefing of the task. --- @return ROUTETASK -function ROUTETASK:New( LandingZones, TaskBriefing ) - local self = BASE:Inherit( self, TASK:New() ) - self:F( { LandingZones, TaskBriefing } ) - - local Valid = true - - Valid = routines.ValidateZone( LandingZones, "LandingZones", Valid ) - - if Valid then - self.Name = 'Route To Zone' - if TaskBriefing then - self.TaskBriefing = TaskBriefing .. " Your co-pilot will provide you with the directions (required flight angle in degrees) and the distance (in km) to the target objective." - else - self.TaskBriefing = "Task: Fly to specified zone(s). Your co-pilot will provide you with the directions (required flight angle in degrees) and the distance (in km) to the target objective." - end - if type( LandingZones ) == "table" then - self.LandingZones = LandingZones - else - self.LandingZones = { LandingZones } - end - self.Stages = { STAGEBRIEF:New(), STAGESTART:New(), STAGEROUTE:New(), STAGEARRIVE:New(), STAGEDONE:New() } - self.SetStage( self, 1 ) - end - - return self -end - ---- A MISSION is the main owner of a Mission orchestration within MOOSE . The Mission framework orchestrates @{CLIENT}s, @{TASK}s, @{STAGE}s etc. --- A @{CLIENT} needs to be registered within the @{MISSION} through the function @{AddClient}. A @{TASK} needs to be registered within the @{MISSION} through the function @{AddTask}. --- @module Mission - ---- The MISSION class --- @type MISSION --- @extends Base#BASE --- @field #MISSION.Clients _Clients --- @field Menu#MENU_COALITION MissionMenu --- @field #string MissionBriefing -MISSION = { - ClassName = "MISSION", - Name = "", - MissionStatus = "PENDING", - _Clients = {}, - Tasks = {}, - TaskMenus = {}, - TaskCategoryMenus = {}, - TaskTypeMenus = {}, - _ActiveTasks = {}, - GoalFunction = nil, - MissionReportTrigger = 0, - MissionProgressTrigger = 0, - MissionReportShow = false, - MissionReportFlash = false, - MissionTimeInterval = 0, - MissionCoalition = "", - SUCCESS = 1, - FAILED = 2, - REPEAT = 3, - _GoalTasks = {} -} - ---- @type MISSION.Clients --- @list - -function MISSION:Meta() - - local self = BASE:Inherit( self, BASE:New() ) - - return self -end - ---- This is the main MISSION declaration method. Each Mission is like the master or a Mission orchestration between, Clients, Tasks, Stages etc. --- @param #MISSION self --- @param #string MissionName is the name of the mission. This name will be used to reference the status of each mission by the players. --- @param #string MissionPriority is a string indicating the "priority" of the Mission. f.e. "Primary", "Secondary" or "First", "Second". It is free format and up to the Mission designer to choose. There are no rules behind this field. --- @param #string MissionBriefing is a string indicating the mission briefing to be shown when a player joins a @{CLIENT}. --- @param DCSCoalitionObject#coalition MissionCoalition is a string indicating the coalition or party to which this mission belongs to. It is free format and can be chosen freely by the mission designer. Note that this field is not to be confused with the coalition concept of the ME. Examples of a Mission Coalition could be "NATO", "CCCP", "Intruders", "Terrorists"... --- @return #MISSION self -function MISSION:New( MissionName, MissionPriority, MissionBriefing, MissionCoalition ) - - self = MISSION:Meta() - self:T( { MissionName, MissionPriority, MissionBriefing, MissionCoalition } ) - - self.Name = MissionName - self.MissionPriority = MissionPriority - self.MissionBriefing = MissionBriefing - self.MissionCoalition = MissionCoalition - - return self -end - ---- Gets the mission name. --- @param #MISSION self --- @return #MISSION self -function MISSION:GetName() - return self.Name -end - ---- Add a scoring to the mission. --- @param #MISSION self --- @return #MISSION self -function MISSION:AddScoring( Scoring ) - self.Scoring = Scoring - return self -end - ---- Get the scoring object of a mission. --- @param #MISSION self --- @return #SCORING Scoring -function MISSION:GetScoring() - return self.Scoring -end - - ---- Sets the Planned Task menu. --- @param #MISSION self -function MISSION:SetPlannedMenu() - - for _, Task in pairs( self.Tasks ) do - local Task = Task -- Task#TASK_BASE - Task:RemoveMenu() - Task:SetPlannedMenu() - end - -end - ---- Sets the Assigned Task menu. --- @param #MISSION self --- @param Task#TASK_BASE Task --- @param #string MenuText The menu text. --- @return #MISSION self -function MISSION:SetAssignedMenu( Task ) - - for _, Task in pairs( self.Tasks ) do - local Task = Task -- Task#TASK_BASE - Task:RemoveMenu() - Task:SetAssignedMenu() - end - -end - ---- Removes a Task menu. --- @param #MISSION self --- @param Task#TASK_BASE Task --- @return #MISSION self -function MISSION:RemoveTaskMenu( Task ) - - Task:RemoveMenu() -end - - ---- Gets the mission menu for the coalition. --- @param #MISSION self --- @param Group#GROUP TaskGroup --- @return Menu#MENU_COALITION self -function MISSION:GetMissionMenu( TaskGroup ) - local TaskGroupName = TaskGroup:GetName() - return self.MenuMission[TaskGroupName] -end - - ---- Clears the mission menu for the coalition. --- @param #MISSION self --- @return #MISSION self -function MISSION:ClearMissionMenu() - self.MissionMenu:Remove() - self.MissionMenu = nil -end - ---- Get the TASK identified by the TaskNumber from the Mission. This function is useful in GoalFunctions. --- @param #string TaskIndex is the Index of the @{Task} within the @{Mission}. --- @param #number TaskID is the ID of the @{Task} within the @{Mission}. --- @return Task#TASK_BASE The Task --- @return #nil Returns nil if no task was found. -function MISSION:GetTask( TaskName ) - self:F( { TaskName } ) - - return self.Tasks[TaskName] -end - - ---- Register a @{Task} to be completed within the @{Mission}. --- Note that there can be multiple @{Task}s registered to be completed. --- Each Task can be set a certain Goals. The Mission will not be completed until all Goals are reached. --- @param #MISSION self --- @param Task#TASK_BASE Task is the @{Task} object. --- @return Task#TASK_BASE The task added. -function MISSION:AddTask( Task ) - - local TaskName = Task:GetTaskName() - self:F( TaskName ) - self.Tasks[TaskName] = self.Tasks[TaskName] or { n = 0 } - - self.Tasks[TaskName] = Task - - return Task -end - ---- Removes a @{Task} to be completed within the @{Mission}. --- Note that there can be multiple @{Task}s registered to be completed. --- Each Task can be set a certain Goals. The Mission will not be completed until all Goals are reached. --- @param #MISSION self --- @param Task#TASK_BASE Task is the @{Task} object. --- @return #nil The cleaned Task reference. -function MISSION:RemoveTask( Task ) - - local TaskName = Task:GetTaskName() - self:F( TaskName ) - self.Tasks[TaskName] = self.Tasks[TaskName] or { n = 0 } - - Task:CleanUp() -- Cleans all events and sets task to nil to get Garbage Collected - - -- Ensure everything gets garbarge collected. - self.Tasks[TaskName] = nil - Task = nil - - return nil -end - ---- Return the next @{Task} ID to be completed within the @{Mission}. --- @param #MISSION self --- @param Task#TASK_BASE Task is the @{Task} object. --- @return Task#TASK_BASE The task added. -function MISSION:GetNextTaskID( Task ) - - local TaskName = Task:GetTaskName() - self:F( TaskName ) - self.Tasks[TaskName] = self.Tasks[TaskName] or { n = 0 } - - self.Tasks[TaskName].n = self.Tasks[TaskName].n + 1 - - return self.Tasks[TaskName].n -end - - - ---- old stuff - ---- Returns if a Mission has completed. --- @return bool -function MISSION:IsCompleted() - self:F() - return self.MissionStatus == "ACCOMPLISHED" -end - ---- Set a Mission to completed. -function MISSION:Completed() - self:F() - self.MissionStatus = "ACCOMPLISHED" - self:StatusToClients() -end - ---- Returns if a Mission is ongoing. --- treturn bool -function MISSION:IsOngoing() - self:F() - return self.MissionStatus == "ONGOING" -end - ---- Set a Mission to ongoing. -function MISSION:Ongoing() - self:F() - self.MissionStatus = "ONGOING" - --self:StatusToClients() -end - ---- Returns if a Mission is pending. --- treturn bool -function MISSION:IsPending() - self:F() - return self.MissionStatus == "PENDING" -end - ---- Set a Mission to pending. -function MISSION:Pending() - self:F() - self.MissionStatus = "PENDING" - self:StatusToClients() -end - ---- Returns if a Mission has failed. --- treturn bool -function MISSION:IsFailed() - self:F() - return self.MissionStatus == "FAILED" -end - ---- Set a Mission to failed. -function MISSION:Failed() - self:F() - self.MissionStatus = "FAILED" - self:StatusToClients() -end - ---- Send the status of the MISSION to all Clients. -function MISSION:StatusToClients() - self:F() - if self.MissionReportFlash then - for ClientID, Client in pairs( self._Clients ) do - Client:Message( self.MissionCoalition .. ' "' .. self.Name .. '": ' .. self.MissionStatus .. '! ( ' .. self.MissionPriority .. ' mission ) ', 10, "Mission Command: Mission Status") - end - end -end - ---- Handles the reporting. After certain time intervals, a MISSION report MESSAGE will be shown to All Players. -function MISSION:ReportTrigger() - self:F() - - if self.MissionReportShow == true then - self.MissionReportShow = false - return true - else - if self.MissionReportFlash == true then - if timer.getTime() >= self.MissionReportTrigger then - self.MissionReportTrigger = timer.getTime() + self.MissionTimeInterval - return true - else - return false - end - else - return false - end - end -end - ---- Report the status of all MISSIONs to all active Clients. -function MISSION:ReportToAll() - self:F() - - local AlivePlayers = '' - for ClientID, Client in pairs( self._Clients ) do - if Client:GetDCSGroup() then - if Client:GetClientGroupDCSUnit() then - if Client:GetClientGroupDCSUnit():getLife() > 0.0 then - if AlivePlayers == '' then - AlivePlayers = ' Players: ' .. Client:GetClientGroupDCSUnit():getPlayerName() - else - AlivePlayers = AlivePlayers .. ' / ' .. Client:GetClientGroupDCSUnit():getPlayerName() - end - end - end - end - end - local Tasks = self:GetTasks() - local TaskText = "" - for TaskID, TaskData in pairs( Tasks ) do - TaskText = TaskText .. " - Task " .. TaskID .. ": " .. TaskData.Name .. ": " .. TaskData:GetGoalProgress() .. "\n" - end - MESSAGE:New( self.MissionCoalition .. ' "' .. self.Name .. '": ' .. self.MissionStatus .. ' ( ' .. self.MissionPriority .. ' mission )' .. AlivePlayers .. "\n" .. TaskText:gsub("\n$",""), 10, "Mission Command: Mission Report" ):ToAll() -end - - ---- Add a goal function to a MISSION. Goal functions are called when a @{TASK} within a mission has been completed. --- @param function GoalFunction is the function defined by the mission designer to evaluate whether a certain goal has been reached after a @{TASK} finishes within the @{MISSION}. A GoalFunction must accept 2 parameters: Mission, Client, which contains the current MISSION object and the current CLIENT object respectively. --- @usage --- PatriotActivation = { --- { "US SAM Patriot Zerti", false }, --- { "US SAM Patriot Zegduleti", false }, --- { "US SAM Patriot Gvleti", false } --- } --- --- function DeployPatriotTroopsGoal( Mission, Client ) --- --- --- -- Check if the cargo is all deployed for mission success. --- for CargoID, CargoData in pairs( Mission._Cargos ) do --- if Group.getByName( CargoData.CargoGroupName ) then --- CargoGroup = Group.getByName( CargoData.CargoGroupName ) --- if CargoGroup then --- -- Check if the cargo is ready to activate --- CurrentLandingZoneID = routines.IsUnitInZones( CargoGroup:getUnits()[1], Mission:GetTask( 2 ).LandingZones ) -- The second task is the Deploytask to measure mission success upon --- if CurrentLandingZoneID then --- if PatriotActivation[CurrentLandingZoneID][2] == false then --- -- Now check if this is a new Mission Task to be completed... --- trigger.action.setGroupAIOn( Group.getByName( PatriotActivation[CurrentLandingZoneID][1] ) ) --- PatriotActivation[CurrentLandingZoneID][2] = true --- MessageToBlue( "Mission Command: Message to all airborne units! The " .. PatriotActivation[CurrentLandingZoneID][1] .. " is armed. Our air defenses are now stronger.", 60, "BLUE/PatriotDefense" ) --- MessageToRed( "Mission Command: Our satellite systems are detecting additional NATO air defenses. To all airborne units: Take care!!!", 60, "RED/PatriotDefense" ) --- Mission:GetTask( 2 ):AddGoalCompletion( "Patriots activated", PatriotActivation[CurrentLandingZoneID][1], 1 ) -- Register Patriot activation as part of mission goal. --- end --- end --- end --- end --- end --- end --- --- local Mission = MISSIONSCHEDULER.AddMission( 'NATO Transport Troops', 'Operational', 'Transport 3 groups of air defense engineers from our barracks "Gold" and "Titan" to each patriot battery control center to activate our air defenses.', 'NATO' ) --- Mission:AddGoalFunction( DeployPatriotTroopsGoal ) -function MISSION:AddGoalFunction( GoalFunction ) - self:F() - self.GoalFunction = GoalFunction -end - ---- Register a new @{CLIENT} to participate within the mission. --- @param CLIENT Client is the @{CLIENT} object. The object must have been instantiated with @{CLIENT:New}. --- @return CLIENT --- @usage --- Add a number of Client objects to the Mission. --- Mission:AddClient( CLIENT:FindByName( 'US UH-1H*HOT-Deploy Troops 1', 'Transport 3 groups of air defense engineers from our barracks "Gold" and "Titan" to each patriot battery control center to activate our air defenses.' ):Transport() ) --- Mission:AddClient( CLIENT:FindByName( 'US UH-1H*RAMP-Deploy Troops 3', 'Transport 3 groups of air defense engineers from our barracks "Gold" and "Titan" to each patriot battery control center to activate our air defenses.' ):Transport() ) --- Mission:AddClient( CLIENT:FindByName( 'US UH-1H*HOT-Deploy Troops 2', 'Transport 3 groups of air defense engineers from our barracks "Gold" and "Titan" to each patriot battery control center to activate our air defenses.' ):Transport() ) --- Mission:AddClient( CLIENT:FindByName( 'US UH-1H*RAMP-Deploy Troops 4', 'Transport 3 groups of air defense engineers from our barracks "Gold" and "Titan" to each patriot battery control center to activate our air defenses.' ):Transport() ) -function MISSION:AddClient( Client ) - self:F( { Client } ) - - local Valid = true - - if Valid then - self._Clients[Client.ClientName] = Client - end - - return Client -end - ---- Find a @{CLIENT} object within the @{MISSION} by its ClientName. --- @param CLIENT ClientName is a string defining the Client Group as defined within the ME. --- @return CLIENT --- @usage --- -- Seach for Client "Bomber" within the Mission. --- local BomberClient = Mission:FindClient( "Bomber" ) -function MISSION:FindClient( ClientName ) - self:F( { self._Clients[ClientName] } ) - return self._Clients[ClientName] -end - - ---- Get all the TASKs from the Mission. This function is useful in GoalFunctions. --- @return {TASK,...} Structure of TASKS with the @{TASK} number as the key. --- @usage --- -- Get Tasks from the Mission. --- Tasks = Mission:GetTasks() --- env.info( "Task 2 Completion = " .. Tasks[2]:GetGoalPercentage() .. "%" ) -function MISSION:GetTasks() - self:F() - - return self._Tasks -end - - ---[[ - _TransportExecuteStage: Defines the different stages of Transport unload/load execution. This table is internal and is used to control the validity of Transport load/unload timing. - - - _TransportExecuteStage.EXECUTING - - _TransportExecuteStage.SUCCESS - - _TransportExecuteStage.FAILED - ---]] -_TransportExecuteStage = { - NONE = 0, - EXECUTING = 1, - SUCCESS = 2, - FAILED = 3 -} - - ---- The MISSIONSCHEDULER is an OBJECT and is the main scheduler of ALL active MISSIONs registered within this scheduler. It's workings are considered internal and is automatically created when the Mission.lua file is included. --- @type MISSIONSCHEDULER --- @field #MISSIONSCHEDULER.MISSIONS Missions -MISSIONSCHEDULER = { - Missions = {}, - MissionCount = 0, - TimeIntervalCount = 0, - TimeIntervalShow = 150, - TimeSeconds = 14400, - TimeShow = 5 -} - ---- @type MISSIONSCHEDULER.MISSIONS --- @list <#MISSION> Mission - ---- This is the main MISSIONSCHEDULER Scheduler function. It is considered internal and is automatically created when the Mission.lua file is included. -function MISSIONSCHEDULER.Scheduler() - - - -- loop through the missions in the TransportTasks - for MissionName, MissionData in pairs( MISSIONSCHEDULER.Missions ) do - - local Mission = MissionData -- #MISSION - - if not Mission:IsCompleted() then - - -- This flag will monitor if for this mission, there are clients alive. If this flag is still false at the end of the loop, the mission status will be set to Pending (if not Failed or Completed). - local ClientsAlive = false - - for ClientID, ClientData in pairs( Mission._Clients ) do - - local Client = ClientData -- Client#CLIENT - - if Client:IsAlive() then - - -- There is at least one Client that is alive... So the Mission status is set to Ongoing. - ClientsAlive = true - - -- If this Client was not registered as Alive before: - -- 1. We register the Client as Alive. - -- 2. We initialize the Client Tasks and make a link to the original Mission Task. - -- 3. We initialize the Cargos. - -- 4. We flag the Mission as Ongoing. - if not Client.ClientAlive then - Client.ClientAlive = true - Client.ClientBriefingShown = false - for TaskNumber, Task in pairs( Mission._Tasks ) do - -- Note that this a deepCopy. Each client must have their own Tasks with own Stages!!! - Client._Tasks[TaskNumber] = routines.utils.deepCopy( Mission._Tasks[TaskNumber] ) - -- Each MissionTask must point to the original Mission. - Client._Tasks[TaskNumber].MissionTask = Mission._Tasks[TaskNumber] - Client._Tasks[TaskNumber].Cargos = Mission._Tasks[TaskNumber].Cargos - Client._Tasks[TaskNumber].LandingZones = Mission._Tasks[TaskNumber].LandingZones - end - - Mission:Ongoing() - end - - - -- For each Client, check for each Task the state and evolve the mission. - -- This flag will indicate if the Task of the Client is Complete. - local TaskComplete = false - - for TaskNumber, Task in pairs( Client._Tasks ) do - - if not Task.Stage then - Task:SetStage( 1 ) - end - - - local TransportTime = timer.getTime() - - if not Task:IsDone() then - - if Task:Goal() then - Task:ShowGoalProgress( Mission, Client ) - end - - --env.info( 'Scheduler: Mission = ' .. Mission.Name .. ' / Client = ' .. Client.ClientName .. ' / Task = ' .. Task.Name .. ' / Stage = ' .. Task.ActiveStage .. ' - ' .. Task.Stage.Name .. ' - ' .. Task.Stage.StageType ) - - -- Action - if Task:StageExecute() then - Task.Stage:Execute( Mission, Client, Task ) - end - - -- Wait until execution is finished - if Task.ExecuteStage == _TransportExecuteStage.EXECUTING then - Task.Stage:Executing( Mission, Client, Task ) - end - - -- Validate completion or reverse to earlier stage - if Task.Time + Task.Stage.WaitTime <= TransportTime then - Task:SetStage( Task.Stage:Validate( Mission, Client, Task ) ) - end - - if Task:IsDone() then - --env.info( 'Scheduler: Mission '.. Mission.Name .. ' Task ' .. Task.Name .. ' Stage ' .. Task.Stage.Name .. ' done. TaskComplete = ' .. string.format ( "%s", TaskComplete and "true" or "false" ) ) - TaskComplete = true -- when a task is not yet completed, a mission cannot be completed - - else - -- break only if this task is not yet done, so that future task are not yet activated. - TaskComplete = false -- when a task is not yet completed, a mission cannot be completed - --env.info( 'Scheduler: Mission "'.. Mission.Name .. '" Task "' .. Task.Name .. '" Stage "' .. Task.Stage.Name .. '" break. TaskComplete = ' .. string.format ( "%s", TaskComplete and "true" or "false" ) ) - break - end - - if TaskComplete then - - if Mission.GoalFunction ~= nil then - Mission.GoalFunction( Mission, Client ) - end - if MISSIONSCHEDULER.Scoring then - MISSIONSCHEDULER.Scoring:_AddMissionTaskScore( Client:GetClientGroupDCSUnit(), Mission.Name, 25 ) - end - --- if not Mission:IsCompleted() then --- end - end - end - end - - local MissionComplete = true - for TaskNumber, Task in pairs( Mission._Tasks ) do - if Task:Goal() then --- Task:ShowGoalProgress( Mission, Client ) - if Task:IsGoalReached() then - else - MissionComplete = false - end - else - MissionComplete = false -- If there is no goal, the mission should never be ended. The goal status will be set somewhere else. - end - end - - if MissionComplete then - Mission:Completed() - if MISSIONSCHEDULER.Scoring then - MISSIONSCHEDULER.Scoring:_AddMissionScore( Mission.Name, 100 ) - end - else - if TaskComplete then - -- Reset for new tasking of active client - Client.ClientAlive = false -- Reset the client tasks. - end - end - - - else - if Client.ClientAlive then - env.info( 'Scheduler: Client "' .. Client.ClientName .. '" is inactive.' ) - Client.ClientAlive = false - - -- This is tricky. If we sanitize Client._Tasks before sanitizing Client._Tasks[TaskNumber].MissionTask, then the original MissionTask will be sanitized, and will be lost within the garbage collector. - -- So first sanitize Client._Tasks[TaskNumber].MissionTask, after that, sanitize only the whole _Tasks structure... - --Client._Tasks[TaskNumber].MissionTask = nil - --Client._Tasks = nil - end - end - end - - -- If all Clients of this Mission are not activated, then the Mission status needs to be put back into Pending status. - -- But only if the Mission was Ongoing. In case the Mission is Completed or Failed, the Mission status may not be changed. In these cases, this will be the last run of this Mission in the Scheduler. - if ClientsAlive == false then - if Mission:IsOngoing() then - -- Mission status back to pending... - Mission:Pending() - end - end - end - - Mission:StatusToClients() - - if Mission:ReportTrigger() then - Mission:ReportToAll() - end - end - - return true -end - ---- Start the MISSIONSCHEDULER. -function MISSIONSCHEDULER.Start() - if MISSIONSCHEDULER ~= nil then - --MISSIONSCHEDULER.SchedulerId = routines.scheduleFunction( MISSIONSCHEDULER.Scheduler, { }, 0, 2 ) - MISSIONSCHEDULER.SchedulerId = SCHEDULER:New( nil, MISSIONSCHEDULER.Scheduler, { }, 0, 2 ) - end -end - ---- Stop the MISSIONSCHEDULER. -function MISSIONSCHEDULER.Stop() - if MISSIONSCHEDULER.SchedulerId then - routines.removeFunction(MISSIONSCHEDULER.SchedulerId) - MISSIONSCHEDULER.SchedulerId = nil - end -end - ---- This is the main MISSION declaration method. Each Mission is like the master or a Mission orchestration between, Clients, Tasks, Stages etc. --- @param Mission is the MISSION object instantiated by @{MISSION:New}. --- @return MISSION --- @usage --- -- Declare a mission. --- Mission = MISSION:New( 'Russia Transport Troops SA-6', --- 'Operational', --- 'Transport troops from the control center to one of the SA-6 SAM sites to activate their operation.', --- 'Russia' ) --- MISSIONSCHEDULER:AddMission( Mission ) -function MISSIONSCHEDULER.AddMission( Mission ) - MISSIONSCHEDULER.Missions[Mission.Name] = Mission - MISSIONSCHEDULER.MissionCount = MISSIONSCHEDULER.MissionCount + 1 - -- Add an overall AI Client for the AI tasks... This AI Client will facilitate the Events in the background for each Task. - --MissionAdd:AddClient( CLIENT:Register( 'AI' ) ) - - return Mission -end - ---- Remove a MISSION from the MISSIONSCHEDULER. --- @param MissionName is the name of the MISSION given at declaration using @{AddMission}. --- @usage --- -- Declare a mission. --- Mission = MISSION:New( 'Russia Transport Troops SA-6', --- 'Operational', --- 'Transport troops from the control center to one of the SA-6 SAM sites to activate their operation.', --- 'Russia' ) --- MISSIONSCHEDULER:AddMission( Mission ) --- --- -- Now remove the Mission. --- MISSIONSCHEDULER:RemoveMission( 'Russia Transport Troops SA-6' ) -function MISSIONSCHEDULER.RemoveMission( MissionName ) - MISSIONSCHEDULER.Missions[MissionName] = nil - MISSIONSCHEDULER.MissionCount = MISSIONSCHEDULER.MissionCount - 1 -end - ---- Find a MISSION within the MISSIONSCHEDULER. --- @param MissionName is the name of the MISSION given at declaration using @{AddMission}. --- @return MISSION --- @usage --- -- Declare a mission. --- Mission = MISSION:New( 'Russia Transport Troops SA-6', --- 'Operational', --- 'Transport troops from the control center to one of the SA-6 SAM sites to activate their operation.', --- 'Russia' ) --- MISSIONSCHEDULER:AddMission( Mission ) --- --- -- Now find the Mission. --- MissionFind = MISSIONSCHEDULER:FindMission( 'Russia Transport Troops SA-6' ) -function MISSIONSCHEDULER.FindMission( MissionName ) - return MISSIONSCHEDULER.Missions[MissionName] -end - --- Internal function used by the MISSIONSCHEDULER menu. -function MISSIONSCHEDULER.ReportMissionsShow( ) - for MissionName, Mission in pairs( MISSIONSCHEDULER.Missions ) do - Mission.MissionReportShow = true - Mission.MissionReportFlash = false - end -end - --- Internal function used by the MISSIONSCHEDULER menu. -function MISSIONSCHEDULER.ReportMissionsFlash( TimeInterval ) - local Count = 0 - for MissionName, Mission in pairs( MISSIONSCHEDULER.Missions ) do - Mission.MissionReportShow = false - Mission.MissionReportFlash = true - Mission.MissionReportTrigger = timer.getTime() + Count * TimeInterval - Mission.MissionTimeInterval = MISSIONSCHEDULER.MissionCount * TimeInterval - env.info( "TimeInterval = " .. Mission.MissionTimeInterval ) - Count = Count + 1 - end -end - --- Internal function used by the MISSIONSCHEDULER menu. -function MISSIONSCHEDULER.ReportMissionsHide( Prm ) - for MissionName, Mission in pairs( MISSIONSCHEDULER.Missions ) do - Mission.MissionReportShow = false - Mission.MissionReportFlash = false - end -end - ---- Enables a MENU option in the communications menu under F10 to control the status of the active missions. --- This function should be called only once when starting the MISSIONSCHEDULER. -function MISSIONSCHEDULER.ReportMenu() - local ReportMenu = SUBMENU:New( 'Status' ) - local ReportMenuShow = COMMANDMENU:New( 'Show Report Missions', ReportMenu, MISSIONSCHEDULER.ReportMissionsShow, 0 ) - local ReportMenuFlash = COMMANDMENU:New('Flash Report Missions', ReportMenu, MISSIONSCHEDULER.ReportMissionsFlash, 120 ) - local ReportMenuHide = COMMANDMENU:New( 'Hide Report Missions', ReportMenu, MISSIONSCHEDULER.ReportMissionsHide, 0 ) -end - ---- Show the remaining mission time. -function MISSIONSCHEDULER:TimeShow() - self.TimeIntervalCount = self.TimeIntervalCount + 1 - if self.TimeIntervalCount >= self.TimeTriggerShow then - local TimeMsg = string.format("%00d", ( self.TimeSeconds / 60 ) - ( timer.getTime() / 60 )) .. ' minutes left until mission reload.' - MESSAGE:New( TimeMsg, self.TimeShow, "Mission time" ):ToAll() - self.TimeIntervalCount = 0 - end -end - -function MISSIONSCHEDULER:Time( TimeSeconds, TimeIntervalShow, TimeShow ) - - self.TimeIntervalCount = 0 - self.TimeSeconds = TimeSeconds - self.TimeIntervalShow = TimeIntervalShow - self.TimeShow = TimeShow -end - ---- Adds a mission scoring to the game. -function MISSIONSCHEDULER:Scoring( Scoring ) - - self.Scoring = Scoring -end - --- The CLEANUP class keeps an area clean of crashing or colliding airplanes. It also prevents airplanes from firing within this area. -- @module CleanUp -- @author Flightcontrol @@ -20572,7 +17417,7 @@ end --- The CLEANUP class. -- @type CLEANUP --- @extends Base#BASE +-- @extends Core.Base#BASE CLEANUP = { ClassName = "CLEANUP", ZoneNames = {}, @@ -20613,7 +17458,7 @@ end --- Destroys a group from the simulator, but checks first if it is still existing! -- @param #CLEANUP self --- @param DCSGroup#Group GroupObject The object to be destroyed. +-- @param Dcs.DCSWrapper.Group#Group GroupObject The object to be destroyed. -- @param #string CleanUpGroupName The groupname... function CLEANUP:_DestroyGroup( GroupObject, CleanUpGroupName ) self:F( { GroupObject, CleanUpGroupName } ) @@ -20624,9 +17469,9 @@ function CLEANUP:_DestroyGroup( GroupObject, CleanUpGroupName ) end end ---- Destroys a @{DCSUnit#Unit} from the simulator, but checks first if it is still existing! +--- Destroys a @{Dcs.DCSWrapper.Unit#Unit} from the simulator, but checks first if it is still existing! -- @param #CLEANUP self --- @param DCSUnit#Unit CleanUpUnit The object to be destroyed. +-- @param Dcs.DCSWrapper.Unit#Unit CleanUpUnit The object to be destroyed. -- @param #string CleanUpUnitName The Unit name ... function CLEANUP:_DestroyUnit( CleanUpUnit, CleanUpUnitName ) self:F( { CleanUpUnit, CleanUpUnitName } ) @@ -20651,10 +17496,10 @@ function CLEANUP:_DestroyUnit( CleanUpUnit, CleanUpUnitName ) end end --- TODO check DCSTypes#Weapon +-- TODO check Dcs.DCSTypes#Weapon --- Destroys a missile from the simulator, but checks first if it is still existing! -- @param #CLEANUP self --- @param DCSTypes#Weapon MissileObject +-- @param Dcs.DCSTypes#Weapon MissileObject function CLEANUP:_DestroyMissile( MissileObject ) self:F( { MissileObject } ) @@ -20696,7 +17541,7 @@ end --- Detects if a crash event occurs. -- Crashed units go into a CleanUpList for removal. -- @param #CLEANUP self --- @param DCSTypes#Event event +-- @param Dcs.DCSTypes#Event event function CLEANUP:_EventCrash( Event ) self:F( { Event } ) @@ -20719,7 +17564,7 @@ end --- Detects if a unit shoots a missile. -- If this occurs within one of the zones, then the weapon used must be destroyed. -- @param #CLEANUP self --- @param DCSTypes#Event event +-- @param Dcs.DCSTypes#Event event function CLEANUP:_EventShot( Event ) self:F( { Event } ) @@ -20736,7 +17581,7 @@ end --- Detects if the Unit has an S_EVENT_HIT within the given ZoneNames. If this is the case, destroy the unit. -- @param #CLEANUP self --- @param DCSTypes#Event event +-- @param Dcs.DCSTypes#Event event function CLEANUP:_EventHitCleanUp( Event ) self:F( { Event } ) @@ -20761,7 +17606,7 @@ function CLEANUP:_EventHitCleanUp( Event ) end end ---- Add the @{DCSUnit#Unit} to the CleanUpList for CleanUp. +--- Add the @{Dcs.DCSWrapper.Unit#Unit} to the CleanUpList for CleanUp. function CLEANUP:_AddForCleanUp( CleanUpUnit, CleanUpUnitName ) self:F( { CleanUpUnit, CleanUpUnitName } ) @@ -20779,7 +17624,7 @@ end --- Detects if the Unit has an S_EVENT_ENGINE_SHUTDOWN or an S_EVENT_HIT within the given ZoneNames. If this is the case, add the Group to the CLEANUP List. -- @param #CLEANUP self --- @param DCSTypes#Event event +-- @param Dcs.DCSTypes#Event event function CLEANUP:_EventAddForCleanUp( Event ) if Event.IniDCSUnit then @@ -20882,7 +17727,7 @@ end --- This module contains the SPAWN class. -- --- 1) @{Spawn#SPAWN} class, extends @{Base#BASE} +-- 1) @{Functional.Spawn#SPAWN} class, extends @{Core.Base#BASE} -- ============================================= -- The @{#SPAWN} class allows to spawn dynamically new groups, based on pre-defined initialization settings, modifying the behaviour when groups are spawned. -- For each group to be spawned, within the mission editor, a group has to be created with the "late activation flag" set. We call this group the *"Spawn Template"* of the SPAWN object. @@ -20908,9 +17753,10 @@ end -- -- 1.1) SPAWN construction methods -- ------------------------------- --- Create a new SPAWN object with the @{#SPAWN.New} or the @{#SPAWN.NewWithAlias} methods: +-- Create a new SPAWN object with the @{#SPAWN.New}() or the @{#SPAWN.NewWithAlias}() methods: -- --- * @{#SPAWN.New}: Creates a new SPAWN object taking the name of the group that represents the GROUP Template (definition). +-- * @{#SPAWN.New}(): Creates a new SPAWN object taking the name of the group that represents the GROUP Template (definition). +-- * @{#SPAWN.NewWithAlias}(): Creates a new SPAWN object taking the name of the group that represents the GROUP Template (definition), and gives each spawned @{Group} an different name. -- -- It is important to understand how the SPAWN class works internally. The SPAWN object created will contain internally a list of groups that will be spawned and that are already spawned. -- The initialization methods will modify this list of groups so that when a group gets spawned, ALL information is already prepared when spawning. This is done for performance reasons. @@ -20918,27 +17764,29 @@ end -- -- 1.2) SPAWN initialization methods -- --------------------------------- --- A spawn object will behave differently based on the usage of initialization methods: +-- A spawn object will behave differently based on the usage of **initialization** methods, which all start with the **Init** prefix: -- --- * @{#SPAWN.Limit}: Limits the amount of groups that can be alive at the same time and that can be dynamically spawned. --- * @{#SPAWN.RandomizeRoute}: Randomize the routes of spawned groups, and for air groups also optionally the height. --- * @{#SPAWN.RandomizeTemplate}: Randomize the group templates so that when a new group is spawned, a random group template is selected from one of the templates defined. --- * @{#SPAWN.Uncontrolled}: Spawn plane groups uncontrolled. --- * @{#SPAWN.Array}: Make groups visible before they are actually activated, and order these groups like a batallion in an array. --- * @{#SPAWN.InitRepeat}: Re-spawn groups when they land at the home base. Similar methods are @{#SPAWN.InitRepeatOnLanding} and @{#SPAWN.InitRepeatOnEngineShutDown}. +-- * @{#SPAWN.InitLimit}(): Limits the amount of groups that can be alive at the same time and that can be dynamically spawned. +-- * @{#SPAWN.InitRandomizeRoute}(): Randomize the routes of spawned groups, and for air groups also optionally the height. +-- * @{#SPAWN.InitRandomizeTemplate}(): Randomize the group templates so that when a new group is spawned, a random group template is selected from one of the templates defined. +-- * @{#SPAWN.InitUncontrolled}(): Spawn plane groups uncontrolled. +-- * @{#SPAWN.InitArray}(): Make groups visible before they are actually activated, and order these groups like a batallion in an array. +-- * @{#SPAWN.InitRepeat}(): Re-spawn groups when they land at the home base. Similar methods are @{#SPAWN.InitRepeatOnLanding} and @{#SPAWN.InitRepeatOnEngineShutDown}. +-- * @{#SPAWN.InitRandomizeUnits}(): Randomizes the @{Unit}s in the @{Group} that is spawned within a **radius band**, given an Outer and Inner radius. +-- * @{#SPAWN.InitRandomizeZones}(): Randomizes the spawning between a predefined list of @{Zone}s that are declared using this function. Each zone can be given a probability factor. -- -- 1.3) SPAWN spawning methods -- --------------------------- -- Groups can be spawned at different times and methods: -- --- * @{#SPAWN.Spawn}: Spawn one new group based on the last spawned index. --- * @{#SPAWN.ReSpawn}: Re-spawn a group based on a given index. --- * @{#SPAWN.SpawnScheduled}: Spawn groups at scheduled but randomized intervals. You can use @{#SPAWN.SpawnScheduleStart} and @{#SPAWN.SpawnScheduleStop} to start and stop the schedule respectively. --- * @{#SPAWN.SpawnFromVec3}: Spawn a new group from a Vec3 coordinate. (The group will can be spawned at a point in the air). --- * @{#SPAWN.SpawnFromVec2}: Spawn a new group from a Vec2 coordinate. (The group will be spawned at land height ). --- * @{#SPAWN.SpawnFromStatic}: Spawn a new group from a structure, taking the position of a @{STATIC}. --- * @{#SPAWN.SpawnFromUnit}: Spawn a new group taking the position of a @{UNIT}. --- * @{#SPAWN.SpawnInZone}: Spawn a new group in a @{ZONE}. +-- * @{#SPAWN.Spawn}(): Spawn one new group based on the last spawned index. +-- * @{#SPAWN.ReSpawn}(): Re-spawn a group based on a given index. +-- * @{#SPAWN.SpawnScheduled}(): Spawn groups at scheduled but randomized intervals. You can use @{#SPAWN.SpawnScheduleStart}() and @{#SPAWN.SpawnScheduleStop}() to start and stop the schedule respectively. +-- * @{#SPAWN.SpawnFromVec3}(): Spawn a new group from a Vec3 coordinate. (The group will can be spawned at a point in the air). +-- * @{#SPAWN.SpawnFromVec2}(): Spawn a new group from a Vec2 coordinate. (The group will be spawned at land height ). +-- * @{#SPAWN.SpawnFromStatic}(): Spawn a new group from a structure, taking the position of a @{Static}. +-- * @{#SPAWN.SpawnFromUnit}(): Spawn a new group taking the position of a @{Unit}. +-- * @{#SPAWN.SpawnInZone}(): Spawn a new group in a @{Zone}. -- -- Note that @{#SPAWN.Spawn} and @{#SPAWN.ReSpawn} return a @{GROUP#GROUP.New} object, that contains a reference to the DCSGroup object. -- You can use the @{GROUP} object to do further actions with the DCSGroup. @@ -20949,32 +17797,132 @@ end -- Every time a SPAWN object spawns a new GROUP object, a reference to the GROUP object is added to an internal table of GROUPS. -- SPAWN provides methods to iterate through that internal GROUP object reference table: -- --- * @{#SPAWN.GetFirstAliveGroup}: Will find the first alive GROUP it has spawned, and return the alive GROUP object and the first Index where the first alive GROUP object has been found. --- * @{#SPAWN.GetNextAliveGroup}: Will find the next alive GROUP object from a given Index, and return a reference to the alive GROUP object and the next Index where the alive GROUP has been found. --- * @{#SPAWN.GetLastAliveGroup}: Will find the last alive GROUP object, and will return a reference to the last live GROUP object and the last Index where the last alive GROUP object has been found. +-- * @{#SPAWN.GetFirstAliveGroup}(): Will find the first alive GROUP it has spawned, and return the alive GROUP object and the first Index where the first alive GROUP object has been found. +-- * @{#SPAWN.GetNextAliveGroup}(): Will find the next alive GROUP object from a given Index, and return a reference to the alive GROUP object and the next Index where the alive GROUP has been found. +-- * @{#SPAWN.GetLastAliveGroup}(): Will find the last alive GROUP object, and will return a reference to the last live GROUP object and the last Index where the last alive GROUP object has been found. -- --- You can use the methods @{#SPAWN.GetFirstAliveGroup} and sequently @{#SPAWN.GetNextAliveGroup} to iterate through the alive GROUPS within the SPAWN object, and to actions... See the respective methods for an example. --- The method @{#SPAWN.GetGroupFromIndex} will return the GROUP object reference from the given Index, dead or alive... +-- You can use the methods @{#SPAWN.GetFirstAliveGroup}() and sequently @{#SPAWN.GetNextAliveGroup}() to iterate through the alive GROUPS within the SPAWN object, and to actions... See the respective methods for an example. +-- The method @{#SPAWN.GetGroupFromIndex}() will return the GROUP object reference from the given Index, dead or alive... -- -- 1.5) SPAWN object cleaning -- -------------------------- -- Sometimes, it will occur during a mission run-time, that ground or especially air objects get damaged, and will while being damged stop their activities, while remaining alive. -- In such cases, the SPAWN object will just sit there and wait until that group gets destroyed, but most of the time it won't, -- and it may occur that no new groups are or can be spawned as limits are reached. --- To prevent this, a @{#SPAWN.CleanUp} initialization method has been defined that will silently monitor the status of each spawned group. +-- To prevent this, a @{#SPAWN.InitCleanUp}() initialization method has been defined that will silently monitor the status of each spawned group. -- Once a group has a velocity = 0, and has been waiting for a defined interval, that group will be cleaned or removed from run-time. -- There is a catch however :-) If a damaged group has returned to an airbase within the coalition, that group will not be considered as "lost"... -- In such a case, when the inactive group is cleaned, a new group will Re-spawned automatically. -- This models AI that has succesfully returned to their airbase, to restart their combat activities. --- Check the @{#SPAWN.CleanUp} for further info. +-- Check the @{#SPAWN.InitCleanUp}() for further info. +-- +-- 1.6) Catch the @{Group} spawn event in a callback function! +-- ----------------------------------------------------------- +-- When using the SpawnScheduled method, new @{Group}s are created following the schedule timing parameters. +-- When a new @{Group} is spawned, you maybe want to execute actions with that group spawned at the spawn event. +-- To SPAWN class supports this functionality through the @{#SPAWN.OnSpawnGroup}( **function( SpawnedGroup ) end ** ) method, which takes a function as a parameter that you can define locally. +-- Whenever a new @{Group} is spawned, the given function is called, and the @{Group} that was just spawned, is given as a parameter. +-- As a result, your spawn event handling function requires one parameter to be declared, which will contain the spawned @{Group} object. +-- A coding example is provided at the description of the @{#SPAWN.OnSpawnGroup}( **function( SpawnedGroup ) end ** ) method. +-- +-- ==== +-- +-- **API CHANGE HISTORY** +-- ====================== +-- +-- The underlying change log documents the API changes. Please read this carefully. The following notation is used: +-- +-- * **Added** parts are expressed in bold type face. +-- * _Removed_ parts are expressed in italic type face. +-- +-- Hereby the change log: +-- +-- 2016-08-15: SPAWN:**InitCleanUp**( SpawnCleanUpInterval ) replaces SPAWN:_CleanUp_( SpawnCleanUpInterval ) +-- +-- * Want to ensure that the methods starting with **Init** are the first called methods before any _Spawn_ method is called! +-- * This notation makes it now more clear which methods are initialization methods and which methods are Spawn enablement methods. +-- +-- 2016-08-15: SPAWN:**InitRandomizeZones( SpawnZones )** added. +-- +-- * This method provides the functionality to randomize the spawning of the Groups at a given list of zones of different types. +-- +-- 2016-08-14: SPAWN:**OnSpawnGroup**( SpawnCallBackFunction, ... ) replaces SPAWN:_SpawnFunction_( SpawnCallBackFunction, ... ). +-- +-- 2016-08-14: SPAWN.SpawnInZone( Zone, __RandomizeGroup__, SpawnIndex ) replaces SpawnInZone( Zone, _RandomizeUnits, OuterRadius, InnerRadius,_ SpawnIndex ). +-- +-- * The RandomizeUnits, OuterRadius and InnerRadius have been replaced with a new method @{#SPAWN.InitRandomizeUnits}( RandomizeUnits, OuterRadius, InnerRadius ). +-- * A new parameter RandomizeGroup to reflect the randomization of the starting position of the Spawned @{Group}. +-- +-- 2016-08-14: SPAWN.SpawnFromVec3( Vec3, SpawnIndex ) replaces SpawnFromVec3( Vec3, _RandomizeUnits, OuterRadius, InnerRadius,_ SpawnIndex ): +-- +-- * The RandomizeUnits, OuterRadius and InnerRadius have been replaced with a new method @{#SPAWN.InitRandomizeUnits}( RandomizeUnits, OuterRadius, InnerRadius ). +-- * A new parameter RandomizeGroup to reflect the randomization of the starting position of the Spawned @{Group}. +-- +-- 2016-08-14: SPAWN.SpawnFromVec2( Vec2, SpawnIndex ) replaces SpawnFromVec2( Vec2, _RandomizeUnits, OuterRadius, InnerRadius,_ SpawnIndex ): +-- +-- * The RandomizeUnits, OuterRadius and InnerRadius have been replaced with a new method @{#SPAWN.InitRandomizeUnits}( RandomizeUnits, OuterRadius, InnerRadius ). +-- * A new parameter RandomizeGroup to reflect the randomization of the starting position of the Spawned @{Group}. +-- +-- 2016-08-14: SPAWN.SpawnFromUnit( SpawnUnit, SpawnIndex ) replaces SpawnFromUnit( SpawnUnit, _RandomizeUnits, OuterRadius, InnerRadius,_ SpawnIndex ): +-- +-- * The RandomizeUnits, OuterRadius and InnerRadius have been replaced with a new method @{#SPAWN.InitRandomizeUnits}( RandomizeUnits, OuterRadius, InnerRadius ). +-- * A new parameter RandomizeGroup to reflect the randomization of the starting position of the Spawned @{Group}. +-- +-- 2016-08-14: SPAWN.SpawnFromUnit( SpawnUnit, SpawnIndex ) replaces SpawnFromStatic( SpawnStatic, _RandomizeUnits, OuterRadius, InnerRadius,_ SpawnIndex ): +-- +-- * The RandomizeUnits, OuterRadius and InnerRadius have been replaced with a new method @{#SPAWN.InitRandomizeUnits}( RandomizeUnits, OuterRadius, InnerRadius ). +-- * A new parameter RandomizeGroup to reflect the randomization of the starting position of the Spawned @{Group}. +-- +-- 2016-08-14: SPAWN.**InitRandomizeUnits( RandomizeUnits, OuterRadius, InnerRadius )** added: +-- +-- * This method enables the randomization of units at the first route point in a radius band at a spawn event. +-- +-- 2016-08-14: SPAWN.**Init**Limit( SpawnMaxUnitsAlive, SpawnMaxGroups ) replaces SPAWN._Limit_( SpawnMaxUnitsAlive, SpawnMaxGroups ): +-- +-- * Want to ensure that the methods starting with **Init** are the first called methods before any _Spawn_ method is called! +-- * This notation makes it now more clear which methods are initialization methods and which methods are Spawn enablement methods. +-- +-- 2016-08-14: SPAWN.**Init**Array( SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY ) replaces SPAWN._Array_( SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY ). +-- +-- * Want to ensure that the methods starting with **Init** are the first called methods before any _Spawn_ method is called! +-- * This notation makes it now more clear which methods are initialization methods and which methods are Spawn enablement methods. +-- +-- 2016-08-14: SPAWN.**Init**RandomizeRoute( SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight ) replaces SPAWN._RandomizeRoute_( SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight ). +-- +-- * Want to ensure that the methods starting with **Init** are the first called methods before any _Spawn_ method is called! +-- * This notation makes it now more clear which methods are initialization methods and which methods are Spawn enablement methods. +-- +-- 2016-08-14: SPAWN.**Init**RandomizeTemplate( SpawnTemplatePrefixTable ) replaces SPAWN._RandomizeTemplate_( SpawnTemplatePrefixTable ). +-- +-- * Want to ensure that the methods starting with **Init** are the first called methods before any _Spawn_ method is called! +-- * This notation makes it now more clear which methods are initialization methods and which methods are Spawn enablement methods. +-- +-- 2016-08-14: SPAWN.**Init**UnControlled() replaces SPAWN._UnControlled_(). +-- +-- * Want to ensure that the methods starting with **Init** are the first called methods before any _Spawn_ method is called! +-- * This notation makes it now more clear which methods are initialization methods and which methods are Spawn enablement methods. +-- +-- === +-- +-- AUTHORS and CONTRIBUTIONS +-- ========================= +-- +-- ### Contributions: +-- +-- * **Aaron**: Posed the idea for Group position randomization at SpawnInZone and make the Unit randomization separate from the Group randomization. +-- +-- ### Authors: +-- +-- * **FlightControl**: Design & Programming -- -- -- @module Spawn --- @author FlightControl + + --- SPAWN Class -- @type SPAWN --- @extends Base#BASE +-- @extends Core.Base#BASE -- @field ClassName -- @field #string SpawnTemplatePrefix -- @field #string SpawnAliasPrefix @@ -20982,15 +17930,18 @@ end -- @field #number MaxAliveUnits -- @field #number SpawnIndex -- @field #number MaxAliveGroups +-- @field #SPAWN.SpawnZoneTable SpawnZoneTable SPAWN = { ClassName = "SPAWN", SpawnTemplatePrefix = nil, SpawnAliasPrefix = nil, } +--- @type SPAWN.SpawnZoneTable +-- @list SpawnZone ---- Creates the main object to spawn a GROUP defined in the DCS ME. +--- Creates the main object to spawn a @{Group} defined in the DCS ME. -- @param #SPAWN self -- @param #string SpawnTemplatePrefix is the name of the Group in the ME that defines the Template. Each new group will have the name starting with SpawnTemplatePrefix. -- @return #SPAWN @@ -21065,7 +18016,7 @@ end --- Limits the Maximum amount of Units that can be alive at the same time, and the maximum amount of groups that can be spawned. -- Note that this method is exceptionally important to balance the performance of the mission. Depending on the machine etc, a mission can only process a maximum amount of units. --- If the time interval must be short, but there should not be more Units or Groups alive than a maximum amount of units, then this function should be used... +-- If the time interval must be short, but there should not be more Units or Groups alive than a maximum amount of units, then this method should be used... -- When a @{#SPAWN.New} is executed and the limit of the amount of units alive is reached, then no new spawn will happen of the group, until some of these units of the spawn object will be destroyed. -- @param #SPAWN self -- @param #number SpawnMaxUnitsAlive The maximum amount of units that can be alive at runtime. @@ -21077,8 +18028,8 @@ end -- -- NATO helicopters engaging in the battle field. -- -- This helicopter group consists of one Unit. So, this group will SPAWN maximum 2 groups simultaneously within the DCSRTE. -- -- There will be maximum 24 groups spawned during the whole mission lifetime. --- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):Limit( 2, 24 ) -function SPAWN:Limit( SpawnMaxUnitsAlive, SpawnMaxGroups ) +-- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitLimit( 2, 24 ) +function SPAWN:InitLimit( SpawnMaxUnitsAlive, SpawnMaxGroups ) self:F( { self.SpawnTemplatePrefix, SpawnMaxUnitsAlive, SpawnMaxGroups } ) self.SpawnMaxUnitsAlive = SpawnMaxUnitsAlive -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time. @@ -21106,8 +18057,8 @@ end -- -- The KA-50 has waypoints Start point ( =0 or SP ), 1, 2, 3, 4, End point (= 5 or DP). -- -- Waypoints 2 and 3 will only be randomized. The others will remain on their original position with each new spawn of the helicopter. -- -- The randomization of waypoint 2 and 3 will take place within a radius of 2000 meters. --- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):RandomizeRoute( 2, 2, 2000 ) -function SPAWN:RandomizeRoute( SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight ) +-- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitRandomizeRoute( 2, 2, 2000 ) +function SPAWN:InitRandomizeRoute( SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight ) self:F( { self.SpawnTemplatePrefix, SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight } ) self.SpawnRandomizeRoute = true @@ -21123,9 +18074,34 @@ function SPAWN:RandomizeRoute( SpawnStartPoint, SpawnEndPoint, SpawnRadius, Spaw return self end +--- Randomizes the UNITs that are spawned within a radius band given an Outer and Inner radius. +-- @param #SPAWN self +-- @param #boolean RandomizeUnits If true, SPAWN will perform the randomization of the @{UNIT}s position within the group between a given outer and inner radius. +-- @param Dcs.DCSTypes#Distance OuterRadius (optional) The outer radius in meters where the new group will be spawned. +-- @param Dcs.DCSTypes#Distance InnerRadius (optional) The inner radius in meters where the new group will NOT be spawned. +-- @return #SPAWN +-- @usage +-- -- NATO helicopters engaging in the battle field. +-- -- The KA-50 has waypoints Start point ( =0 or SP ), 1, 2, 3, 4, End point (= 5 or DP). +-- -- Waypoints 2 and 3 will only be randomized. The others will remain on their original position with each new spawn of the helicopter. +-- -- The randomization of waypoint 2 and 3 will take place within a radius of 2000 meters. +-- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitRandomizeRoute( 2, 2, 2000 ) +function SPAWN:InitRandomizeUnits( RandomizeUnits, OuterRadius, InnerRadius ) + self:F( { self.SpawnTemplatePrefix, RandomizeUnits, OuterRadius, InnerRadius } ) ---- This function is rather complicated to understand. But I'll try to explain. --- This function becomes useful when you need to spawn groups with random templates of groups defined within the mission editor, + self.SpawnRandomizeUnits = RandomizeUnits or false + self.SpawnOuterRadius = OuterRadius or 0 + self.SpawnInnerRadius = InnerRadius or 0 + + for GroupID = 1, self.SpawnMaxGroups do + self:_RandomizeRoute( GroupID ) + end + + return self +end + +--- This method is rather complicated to understand. But I'll try to explain. +-- This method becomes useful when you need to spawn groups with random templates of groups defined within the mission editor, -- but they will all follow the same Template route and have the same prefix name. -- In other words, this method randomizes between a defined set of groups the template to be used for each new spawn of a group. -- @param #SPAWN self @@ -21140,10 +18116,10 @@ end -- Spawn_US_Platoon = { 'US Tank Platoon 1', 'US Tank Platoon 2', 'US Tank Platoon 3', 'US Tank Platoon 4', 'US Tank Platoon 5', -- 'US Tank Platoon 6', 'US Tank Platoon 7', 'US Tank Platoon 8', 'US Tank Platoon 9', 'US Tank Platoon 10', -- 'US Tank Platoon 11', 'US Tank Platoon 12', 'US Tank Platoon 13' } --- Spawn_US_Platoon_Left = SPAWN:New( 'US Tank Platoon Left' ):Limit( 12, 150 ):Schedule( 200, 0.4 ):RandomizeTemplate( Spawn_US_Platoon ):RandomizeRoute( 3, 3, 2000 ) --- Spawn_US_Platoon_Middle = SPAWN:New( 'US Tank Platoon Middle' ):Limit( 12, 150 ):Schedule( 200, 0.4 ):RandomizeTemplate( Spawn_US_Platoon ):RandomizeRoute( 3, 3, 2000 ) --- Spawn_US_Platoon_Right = SPAWN:New( 'US Tank Platoon Right' ):Limit( 12, 150 ):Schedule( 200, 0.4 ):RandomizeTemplate( Spawn_US_Platoon ):RandomizeRoute( 3, 3, 2000 ) -function SPAWN:RandomizeTemplate( SpawnTemplatePrefixTable ) +-- Spawn_US_Platoon_Left = SPAWN:New( 'US Tank Platoon Left' ):InitLimit( 12, 150 ):Schedule( 200, 0.4 ):InitRandomizeTemplate( Spawn_US_Platoon ):InitRandomizeRoute( 3, 3, 2000 ) +-- Spawn_US_Platoon_Middle = SPAWN:New( 'US Tank Platoon Middle' ):InitLimit( 12, 150 ):Schedule( 200, 0.4 ):InitRandomizeTemplate( Spawn_US_Platoon ):InitRandomizeRoute( 3, 3, 2000 ) +-- Spawn_US_Platoon_Right = SPAWN:New( 'US Tank Platoon Right' ):InitLimit( 12, 150 ):Schedule( 200, 0.4 ):InitRandomizeTemplate( Spawn_US_Platoon ):InitRandomizeRoute( 3, 3, 2000 ) +function SPAWN:InitRandomizeTemplate( SpawnTemplatePrefixTable ) self:F( { self.SpawnTemplatePrefix, SpawnTemplatePrefixTable } ) self.SpawnTemplatePrefixTable = SpawnTemplatePrefixTable @@ -21156,12 +18132,33 @@ function SPAWN:RandomizeTemplate( SpawnTemplatePrefixTable ) return self end +--TODO: Add example. +--- This method provides the functionality to randomize the spawning of the Groups at a given list of zones of different types. +-- @param #SPAWN self +-- @param #table SpawnZoneTable A table with @{Zone} objects. If this table is given, then each spawn will be executed within the given list of @{Zone}s objects. +-- @return #SPAWN +-- @usage +-- -- NATO Tank Platoons invading Gori. +-- -- Choose between 3 different zones for each new SPAWN the Group to be executed, regardless of the zone type. +function SPAWN:InitRandomizeZones( SpawnZoneTable ) + self:F( { self.SpawnTemplatePrefix, SpawnZoneTable } ) + + self.SpawnZoneTable = SpawnZoneTable + self.SpawnRandomizeZones = true + + for SpawnGroupID = 1, self.SpawnMaxGroups do + self:_RandomizeZones( SpawnGroupID ) + end + + return self +end + --- For planes and helicopters, when these groups go home and land on their home airbases and farps, they normally would taxi to the parking spot, shut-down their engines and wait forever until the Group is removed by the runtime environment. --- This function is used to re-spawn automatically (so no extra call is needed anymore) the same group after it has landed. +-- This method is used to re-spawn automatically (so no extra call is needed anymore) the same group after it has landed. -- This will enable a spawned group to be re-spawned after it lands, until it is destroyed... -- Note: When the group is respawned, it will re-spawn from the original airbase where it took off. -- So ensure that the routes for groups that respawn, always return to the original airbase, or players may get confused ... @@ -21170,7 +18167,7 @@ end -- @usage -- -- RU Su-34 - AI Ship Attack -- -- Re-SPAWN the Group(s) after each landing and Engine Shut-Down automatically. --- SpawnRU_SU34 = SPAWN:New( 'TF1 RU Su-34 Krymsk@AI - Attack Ships' ):Schedule( 2, 3, 1800, 0.4 ):SpawnUncontrolled():RandomizeRoute( 1, 1, 3000 ):RepeatOnEngineShutDown() +-- SpawnRU_SU34 = SPAWN:New( 'TF1 RU Su-34 Krymsk@AI - Attack Ships' ):Schedule( 2, 3, 1800, 0.4 ):SpawnUncontrolled():InitRandomizeRoute( 1, 1, 3000 ):RepeatOnEngineShutDown() function SPAWN:InitRepeat() self:F( { self.SpawnTemplatePrefix, self.SpawnIndex } ) @@ -21215,11 +18212,15 @@ end -- @param #string SpawnCleanUpInterval The interval to check for inactive groups within seconds. -- @return #SPAWN self -- @usage Spawn_Helicopter:CleanUp( 20 ) -- CleanUp the spawning of the helicopters every 20 seconds when they become inactive. -function SPAWN:CleanUp( SpawnCleanUpInterval ) +function SPAWN:InitCleanUp( SpawnCleanUpInterval ) self:F( { self.SpawnTemplatePrefix, SpawnCleanUpInterval } ) self.SpawnCleanUpInterval = SpawnCleanUpInterval self.SpawnCleanUpTimeStamps = {} + + local SpawnGroup, SpawnCursor = self:GetFirstAliveGroup() + self:T( { "CleanUp Scheduler:", SpawnGroup } ) + --self.CleanUpFunction = routines.scheduleFunction( self._SpawnCleanUpScheduler, { self }, timer.getTime() + 1, SpawnCleanUpInterval ) self.CleanUpScheduler = SCHEDULER:New( self, self._SpawnCleanUpScheduler, {}, 1, SpawnCleanUpInterval, 0.2 ) return self @@ -21237,8 +18238,8 @@ end -- @return #SPAWN self -- @usage -- -- Define an array of Groups. --- Spawn_BE_Ground = SPAWN:New( 'BE Ground' ):Limit( 2, 24 ):Visible( 90, "Diamond", 10, 100, 50 ) -function SPAWN:Array( SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY ) +-- Spawn_BE_Ground = SPAWN:New( 'BE Ground' ):InitLimit( 2, 24 ):InitArray( 90, "Diamond", 10, 100, 50 ) +function SPAWN:InitArray( SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY ) self:F( { self.SpawnTemplatePrefix, SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY } ) self.SpawnVisible = true -- When the first Spawn executes, all the Groups need to be made visible before start. @@ -21298,7 +18299,7 @@ end --- Will spawn a group based on the internal index. -- Note: Uses @{DATABASE} module defined in MOOSE. -- @param #SPAWN self --- @return Group#GROUP The group that was spawned. You can use this group for further actions. +-- @return Wrapper.Group#GROUP The group that was spawned. You can use this group for further actions. function SPAWN:Spawn() self:F( { self.SpawnTemplatePrefix, self.SpawnIndex, self.AliveUnits } ) @@ -21309,7 +18310,7 @@ end -- Note: Uses @{DATABASE} module defined in MOOSE. -- @param #SPAWN self -- @param #string SpawnIndex The index of the group to be spawned. --- @return Group#GROUP The group that was spawned. You can use this group for further actions. +-- @return Wrapper.Group#GROUP The group that was spawned. You can use this group for further actions. function SPAWN:ReSpawn( SpawnIndex ) self:F( { self.SpawnTemplatePrefix, SpawnIndex } ) @@ -21332,7 +18333,8 @@ end --- Will spawn a group with a specified index number. -- Uses @{DATABASE} global object defined in MOOSE. -- @param #SPAWN self --- @return Group#GROUP The group that was spawned. You can use this group for further actions. +-- @param #string SpawnIndex The index of the group to be spawned. +-- @return Wrapper.Group#GROUP The group that was spawned. You can use this group for further actions. function SPAWN:SpawnWithIndex( SpawnIndex ) self:F2( { SpawnTemplatePrefix = self.SpawnTemplatePrefix, SpawnIndex = SpawnIndex, AliveUnits = self.AliveUnits, SpawnMaxGroups = self.SpawnMaxGroups } ) @@ -21341,20 +18343,40 @@ function SPAWN:SpawnWithIndex( SpawnIndex ) if self.SpawnGroups[self.SpawnIndex].Visible then self.SpawnGroups[self.SpawnIndex].Group:Activate() else - _EVENTDISPATCHER:OnBirthForTemplate( self.SpawnGroups[self.SpawnIndex].SpawnTemplate, self._OnBirth, self ) - _EVENTDISPATCHER:OnCrashForTemplate( self.SpawnGroups[self.SpawnIndex].SpawnTemplate, self._OnDeadOrCrash, self ) - _EVENTDISPATCHER:OnDeadForTemplate( self.SpawnGroups[self.SpawnIndex].SpawnTemplate, self._OnDeadOrCrash, self ) + + local SpawnTemplate = self.SpawnGroups[self.SpawnIndex].SpawnTemplate + self:T( SpawnTemplate.name ) + + if SpawnTemplate then + + local PointVec3 = POINT_VEC3:New( SpawnTemplate.route.points[1].x, SpawnTemplate.route.points[1].alt, SpawnTemplate.route.points[1].y ) + self:T( { "Current point of ", self.SpawnTemplatePrefix, PointVec3 } ) + + -- If RandomizeUnits, then Randomize the formation at the start point. + if self.SpawnRandomizeUnits then + for UnitID = 1, #SpawnTemplate.units do + local RandomVec2 = PointVec3:GetRandomVec2InRadius( self.SpawnOuterRadius, self.SpawnInnerRadius ) + SpawnTemplate.units[UnitID].x = RandomVec2.x + SpawnTemplate.units[UnitID].y = RandomVec2.y + self:T( 'SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y ) + end + end + end + + _EVENTDISPATCHER:OnBirthForTemplate( SpawnTemplate, self._OnBirth, self ) + _EVENTDISPATCHER:OnCrashForTemplate( SpawnTemplate, self._OnDeadOrCrash, self ) + _EVENTDISPATCHER:OnDeadForTemplate( SpawnTemplate, self._OnDeadOrCrash, self ) if self.Repeat then - _EVENTDISPATCHER:OnTakeOffForTemplate( self.SpawnGroups[self.SpawnIndex].SpawnTemplate, self._OnTakeOff, self ) - _EVENTDISPATCHER:OnLandForTemplate( self.SpawnGroups[self.SpawnIndex].SpawnTemplate, self._OnLand, self ) + _EVENTDISPATCHER:OnTakeOffForTemplate( SpawnTemplate, self._OnTakeOff, self ) + _EVENTDISPATCHER:OnLandForTemplate( SpawnTemplate, self._OnLand, self ) end if self.RepeatOnEngineShutDown then - _EVENTDISPATCHER:OnEngineShutDownForTemplate( self.SpawnGroups[self.SpawnIndex].SpawnTemplate, self._OnEngineShutDown, self ) + _EVENTDISPATCHER:OnEngineShutDownForTemplate( SpawnTemplate, self._OnEngineShutDown, self ) end - self:T3( self.SpawnGroups[self.SpawnIndex].SpawnTemplate ) + self:T3( SpawnTemplate.name ) - self.SpawnGroups[self.SpawnIndex].Group = _DATABASE:Spawn( self.SpawnGroups[self.SpawnIndex].SpawnTemplate ) + self.SpawnGroups[self.SpawnIndex].Group = _DATABASE:Spawn( SpawnTemplate ) -- If there is a SpawnFunction hook defined, call it. if self.SpawnFunctionHook then @@ -21402,7 +18424,7 @@ function SPAWN:SpawnScheduled( SpawnTime, SpawnTimeVariation ) end --- Will re-start the spawning scheduler. --- Note: This function is only required to be called when the schedule was stopped. +-- Note: This method is only required to be called when the schedule was stopped. function SPAWN:SpawnScheduleStart() self:F( { self.SpawnTemplatePrefix } ) @@ -21418,16 +18440,27 @@ end --- Allows to place a CallFunction hook when a new group spawns. --- The provided function will be called when a new group is spawned, including its given parameters. --- The first parameter of the SpawnFunction is the @{Group#GROUP} that was spawned. +-- The provided method will be called when a new group is spawned, including its given parameters. +-- The first parameter of the SpawnFunction is the @{Wrapper.Group#GROUP} that was spawned. -- @param #SPAWN self --- @param #function SpawnFunctionHook The function to be called when a group spawns. +-- @param #function SpawnCallBackFunction The function to be called when a group spawns. -- @param SpawnFunctionArguments A random amount of arguments to be provided to the function when the group spawns. -- @return #SPAWN -function SPAWN:SpawnFunction( SpawnFunctionHook, ... ) - self:F( SpawnFunction ) +-- @usage +-- -- Declare SpawnObject and call a function when a new Group is spawned. +-- local SpawnObject = SPAWN +-- :New( "SpawnObject" ) +-- :InitLimit( 2, 10 ) +-- :OnSpawnGroup( +-- function( SpawnGroup ) +-- SpawnGroup:E( "I am spawned" ) +-- end +-- ) +-- :SpawnScheduled( 300, 0.3 ) +function SPAWN:OnSpawnGroup( SpawnCallBackFunction, ... ) + self:F( "OnSpawnGroup" ) - self.SpawnFunctionHook = SpawnFunctionHook + self.SpawnFunctionHook = SpawnCallBackFunction self.SpawnFunctionArguments = {} if arg then self.SpawnFunctionArguments = arg @@ -21438,18 +18471,16 @@ end --- Will spawn a group from a Vec3 in 3D space. --- This function is mostly advisable to be used if you want to simulate spawning units in the air, like helicopters or airplanes. +-- This method is mostly advisable to be used if you want to simulate spawning units in the air, like helicopters or airplanes. -- Note that each point in the route assigned to the spawning group is reset to the point of the spawn. -- You can use the returned group to further define the route to be followed. -- @param #SPAWN self --- @param DCSTypes#Vec3 Vec3 The Vec3 coordinates where to spawn the group. --- @param #number OuterRadius (Optional) The outer radius in meters where the new group will be spawned. --- @param #number InnerRadius (Optional) The inner radius in meters where the new group will NOT be spawned. --- @param #number SpawnIndex (Optional) The index which group to spawn within the given zone. --- @return Group#GROUP that was spawned. +-- @param Dcs.DCSTypes#Vec3 Vec3 The Vec3 coordinates where to spawn the group. +-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. +-- @return Wrapper.Group#GROUP that was spawned. -- @return #nil Nothing was spawned. -function SPAWN:SpawnFromVec3( Vec3, OuterRadius, InnerRadius, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, Vec3, OuterRadius, InnerRadius, SpawnIndex } ) +function SPAWN:SpawnFromVec3( Vec3, SpawnIndex ) + self:F( { self.SpawnTemplatePrefix, Vec3, SpawnIndex } ) local PointVec3 = POINT_VEC3:NewFromVec3( Vec3 ) self:T2(PointVec3) @@ -21466,33 +18497,30 @@ function SPAWN:SpawnFromVec3( Vec3, OuterRadius, InnerRadius, SpawnIndex ) if SpawnTemplate then self:T( { "Current point of ", self.SpawnTemplatePrefix, Vec3 } ) + + -- Translate the position of the Group Template to the Vec3. + for UnitID = 1, #SpawnTemplate.units do + self:T( 'Before Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y ) + local UnitTemplate = SpawnTemplate.units[UnitID] + local SX = UnitTemplate.x + local SY = UnitTemplate.y + local BX = SpawnTemplate.route.points[1].x + local BY = SpawnTemplate.route.points[1].y + local TX = Vec3.x + ( SX - BX ) + local TY = Vec3.z + ( SY - BY ) + SpawnTemplate.units[UnitID].x = TX + SpawnTemplate.units[UnitID].y = TY + SpawnTemplate.units[UnitID].alt = Vec3.y + self:T( 'After Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y ) + end SpawnTemplate.route.points[1].x = Vec3.x SpawnTemplate.route.points[1].y = Vec3.z SpawnTemplate.route.points[1].alt = Vec3.y - InnerRadius = InnerRadius or 0 - OuterRadius = OuterRadius or 0 - - -- Apply SpawnFormation - for UnitID = 1, #SpawnTemplate.units do - local RandomVec2 = PointVec3:GetRandomVec2InRadius( OuterRadius, InnerRadius ) - SpawnTemplate.units[UnitID].x = RandomVec2.x - SpawnTemplate.units[UnitID].y = RandomVec2.y - SpawnTemplate.units[UnitID].alt = Vec3.y - self:T( 'SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y ) - end - - -- TODO: Need to rework this. A spawn action should always be at the random point to start from. This move is not correct to be here. --- local RandomVec2 = PointVec3:GetRandomVec2InRadius( OuterRadius, InnerRadius ) --- local Point = {} --- Point.type = "Turning Point" --- Point.x = RandomVec2.x --- Point.y = RandomVec2.y --- Point.action = "Cone" --- Point.speed = 5 --- table.insert( SpawnTemplate.route.points, 2, Point ) - + SpawnTemplate.x = Vec3.x + SpawnTemplate.y = Vec3.z + return self:SpawnWithIndex( self.SpawnIndex ) end end @@ -21501,93 +18529,86 @@ function SPAWN:SpawnFromVec3( Vec3, OuterRadius, InnerRadius, SpawnIndex ) end --- Will spawn a group from a Vec2 in 3D space. --- This function is mostly advisable to be used if you want to simulate spawning groups on the ground from air units, like vehicles. +-- This method is mostly advisable to be used if you want to simulate spawning groups on the ground from air units, like vehicles. -- Note that each point in the route assigned to the spawning group is reset to the point of the spawn. -- You can use the returned group to further define the route to be followed. -- @param #SPAWN self --- @param DCSTypes#Vec2 Vec2 The Vec2 coordinates where to spawn the group. --- @param #number OuterRadius (Optional) The outer radius in meters where the new group will be spawned. --- @param #number InnerRadius (Optional) The inner radius in meters where the new group will NOT be spawned. --- @param #number SpawnIndex (Optional) The index which group to spawn within the given zone. --- @return Group#GROUP that was spawned. +-- @param Dcs.DCSTypes#Vec2 Vec2 The Vec2 coordinates where to spawn the group. +-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. +-- @return Wrapper.Group#GROUP that was spawned. -- @return #nil Nothing was spawned. -function SPAWN:SpawnFromVec2( Vec2, OuterRadius, InnerRadius, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, Vec2, OuterRadius, InnerRadius, SpawnIndex } ) +function SPAWN:SpawnFromVec2( Vec2, SpawnIndex ) + self:F( { self.SpawnTemplatePrefix, Vec2, SpawnIndex } ) local PointVec2 = POINT_VEC2:NewFromVec2( Vec2 ) - return self:SpawnFromVec3( PointVec2:GetVec3(), OuterRadius, InnerRadius, SpawnIndex ) + return self:SpawnFromVec3( PointVec2:GetVec3(), SpawnIndex ) end ---- Will spawn a group from a hosting unit. This function is mostly advisable to be used if you want to simulate spawning from air units, like helicopters, which are dropping infantry into a defined Landing Zone. +--- Will spawn a group from a hosting unit. This method is mostly advisable to be used if you want to simulate spawning from air units, like helicopters, which are dropping infantry into a defined Landing Zone. -- Note that each point in the route assigned to the spawning group is reset to the point of the spawn. -- You can use the returned group to further define the route to be followed. -- @param #SPAWN self --- @param Unit#UNIT HostUnit The air or ground unit dropping or unloading the group. --- @param #number OuterRadius (Optional) The outer radius in meters where the new group will be spawned. --- @param #number InnerRadius (Optional) The inner radius in meters where the new group will NOT be spawned. --- @param #number SpawnIndex (Optional) The index which group to spawn within the given zone. --- @return Group#GROUP that was spawned. +-- @param Wrapper.Unit#UNIT HostUnit The air or ground unit dropping or unloading the group. +-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. +-- @return Wrapper.Group#GROUP that was spawned. -- @return #nil Nothing was spawned. -function SPAWN:SpawnFromUnit( HostUnit, OuterRadius, InnerRadius, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, HostUnit, OuterRadius, InnerRadius, SpawnIndex } ) +function SPAWN:SpawnFromUnit( HostUnit, SpawnIndex ) + self:F( { self.SpawnTemplatePrefix, HostUnit, SpawnIndex } ) if HostUnit and HostUnit:IsAlive() then -- and HostUnit:getUnit(1):inAir() == false then - return self:SpawnFromVec3( HostUnit:GetVec3(), OuterRadius, InnerRadius, SpawnIndex ) + return self:SpawnFromVec3( HostUnit:GetVec3(), SpawnIndex ) end return nil end ---- Will spawn a group from a hosting static. This function is mostly advisable to be used if you want to simulate spawning from buldings and structures (static buildings). +--- Will spawn a group from a hosting static. This method is mostly advisable to be used if you want to simulate spawning from buldings and structures (static buildings). -- You can use the returned group to further define the route to be followed. -- @param #SPAWN self --- @param Static#STATIC HostStatic The static dropping or unloading the group. --- @param #number OuterRadius (Optional) The outer radius in meters where the new group will be spawned. --- @param #number InnerRadius (Optional) The inner radius in meters where the new group will NOT be spawned. --- @param #number SpawnIndex (Optional) The index which group to spawn within the given zone. --- @return Group#GROUP that was spawned. +-- @param Wrapper.Static#STATIC HostStatic The static dropping or unloading the group. +-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. +-- @return Wrapper.Group#GROUP that was spawned. -- @return #nil Nothing was spawned. -function SPAWN:SpawnFromStatic( HostStatic, OuterRadius, InnerRadius, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, HostStatic, OuterRadius, InnerRadius, SpawnIndex } ) +function SPAWN:SpawnFromStatic( HostStatic, SpawnIndex ) + self:F( { self.SpawnTemplatePrefix, HostStatic, SpawnIndex } ) if HostStatic and HostStatic:IsAlive() then - return self:SpawnFromVec3( HostStatic:GetVec3(), OuterRadius, InnerRadius, SpawnIndex ) + return self:SpawnFromVec3( HostStatic:GetVec3(), SpawnIndex ) end return nil end ---- Will spawn a Group within a given @{Zone#ZONE}. --- Once the group is spawned within the zone, it will continue on its route. --- The first waypoint (where the group is spawned) is replaced with the zone coordinates. +--- Will spawn a Group within a given @{Zone}. +-- The @{Zone} can be of any type derived from @{Core.Zone#ZONE_BASE}. +-- Once the @{Group} is spawned within the zone, the @{Group} will continue on its route. +-- The **first waypoint** (where the group is spawned) is replaced with the zone location coordinates. -- @param #SPAWN self --- @param Zone#ZONE Zone The zone where the group is to be spawned. --- @param #number ZoneRandomize (Optional) Set to true if you want to randomize the starting point in the zone. --- @param #number SpawnIndex (Optional) The index which group to spawn within the given zone. --- @return Group#GROUP that was spawned. +-- @param Core.Zone#ZONE Zone The zone where the group is to be spawned. +-- @param #boolean RandomizeGroup (optional) Randomization of the @{Group} position in the zone. +-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. +-- @return Wrapper.Group#GROUP that was spawned. -- @return #nil when nothing was spawned. -function SPAWN:SpawnInZone( Zone, ZoneRandomize, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, Zone, ZoneRandomize, SpawnIndex } ) +function SPAWN:SpawnInZone( Zone, RandomizeGroup, SpawnIndex ) + self:F( { self.SpawnTemplatePrefix, Zone, RandomizeGroup, SpawnIndex } ) if Zone then - if ZoneRandomize then - return self:SpawnFromVec2( Zone:GetVec2(), Zone:GetRadius(), 0, SpawnIndex ) + if RandomizeGroup then + return self:SpawnFromVec2( Zone:GetRandomVec2(), SpawnIndex ) else - return self:SpawnFromVec2( Zone:GetVec2(), 0, 0, SpawnIndex ) + return self:SpawnFromVec2( Zone:GetVec2(), SpawnIndex ) end end return nil end - - - ---- Will spawn a plane group in uncontrolled mode... +--- (AIR) Will spawn a plane group in uncontrolled mode... -- This will be similar to the uncontrolled flag setting in the ME. +-- @param #SPAWN self -- @return #SPAWN self -function SPAWN:UnControlled() +function SPAWN:InitUnControlled() self:F( { self.SpawnTemplatePrefix } ) self.SpawnUnControlled = true @@ -21624,12 +18645,12 @@ function SPAWN:SpawnGroupName( SpawnIndex ) end ---- Will find the first alive GROUP it has spawned, and return the alive GROUP object and the first Index where the first alive GROUP object has been found. +--- Will find the first alive @{Group} it has spawned, and return the alive @{Group} object and the first Index where the first alive @{Group} object has been found. -- @param #SPAWN self --- @return Group#GROUP, #number The GROUP object found, the new Index where the group was found. +-- @return Wrapper.Group#GROUP, #number The @{Group} object found, the new Index where the group was found. -- @return #nil, #nil When no group is found, #nil is returned. -- @usage --- -- Find the first alive GROUP object of the SpawnPlanes SPAWN object GROUP collection that it has spawned during the mission. +-- -- Find the first alive @{Group} object of the SpawnPlanes SPAWN object @{Group} collection that it has spawned during the mission. -- local GroupPlane, Index = SpawnPlanes:GetFirstAliveGroup() -- while GroupPlane ~= nil do -- -- Do actions with the GroupPlane object. @@ -21649,13 +18670,13 @@ function SPAWN:GetFirstAliveGroup() end ---- Will find the next alive GROUP object from a given Index, and return a reference to the alive GROUP object and the next Index where the alive GROUP has been found. +--- Will find the next alive @{Group} object from a given Index, and return a reference to the alive @{Group} object and the next Index where the alive @{Group} has been found. -- @param #SPAWN self --- @param #number SpawnIndexStart A Index holding the start position to search from. This function can also be used to find the first alive GROUP object from the given Index. --- @return Group#GROUP, #number The next alive GROUP object found, the next Index where the next alive GROUP object was found. --- @return #nil, #nil When no alive GROUP object is found from the start Index position, #nil is returned. +-- @param #number SpawnIndexStart A Index holding the start position to search from. This method can also be used to find the first alive @{Group} object from the given Index. +-- @return Wrapper.Group#GROUP, #number The next alive @{Group} object found, the next Index where the next alive @{Group} object was found. +-- @return #nil, #nil When no alive @{Group} object is found from the start Index position, #nil is returned. -- @usage --- -- Find the first alive GROUP object of the SpawnPlanes SPAWN object GROUP collection that it has spawned during the mission. +-- -- Find the first alive @{Group} object of the SpawnPlanes SPAWN object @{Group} collection that it has spawned during the mission. -- local GroupPlane, Index = SpawnPlanes:GetFirstAliveGroup() -- while GroupPlane ~= nil do -- -- Do actions with the GroupPlane object. @@ -21675,12 +18696,12 @@ function SPAWN:GetNextAliveGroup( SpawnIndexStart ) return nil, nil end ---- Will find the last alive GROUP object, and will return a reference to the last live GROUP object and the last Index where the last alive GROUP object has been found. +--- Will find the last alive @{Group} object, and will return a reference to the last live @{Group} object and the last Index where the last alive @{Group} object has been found. -- @param #SPAWN self --- @return Group#GROUP, #number The last alive GROUP object found, the last Index where the last alive GROUP object was found. --- @return #nil, #nil When no alive GROUP object is found, #nil is returned. +-- @return Wrapper.Group#GROUP, #number The last alive @{Group} object found, the last Index where the last alive @{Group} object was found. +-- @return #nil, #nil When no alive @{Group} object is found, #nil is returned. -- @usage --- -- Find the last alive GROUP object of the SpawnPlanes SPAWN object GROUP collection that it has spawned during the mission. +-- -- Find the last alive @{Group} object of the SpawnPlanes SPAWN object @{Group} collection that it has spawned during the mission. -- local GroupPlane, Index = SpawnPlanes:GetLastAliveGroup() -- if GroupPlane then -- GroupPlane can be nil!!! -- -- Do actions with the GroupPlane object. @@ -21708,7 +18729,7 @@ end -- If no index is given, it will return the first group in the list. -- @param #SPAWN self -- @param #number SpawnIndex The index of the group to return. --- @return Group#GROUP self +-- @return Wrapper.Group#GROUP self function SPAWN:GetGroupFromIndex( SpawnIndex ) self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnIndex } ) @@ -21728,7 +18749,7 @@ end -- The method will search for a #-mark, and will return the index behind the #-mark of the DCSUnit. -- It will return nil of no prefix was found. -- @param #SPAWN self --- @param DCSUnit#Unit DCSUnit The @{DCSUnit} to be searched. +-- @param Dcs.DCSWrapper.Unit#Unit DCSUnit The @{DCSUnit} to be searched. -- @return #string The prefix -- @return #nil Nothing found function SPAWN:_GetGroupIndexFromDCSUnit( DCSUnit ) @@ -21750,7 +18771,7 @@ end -- The method will search for a #-mark, and will return the text before the #-mark. -- It will return nil of no prefix was found. -- @param #SPAWN self --- @param DCSUnit#UNIT DCSUnit The @{DCSUnit} to be searched. +-- @param Dcs.DCSWrapper.Unit#UNIT DCSUnit The @{DCSUnit} to be searched. -- @return #string The prefix -- @return #nil Nothing found function SPAWN:_GetPrefixFromDCSUnit( DCSUnit ) @@ -21770,8 +18791,8 @@ end --- Return the group within the SpawnGroups collection with input a DCSUnit. -- @param #SPAWN self --- @param DCSUnit#Unit DCSUnit The @{DCSUnit} to be searched. --- @return Group#GROUP The Group +-- @param Dcs.DCSWrapper.Unit#Unit DCSUnit The @{DCSUnit} to be searched. +-- @return Wrapper.Group#GROUP The Group -- @return #nil Nothing found function SPAWN:_GetGroupFromDCSUnit( DCSUnit ) self:F3( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, DCSUnit } ) @@ -21920,8 +18941,6 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) for UnitID = 1, #SpawnTemplate.units do SpawnTemplate.units[UnitID].name = string.format( SpawnTemplate.name .. '-%02d', UnitID ) SpawnTemplate.units[UnitID].unitId = nil - SpawnTemplate.units[UnitID].x = SpawnTemplate.route.points[1].x - SpawnTemplate.units[UnitID].y = SpawnTemplate.route.points[1].y end self:T3( { "Template:", SpawnTemplate } ) @@ -21958,6 +18977,8 @@ function SPAWN:_RandomizeRoute( SpawnIndex ) end end + self:_RandomizeZones( SpawnIndex ) + return self end @@ -21988,6 +19009,57 @@ function SPAWN:_RandomizeTemplate( SpawnIndex ) return self end +--- Private method that randomizes the @{Zone}s where the Group will be spawned. +-- @param #SPAWN self +-- @param #number SpawnIndex +-- @return #SPAWN self +function SPAWN:_RandomizeZones( SpawnIndex ) + self:F( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeZones } ) + + if self.SpawnRandomizeZones then + local SpawnZone = nil -- Core.Zone#ZONE_BASE + while not SpawnZone do + self:T( { SpawnZoneTableCount = #self.SpawnZoneTable, self.SpawnZoneTable } ) + local ZoneID = math.random( #self.SpawnZoneTable ) + self:T( ZoneID ) + SpawnZone = self.SpawnZoneTable[ ZoneID ]:GetZoneMaybe() + end + + self:T( "Preparing Spawn in Zone", SpawnZone:GetName() ) + + local SpawnVec2 = SpawnZone:GetRandomVec2() + + self:T( { SpawnVec2 = SpawnVec2 } ) + + local SpawnTemplate = self.SpawnGroups[SpawnIndex].SpawnTemplate + + self:T( { Route = SpawnTemplate.route } ) + + for UnitID = 1, #SpawnTemplate.units do + local UnitTemplate = SpawnTemplate.units[UnitID] + self:T( 'Before Translation SpawnTemplate.units['..UnitID..'].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. UnitTemplate.y ) + local SX = UnitTemplate.x + local SY = UnitTemplate.y + local BX = SpawnTemplate.route.points[1].x + local BY = SpawnTemplate.route.points[1].y + local TX = SpawnVec2.x + ( SX - BX ) + local TY = SpawnVec2.y + ( SY - BY ) + UnitTemplate.x = TX + UnitTemplate.y = TY + -- TODO: Manage altitude based on landheight... + --SpawnTemplate.units[UnitID].alt = SpawnVec2: + self:T( 'After Translation SpawnTemplate.units['..UnitID..'].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. UnitTemplate.y ) + end + SpawnTemplate.x = SpawnVec2.x + SpawnTemplate.y = SpawnVec2.y + SpawnTemplate.route.points[1].x = SpawnVec2.x + SpawnTemplate.route.points[1].y = SpawnVec2.y + end + + return self + +end + function SPAWN:_TranslateRotate( SpawnIndex, SpawnRootX, SpawnRootY, SpawnX, SpawnY, SpawnAngle ) self:F( { self.SpawnTemplatePrefix, SpawnIndex, SpawnRootX, SpawnRootY, SpawnX, SpawnY, SpawnAngle } ) @@ -22031,7 +19103,7 @@ function SPAWN:_TranslateRotate( SpawnIndex, SpawnRootX, SpawnRootY, SpawnX, Spa return self end ---- Get the next index of the groups to be spawned. This function is complicated, as it is used at several spaces. +--- Get the next index of the groups to be spawned. This method is complicated, as it is used at several spaces. function SPAWN:_GetSpawnIndex( SpawnIndex ) self:F2( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnMaxGroups, self.SpawnMaxUnitsAlive, self.AliveUnits, #self.SpawnTemplate.units } ) @@ -22059,7 +19131,7 @@ end -- TODO Need to delete this... _DATABASE does this now ... --- @param #SPAWN self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event function SPAWN:_OnBirth( Event ) if timer.getTime0() < timer.getAbsTime() then @@ -22079,7 +19151,7 @@ end -- @todo Need to delete this... _DATABASE does this now ... --- @param #SPAWN self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event function SPAWN:_OnDeadOrCrash( Event ) self:F( self.SpawnTemplatePrefix, Event ) @@ -22166,32 +19238,64 @@ function SPAWN:_Scheduler() return true end +--- Schedules the CleanUp of Groups +-- @param #SPAWN self +-- @return #boolean True = Continue Scheduler function SPAWN:_SpawnCleanUpScheduler() self:F( { "CleanUp Scheduler:", self.SpawnTemplatePrefix } ) - local SpawnCursor - local SpawnGroup, SpawnCursor = self:GetFirstAliveGroup( SpawnCursor ) - - self:T( { "CleanUp Scheduler:", SpawnGroup } ) + local SpawnGroup, SpawnCursor = self:GetFirstAliveGroup() + self:T( { "CleanUp Scheduler:", SpawnGroup, SpawnCursor } ) while SpawnGroup do - - if SpawnGroup:AllOnGround() and SpawnGroup:GetMaxVelocity() < 1 then - if not self.SpawnCleanUpTimeStamps[SpawnGroup:GetName()] then - self.SpawnCleanUpTimeStamps[SpawnGroup:GetName()] = timer.getTime() - else - if self.SpawnCleanUpTimeStamps[SpawnGroup:GetName()] + self.SpawnCleanUpInterval < timer.getTime() then - self:T( { "CleanUp Scheduler:", "Cleaning:", SpawnGroup } ) - SpawnGroup:Destroy() - end - end - else - self.SpawnCleanUpTimeStamps[SpawnGroup:GetName()] = nil - end + + local SpawnUnits = SpawnGroup:GetUnits() + + for UnitID, UnitData in pairs( SpawnUnits ) do + + local SpawnUnit = UnitData -- Wrapper.Unit#UNIT + local SpawnUnitName = SpawnUnit:GetName() + + + self.SpawnCleanUpTimeStamps[SpawnUnitName] = self.SpawnCleanUpTimeStamps[SpawnUnitName] or {} + local Stamp = self.SpawnCleanUpTimeStamps[SpawnUnitName] + self:T( { SpawnUnitName, Stamp } ) + + if Stamp.Vec2 then + if SpawnUnit:InAir() == false and SpawnUnit:GetVelocityKMH() < 1 then + local NewVec2 = SpawnUnit:GetVec2() + if Stamp.Vec2.x == NewVec2.x and Stamp.Vec2.y == NewVec2.y then + -- If the plane is not moving, and is on the ground, assign it with a timestamp... + if Stamp.Time + self.SpawnCleanUpInterval < timer.getTime() then + self:T( { "CleanUp Scheduler:", "ReSpawning:", SpawnGroup:GetName() } ) + self:ReSpawn( SpawnCursor ) + Stamp.Vec2 = nil + Stamp.Time = nil + end + else + Stamp.Time = timer.getTime() + Stamp.Vec2 = SpawnUnit:GetVec2() + end + else + Stamp.Vec2 = nil + Stamp.Time = nil + end + else + if SpawnUnit:InAir() == false then + Stamp.Vec2 = SpawnUnit:GetVec2() + if SpawnUnit:GetVelocityKMH() < 1 then + Stamp.Time = timer.getTime() + end + else + Stamp.Time = nil + Stamp.Vec2 = nil + end + end + end SpawnGroup, SpawnCursor = self:GetNextAliveGroup( SpawnCursor ) - self:T( { "CleanUp Scheduler:", SpawnGroup } ) + self:T( { "CleanUp Scheduler:", SpawnGroup, SpawnCursor } ) end @@ -22333,7 +19437,7 @@ end --- The SEAD class -- @type SEAD --- @extends Base#BASE +-- @extends Core.Base#BASE SEAD = { ClassName = "SEAD", TargetSkill = { @@ -22537,7 +19641,7 @@ end -- ============================ -- Create a new SPAWN object with the @{#ESCORT.New} method: -- --- * @{#ESCORT.New}: Creates a new ESCORT object from a @{Group#GROUP} for a @{Client#CLIENT}, with an optional briefing text. +-- * @{#ESCORT.New}: Creates a new ESCORT object from a @{Wrapper.Group#GROUP} for a @{Wrapper.Client#CLIENT}, with an optional briefing text. -- -- ESCORT initialization methods. -- ============================== @@ -22574,17 +19678,17 @@ end --- ESCORT class -- @type ESCORT --- @extends Base#BASE --- @field Client#CLIENT EscortClient --- @field Group#GROUP EscortGroup +-- @extends Core.Base#BASE +-- @field Wrapper.Client#CLIENT EscortClient +-- @field Wrapper.Group#GROUP EscortGroup -- @field #string EscortName -- @field #ESCORT.MODE EscortMode The mode the escort is in. --- @field Scheduler#SCHEDULER FollowScheduler The instance of the SCHEDULER class. +-- @field Core.Scheduler#SCHEDULER FollowScheduler The instance of the SCHEDULER class. -- @field #number FollowDistance The current follow distance. -- @field #boolean ReportTargets If true, nearby targets are reported. --- @Field DCSTypes#AI.Option.Air.val.ROE OptionROE Which ROE is set to the EscortGroup. --- @field DCSTypes#AI.Option.Air.val.REACTION_ON_THREAT OptionReactionOnThreat Which REACTION_ON_THREAT is set to the EscortGroup. --- @field Menu#MENU_CLIENT EscortMenuResumeMission +-- @Field Dcs.DCSTypes#AI.Option.Air.val.ROE OptionROE Which ROE is set to the EscortGroup. +-- @field Dcs.DCSTypes#AI.Option.Air.val.REACTION_ON_THREAT OptionReactionOnThreat Which REACTION_ON_THREAT is set to the EscortGroup. +-- @field Core.Menu#MENU_CLIENT EscortMenuResumeMission ESCORT = { ClassName = "ESCORT", EscortName = nil, -- The Escort Name @@ -22618,8 +19722,8 @@ ESCORT = { --- ESCORT class constructor for an AI group -- @param #ESCORT self --- @param Client#CLIENT EscortClient The client escorted by the EscortGroup. --- @param Group#GROUP EscortGroup The group AI escorting the EscortClient. +-- @param Wrapper.Client#CLIENT EscortClient The client escorted by the EscortGroup. +-- @param Wrapper.Group#GROUP EscortGroup The group AI escorting the EscortClient. -- @param #string EscortName Name of the escort. -- @param #string EscortBriefing A text showing the ESCORT briefing to the player. Note that if no EscortBriefing is provided, the default briefing will be shown. -- @return #ESCORT self @@ -22636,8 +19740,8 @@ function ESCORT:New( EscortClient, EscortGroup, EscortName, EscortBriefing ) local self = BASE:Inherit( self, BASE:New() ) self:F( { EscortClient, EscortGroup, EscortName } ) - self.EscortClient = EscortClient -- Client#CLIENT - self.EscortGroup = EscortGroup -- Group#GROUP + self.EscortClient = EscortClient -- Wrapper.Client#CLIENT + self.EscortGroup = EscortGroup -- Wrapper.Group#GROUP self.EscortName = EscortName self.EscortBriefing = EscortBriefing @@ -22725,7 +19829,7 @@ end --- Defines a menu slot to let the escort Join and Follow you at a certain distance. -- This menu will appear under **Navigation**. -- @param #ESCORT self --- @param DCSTypes#Distance Distance The distance in meters that the escort needs to follow the client. +-- @param Dcs.DCSTypes#Distance Distance The distance in meters that the escort needs to follow the client. -- @return #ESCORT function ESCORT:MenuFollowAt( Distance ) self:F(Distance) @@ -22750,8 +19854,8 @@ end --- Defines a menu slot to let the escort hold at their current position and stay low with a specified height during a specified time in seconds. -- This menu will appear under **Hold position**. -- @param #ESCORT self --- @param DCSTypes#Distance Height Optional parameter that sets the height in meters to let the escort orbit at the current location. The default value is 30 meters. --- @param DCSTypes#Time Seconds Optional parameter that lets the escort orbit at the current position for a specified time. (not implemented yet). The default value is 0 seconds, meaning, that the escort will orbit forever until a sequent command is given. +-- @param Dcs.DCSTypes#Distance Height Optional parameter that sets the height in meters to let the escort orbit at the current location. The default value is 30 meters. +-- @param Dcs.DCSTypes#Time Seconds Optional parameter that lets the escort orbit at the current position for a specified time. (not implemented yet). The default value is 0 seconds, meaning, that the escort will orbit forever until a sequent command is given. -- @param #string MenuTextFormat Optional parameter that shows the menu option text. The text string is formatted, and should contain two %d tokens in the string. The first for the Height, the second for the Time (if given). If no text is given, the default text will be displayed. -- @return #ESCORT -- TODO: Implement Seconds parameter. Challenge is to first develop the "continue from last activity" function. @@ -22812,8 +19916,8 @@ end --- Defines a menu slot to let the escort hold at the client position and stay low with a specified height during a specified time in seconds. -- This menu will appear under **Navigation**. -- @param #ESCORT self --- @param DCSTypes#Distance Height Optional parameter that sets the height in meters to let the escort orbit at the current location. The default value is 30 meters. --- @param DCSTypes#Time Seconds Optional parameter that lets the escort orbit at the current position for a specified time. (not implemented yet). The default value is 0 seconds, meaning, that the escort will orbit forever until a sequent command is given. +-- @param Dcs.DCSTypes#Distance Height Optional parameter that sets the height in meters to let the escort orbit at the current location. The default value is 30 meters. +-- @param Dcs.DCSTypes#Time Seconds Optional parameter that lets the escort orbit at the current position for a specified time. (not implemented yet). The default value is 0 seconds, meaning, that the escort will orbit forever until a sequent command is given. -- @param #string MenuTextFormat Optional parameter that shows the menu option text. The text string is formatted, and should contain one or two %d tokens in the string. The first for the Height, the second for the Time (if given). If no text is given, the default text will be displayed. -- @return #ESCORT -- TODO: Implement Seconds parameter. Challenge is to first develop the "continue from last activity" function. @@ -22873,8 +19977,8 @@ end --- Defines a menu slot to let the escort scan for targets at a certain height for a certain time in seconds. -- This menu will appear under **Scan targets**. -- @param #ESCORT self --- @param DCSTypes#Distance Height Optional parameter that sets the height in meters to let the escort orbit at the current location. The default value is 30 meters. --- @param DCSTypes#Time Seconds Optional parameter that lets the escort orbit at the current position for a specified time. (not implemented yet). The default value is 0 seconds, meaning, that the escort will orbit forever until a sequent command is given. +-- @param Dcs.DCSTypes#Distance Height Optional parameter that sets the height in meters to let the escort orbit at the current location. The default value is 30 meters. +-- @param Dcs.DCSTypes#Time Seconds Optional parameter that lets the escort orbit at the current position for a specified time. (not implemented yet). The default value is 0 seconds, meaning, that the escort will orbit forever until a sequent command is given. -- @param #string MenuTextFormat Optional parameter that shows the menu option text. The text string is formatted, and should contain one or two %d tokens in the string. The first for the Height, the second for the Time (if given). If no text is given, the default text will be displayed. -- @return #ESCORT function ESCORT:MenuScanForTargets( Height, Seconds, MenuTextFormat ) @@ -22951,10 +20055,10 @@ function ESCORT:MenuFlare( MenuTextFormat ) if not self.EscortMenuFlare then self.EscortMenuFlare = MENU_CLIENT:New( self.EscortClient, MenuText, self.EscortMenuReportNavigation, ESCORT._Flare, { ParamSelf = self } ) - self.EscortMenuFlareGreen = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release green flare", self.EscortMenuFlare, ESCORT._Flare, { ParamSelf = self, ParamColor = UNIT.FlareColor.Green, ParamMessage = "Released a green flare!" } ) - self.EscortMenuFlareRed = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release red flare", self.EscortMenuFlare, ESCORT._Flare, { ParamSelf = self, ParamColor = UNIT.FlareColor.Red, ParamMessage = "Released a red flare!" } ) - self.EscortMenuFlareWhite = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release white flare", self.EscortMenuFlare, ESCORT._Flare, { ParamSelf = self, ParamColor = UNIT.FlareColor.White, ParamMessage = "Released a white flare!" } ) - self.EscortMenuFlareYellow = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release yellow flare", self.EscortMenuFlare, ESCORT._Flare, { ParamSelf = self, ParamColor = UNIT.FlareColor.Yellow, ParamMessage = "Released a yellow flare!" } ) + self.EscortMenuFlareGreen = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release green flare", self.EscortMenuFlare, ESCORT._Flare, { ParamSelf = self, ParamColor = FLARECOLOR.Green, ParamMessage = "Released a green flare!" } ) + self.EscortMenuFlareRed = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release red flare", self.EscortMenuFlare, ESCORT._Flare, { ParamSelf = self, ParamColor = FLARECOLOR.Red, ParamMessage = "Released a red flare!" } ) + self.EscortMenuFlareWhite = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release white flare", self.EscortMenuFlare, ESCORT._Flare, { ParamSelf = self, ParamColor = FLARECOLOR.White, ParamMessage = "Released a white flare!" } ) + self.EscortMenuFlareYellow = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release yellow flare", self.EscortMenuFlare, ESCORT._Flare, { ParamSelf = self, ParamColor = FLARECOLOR.Yellow, ParamMessage = "Released a yellow flare!" } ) end return self @@ -22999,7 +20103,7 @@ end -- This menu will appear under **Report targets**. -- Note that if a report targets menu is not specified, no targets will be detected by the escort, and the attack and assisted attack menus will not be displayed. -- @param #ESCORT self --- @param DCSTypes#Time Seconds Optional parameter that lets the escort report their current detected targets after specified time interval in seconds. The default time is 30 seconds. +-- @param Dcs.DCSTypes#Time Seconds Optional parameter that lets the escort report their current detected targets after specified time interval in seconds. The default time is 30 seconds. -- @return #ESCORT function ESCORT:MenuReportTargets( Seconds ) self:F( { Seconds } ) @@ -23121,8 +20225,8 @@ function ESCORT._HoldPosition( MenuParam ) local EscortGroup = self.EscortGroup local EscortClient = self.EscortClient - local OrbitGroup = MenuParam.ParamOrbitGroup -- Group#GROUP - local OrbitUnit = OrbitGroup:GetUnit(1) -- Unit#UNIT + local OrbitGroup = MenuParam.ParamOrbitGroup -- Wrapper.Group#GROUP + local OrbitUnit = OrbitGroup:GetUnit(1) -- Wrapper.Unit#UNIT local OrbitHeight = MenuParam.ParamHeight local OrbitSeconds = MenuParam.ParamSeconds -- Not implemented yet @@ -23171,10 +20275,10 @@ function ESCORT._JoinUpAndFollow( MenuParam ) end --- JoinsUp and Follows a CLIENT. --- @param Escort#ESCORT self --- @param Group#GROUP EscortGroup --- @param Client#CLIENT EscortClient --- @param DCSTypes#Distance Distance +-- @param Functional.Escort#ESCORT self +-- @param Wrapper.Group#GROUP EscortGroup +-- @param Wrapper.Client#CLIENT EscortClient +-- @param Dcs.DCSTypes#Distance Distance function ESCORT:JoinUpAndFollow( EscortGroup, EscortClient, Distance ) self:F( { EscortGroup, EscortClient, Distance } ) @@ -23289,7 +20393,7 @@ function ESCORT._ScanTargets( MenuParam ) end ---- @param Group#GROUP EscortGroup +--- @param Wrapper.Group#GROUP EscortGroup function _Resume( EscortGroup ) env.info( '_Resume' ) @@ -23308,7 +20412,7 @@ function ESCORT._AttackTarget( MenuParam ) local EscortGroup = self.EscortGroup local EscortClient = self.EscortClient - local AttackUnit = MenuParam.ParamUnit -- Unit#UNIT + local AttackUnit = MenuParam.ParamUnit -- Wrapper.Unit#UNIT self.FollowScheduler:Stop() @@ -23349,7 +20453,7 @@ function ESCORT._AssistTarget( MenuParam ) local EscortGroup = self.EscortGroup local EscortClient = self.EscortClient local EscortGroupAttack = MenuParam.ParamEscortGroup - local AttackUnit = MenuParam.ParamUnit -- Unit#UNIT + local AttackUnit = MenuParam.ParamUnit -- Wrapper.Unit#UNIT self.FollowScheduler:Stop() @@ -23438,7 +20542,7 @@ end function ESCORT:RegisterRoute() self:F() - local EscortGroup = self.EscortGroup -- Group#GROUP + local EscortGroup = self.EscortGroup -- Wrapper.Group#GROUP local TaskPoints = EscortGroup:GetTaskRoute() @@ -23447,7 +20551,7 @@ function ESCORT:RegisterRoute() return TaskPoints end ---- @param Escort#ESCORT self +--- @param Functional.Escort#ESCORT self function ESCORT:_FollowScheduler() self:F( { self.FollowDistance } ) @@ -23740,7 +20844,7 @@ end -- -- === -- --- 1) @{MissileTrainer#MISSILETRAINER} class, extends @{Base#BASE} +-- 1) @{Functional.MissileTrainer#MISSILETRAINER} class, extends @{Core.Base#BASE} -- =============================================================== -- The @{#MISSILETRAINER} class uses the DCS world messaging system to be alerted of any missiles fired, and when a missile would hit your aircraft, -- the class will destroy the missile within a certain range, to avoid damage to your aircraft. @@ -23821,8 +20925,8 @@ end --- The MISSILETRAINER class -- @type MISSILETRAINER --- @field Set#SET_CLIENT DBClients --- @extends Base#BASE +-- @field Core.Set#SET_CLIENT DBClients +-- @extends Core.Base#BASE MISSILETRAINER = { ClassName = "MISSILETRAINER", TrackingMissiles = {}, @@ -23929,7 +21033,7 @@ function MISSILETRAINER:New( Distance, Briefing ) -- self.DB:ForEachClient( --- --- @param Client#CLIENT Client +-- --- @param Wrapper.Client#CLIENT Client -- function( Client ) -- -- ... actions ... @@ -24187,7 +21291,7 @@ end --- Detects if an SA site was shot with an anti radiation missile. In this case, take evasive actions based on the skill level set within the ME. -- @param #MISSILETRAINER self --- @param Event#EVENTDATA Event +-- @param Core.Event#EVENTDATA Event function MISSILETRAINER:_EventShot( Event ) self:F( { Event } ) @@ -24439,520 +21543,13 @@ function MISSILETRAINER:_TrackMissiles() return true end ---- This module contains the PATROLZONE class. --- --- === --- --- 1) @{Patrol#PATROLZONE} class, extends @{Base#BASE} --- =================================================== --- The @{Patrol#PATROLZONE} class implements the core functions to patrol a @{Zone}. --- --- 1.1) PATROLZONE constructor: --- ---------------------------- --- @{PatrolZone#PATROLZONE.New}(): Creates a new PATROLZONE object. --- --- 1.2) Modify the PATROLZONE parameters: --- -------------------------------------- --- The following methods are available to modify the parameters of a PATROLZONE object: --- --- * @{PatrolZone#PATROLZONE.SetGroup}(): Set the AI Patrol Group. --- * @{PatrolZone#PATROLZONE.SetSpeed}(): Set the patrol speed of the AI, for the next patrol. --- * @{PatrolZone#PATROLZONE.SetAltitude}(): Set altitude of the AI, for the next patrol. --- --- 1.3) Manage the out of fuel in the PATROLZONE: --- ---------------------------------------------- --- When the PatrolGroup is out of fuel, it is required that a new PatrolGroup is started, before the old PatrolGroup can return to the home base. --- Therefore, with a parameter and a calculation of the distance to the home base, the fuel treshold is calculated. --- When the fuel treshold is reached, the PatrolGroup will continue for a given time its patrol task in orbit, while a new PatrolGroup is targetted to the PATROLZONE. --- Once the time is finished, the old PatrolGroup will return to the base. --- Use the method @{PatrolZone#PATROLZONE.ManageFuel}() to have this proces in place. --- --- === --- --- @module PatrolZone --- @author FlightControl - - ---- PATROLZONE class --- @type PATROLZONE --- @field Group#GROUP PatrolGroup The @{Group} patrolling. --- @field Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed. --- @field DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. --- @field DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. --- @field DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Group} in km/h. --- @field DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Group} in km/h. --- @extends Base#BASE -PATROLZONE = { - ClassName = "PATROLZONE", -} - ---- Creates a new PATROLZONE object, taking a @{Group} object as a parameter. The GROUP needs to be alive. --- @param #PATROLZONE self --- @param Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed. --- @param DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. --- @param DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. --- @param DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Group} in km/h. --- @param DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Group} in km/h. --- @return #PATROLZONE self --- @usage --- -- Define a new PATROLZONE Object. This PatrolArea will patrol a group within PatrolZone between 3000 and 6000 meters, with a variying speed between 600 and 900 km/h. --- PatrolZone = ZONE:New( 'PatrolZone' ) --- PatrolGroup = GROUP:FindByName( "Patrol Group" ) --- PatrolArea = PATROLZONE:New( PatrolGroup, PatrolZone, 3000, 6000, 600, 900 ) -function PATROLZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed ) - - -- Inherits from BASE - local self = BASE:Inherit( self, BASE:New() ) - - self.PatrolZone = PatrolZone - self.PatrolFloorAltitude = PatrolFloorAltitude - self.PatrolCeilingAltitude = PatrolCeilingAltitude - self.PatrolMinSpeed = PatrolMinSpeed - self.PatrolMaxSpeed = PatrolMaxSpeed - - return self -end - ---- Set the @{Group} to act as the Patroller. --- @param #PATROLZONE self --- @param Group#GROUP PatrolGroup The @{Group} patrolling. --- @return #PATROLZONE self -function PATROLZONE:SetGroup( PatrolGroup ) - - self.PatrolGroup = PatrolGroup - self.PatrolGroupTemplateName = PatrolGroup:GetName() - self:NewPatrolRoute() - - if not self.PatrolOutOfFuelMonitor then - self.PatrolOutOfFuelMonitor = SCHEDULER:New( nil, _MonitorOutOfFuelScheduled, { self }, 1, 120, 0 ) - self.SpawnPatrolGroup = SPAWN:New( self.PatrolGroupTemplateName ) - end - - return self -end - ---- Sets (modifies) the minimum and maximum speed of the patrol. --- @param #PATROLZONE self --- @param DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Group} in km/h. --- @param DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Group} in km/h. --- @return #PATROLZONE self -function PATROLZONE:SetSpeed( PatrolMinSpeed, PatrolMaxSpeed ) - self:F2( { PatrolMinSpeed, PatrolMaxSpeed } ) - - self.PatrolMinSpeed = PatrolMinSpeed - self.PatrolMaxSpeed = PatrolMaxSpeed -end - ---- Sets the floor and ceiling altitude of the patrol. --- @param #PATROLZONE self --- @param DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. --- @param DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. --- @return #PATROLZONE self -function PATROLZONE:SetAltitude( PatrolFloorAltitude, PatrolCeilingAltitude ) - self:F2( { PatrolFloorAltitude, PatrolCeilingAltitude } ) - - self.PatrolFloorAltitude = PatrolFloorAltitude - self.PatrolCeilingAltitude = PatrolCeilingAltitude -end - - - ---- @param Group#GROUP PatrolGroup -function _NewPatrolRoute( PatrolGroup ) - - PatrolGroup:T( "NewPatrolRoute" ) - local PatrolZone = PatrolGroup:GetState( PatrolGroup, "PatrolZone" ) -- PatrolZone#PATROLZONE - PatrolZone:NewPatrolRoute() -end - ---- Defines a new patrol route using the @{PatrolZone} parameters and settings. --- @param #PATROLZONE self --- @return #PATROLZONE self -function PATROLZONE:NewPatrolRoute() - - self:F2() - - local PatrolRoute = {} - - if self.PatrolGroup:IsAlive() then - --- Determine if the PatrolGroup is within the PatrolZone. - -- If not, make a waypoint within the to that the PatrolGroup will fly at maximum speed to that point. - --- --- Calculate the current route point. --- local CurrentVec2 = self.PatrolGroup:GetVec2() --- local CurrentAltitude = self.PatrolGroup:GetUnit(1):GetAltitude() --- local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) --- local CurrentRoutePoint = CurrentPointVec3:RoutePointAir( --- POINT_VEC3.RoutePointAltType.BARO, --- POINT_VEC3.RoutePointType.TurningPoint, --- POINT_VEC3.RoutePointAction.TurningPoint, --- ToPatrolZoneSpeed, --- true --- ) --- --- PatrolRoute[#PatrolRoute+1] = CurrentRoutePoint - - self:T2( PatrolRoute ) - - if self.PatrolGroup:IsNotInZone( self.PatrolZone ) then - --- Find a random 2D point in PatrolZone. - local ToPatrolZoneVec2 = self.PatrolZone:GetRandomVec2() - self:T2( ToPatrolZoneVec2 ) - - --- Define Speed and Altitude. - local ToPatrolZoneAltitude = math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ) - local ToPatrolZoneSpeed = self.PatrolMaxSpeed - self:T2( ToPatrolZoneSpeed ) - - --- Obtain a 3D @{Point} from the 2D point + altitude. - local ToPatrolZonePointVec3 = POINT_VEC3:New( ToPatrolZoneVec2.x, ToPatrolZoneAltitude, ToPatrolZoneVec2.y ) - - --- Create a route point of type air. - local ToPatrolZoneRoutePoint = ToPatrolZonePointVec3:RoutePointAir( - POINT_VEC3.RoutePointAltType.BARO, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - ToPatrolZoneSpeed, - true - ) - - PatrolRoute[#PatrolRoute+1] = ToPatrolZoneRoutePoint - - end - - --- Define a random point in the @{Zone}. The AI will fly to that point within the zone. - - --- Find a random 2D point in PatrolZone. - local ToTargetVec2 = self.PatrolZone:GetRandomVec2() - self:T2( ToTargetVec2 ) - - --- Define Speed and Altitude. - local ToTargetAltitude = math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ) - local ToTargetSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed ) - self:T2( { self.PatrolMinSpeed, self.PatrolMaxSpeed, ToTargetSpeed } ) - - --- Obtain a 3D @{Point} from the 2D point + altitude. - local ToTargetPointVec3 = POINT_VEC3:New( ToTargetVec2.x, ToTargetAltitude, ToTargetVec2.y ) - - --- Create a route point of type air. - local ToTargetRoutePoint = ToTargetPointVec3:RoutePointAir( - POINT_VEC3.RoutePointAltType.BARO, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - ToTargetSpeed, - true - ) - - --ToTargetPointVec3:SmokeRed() - - PatrolRoute[#PatrolRoute+1] = ToTargetRoutePoint - - --- Now we're going to do something special, we're going to call a function from a waypoint action at the PatrolGroup... - self.PatrolGroup:WayPointInitialize( PatrolRoute ) - - --- Do a trick, link the NewPatrolRoute function of the PATROLGROUP object to the PatrolGroup in a temporary variable ... - self.PatrolGroup:SetState( self.PatrolGroup, "PatrolZone", self ) - self.PatrolGroup:WayPointFunction( #PatrolRoute, 1, "_NewPatrolRoute" ) - - --- NOW ROUTE THE GROUP! - self.PatrolGroup:WayPointExecute( 1, 2 ) - end - -end - ---- When the PatrolGroup is out of fuel, it is required that a new PatrolGroup is started, before the old PatrolGroup can return to the home base. --- Therefore, with a parameter and a calculation of the distance to the home base, the fuel treshold is calculated. --- When the fuel treshold is reached, the PatrolGroup will continue for a given time its patrol task in orbit, while a new PatrolGroup is targetted to the PATROLZONE. --- Once the time is finished, the old PatrolGroup will return to the base. --- @param #PATROLZONE self --- @param #number PatrolFuelTresholdPercentage The treshold in percentage (between 0 and 1) when the PatrolGroup is considered to get out of fuel. --- @param #number PatrolOutOfFuelOrbitTime The amount of seconds the out of fuel PatrolGroup will orbit before returning to the base. --- @return #PATROLZONE self -function PATROLZONE:ManageFuel( PatrolFuelTresholdPercentage, PatrolOutOfFuelOrbitTime ) - - self.PatrolManageFuel = true - self.PatrolFuelTresholdPercentage = PatrolFuelTresholdPercentage - self.PatrolOutOfFuelOrbitTime = PatrolOutOfFuelOrbitTime - - if self.PatrolGroup then - self.PatrolOutOfFuelMonitor = SCHEDULER:New( self, self._MonitorOutOfFuelScheduled, {}, 1, 120, 0 ) - self.SpawnPatrolGroup = SPAWN:New( self.PatrolGroupTemplateName ) - end - return self -end - ---- @param #PATROLZONE self -function _MonitorOutOfFuelScheduled( self ) - self:F2( "_MonitorOutOfFuelScheduled" ) - - if self.PatrolGroup and self.PatrolGroup:IsAlive() then - - local Fuel = self.PatrolGroup:GetUnit(1):GetFuel() - if Fuel < self.PatrolFuelTresholdPercentage then - local OldPatrolGroup = self.PatrolGroup - local PatrolGroupTemplate = self.PatrolGroup:GetTemplate() - - local OrbitTask = OldPatrolGroup:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed ) - local TimedOrbitTask = OldPatrolGroup:TaskControlled( OrbitTask, OldPatrolGroup:TaskCondition(nil,nil,nil,nil,self.PatrolOutOfFuelOrbitTime,nil ) ) - OldPatrolGroup:SetTask( TimedOrbitTask, 10 ) - - local NewPatrolGroup = self.SpawnPatrolGroup:Spawn() - self.PatrolGroup = NewPatrolGroup - self:NewPatrolRoute() - end - else - self.PatrolOutOfFuelMonitor:Stop() - end -end--- This module contains the AIBALANCER class. --- --- === --- --- 1) @{AIBalancer#AIBALANCER} class, extends @{Base#BASE} --- ================================================ --- The @{AIBalancer#AIBALANCER} class controls the dynamic spawning of AI GROUPS depending on a SET_CLIENT. --- There will be as many AI GROUPS spawned as there at CLIENTS in SET_CLIENT not spawned. --- --- 1.1) AIBALANCER construction method: --- ------------------------------------ --- Create a new AIBALANCER object with the @{#AIBALANCER.New} method: --- --- * @{#AIBALANCER.New}: Creates a new AIBALANCER object. --- --- 1.2) AIBALANCER returns AI to Airbases: --- --------------------------------------- --- You can configure to have the AI to return to: --- --- * @{#AIBALANCER.ReturnToHomeAirbase}: Returns the AI to the home @{Airbase#AIRBASE}. --- * @{#AIBALANCER.ReturnToNearestAirbases}: Returns the AI to the nearest friendly @{Airbase#AIRBASE}. --- --- 1.3) AIBALANCER allows AI to patrol specific zones: --- --------------------------------------------------- --- Use @{AIBalancer#AIBALANCER.SetPatrolZone}() to specify a zone where the AI needs to patrol. --- --- === --- --- ### Contributions: --- --- * **Dutch_Baron (James)** Who you can search on the Eagle Dynamics Forums. --- Working together with James has resulted in the creation of the AIBALANCER class. --- James has shared his ideas on balancing AI with air units, and together we made a first design which you can use now :-) --- --- * **SNAFU** --- Had a couple of mails with the guys to validate, if the same concept in the GCI/CAP script could be reworked within MOOSE. --- None of the script code has been used however within the new AIBALANCER moose class. --- --- ### Authors: --- --- * FlightControl - Framework Design & Programming --- --- @module AIBalancer - ---- AIBALANCER class --- @type AIBALANCER --- @field Set#SET_CLIENT SetClient --- @field Spawn#SPAWN SpawnAI --- @field #boolean ToNearestAirbase --- @field Set#SET_AIRBASE ReturnAirbaseSet --- @field DCSTypes#Distance ReturnTresholdRange --- @field #boolean ToHomeAirbase --- @field PatrolZone#PATROLZONE PatrolZone --- @extends Base#BASE -AIBALANCER = { - ClassName = "AIBALANCER", - PatrolZones = {}, - AIGroups = {}, -} - ---- Creates a new AIBALANCER object, building a set of units belonging to a coalitions, categories, countries, types or with defined prefix names. --- @param #AIBALANCER self --- @param SetClient A SET_CLIENT object that will contain the CLIENT objects to be monitored if they are alive or not (joined by a player). --- @param SpawnAI A SPAWN object that will spawn the AI units required, balancing the SetClient. --- @return #AIBALANCER self -function AIBALANCER:New( SetClient, SpawnAI ) - - -- Inherits from BASE - local self = BASE:Inherit( self, BASE:New() ) - - self.SetClient = SetClient - if type( SpawnAI ) == "table" then - if SpawnAI.ClassName and SpawnAI.ClassName == "SPAWN" then - self.SpawnAI = { SpawnAI } - else - local SpawnObjects = true - for SpawnObjectID, SpawnObject in pairs( SpawnAI ) do - if SpawnObject.ClassName and SpawnObject.ClassName == "SPAWN" then - self:E( SpawnObject.ClassName ) - else - self:E( "other object" ) - SpawnObjects = false - end - end - if SpawnObjects == true then - self.SpawnAI = SpawnAI - else - error( "No SPAWN object given in parameter SpawnAI, either as a single object or as a table of objects!" ) - end - end - end - - self.ToNearestAirbase = false - self.ReturnHomeAirbase = false - - self.AIMonitorSchedule = SCHEDULER:New( self, self._ClientAliveMonitorScheduler, {}, 1, 10, 0 ) - - return self -end - ---- Returns the AI to the nearest friendly @{Airbase#AIRBASE}. --- @param #AIBALANCER self --- @param DCSTypes#Distance ReturnTresholdRange If there is an enemy @{Client#CLIENT} within the ReturnTresholdRange given in meters, the AI will not return to the nearest @{Airbase#AIRBASE}. --- @param Set#SET_AIRBASE ReturnAirbaseSet The SET of @{Set#SET_AIRBASE}s to evaluate where to return to. -function AIBALANCER:ReturnToNearestAirbases( ReturnTresholdRange, ReturnAirbaseSet ) - - self.ToNearestAirbase = true - self.ReturnTresholdRange = ReturnTresholdRange - self.ReturnAirbaseSet = ReturnAirbaseSet -end - ---- Returns the AI to the home @{Airbase#AIRBASE}. --- @param #AIBALANCER self --- @param DCSTypes#Distance ReturnTresholdRange If there is an enemy @{Client#CLIENT} within the ReturnTresholdRange given in meters, the AI will not return to the nearest @{Airbase#AIRBASE}. -function AIBALANCER:ReturnToHomeAirbase( ReturnTresholdRange ) - - self.ToHomeAirbase = true - self.ReturnTresholdRange = ReturnTresholdRange -end - ---- Let the AI patrol a @{Zone} with a given Speed range and Altitude range. --- @param #AIBALANCER self --- @param PatrolZone#PATROLZONE PatrolZone The @{PatrolZone} where the AI needs to patrol. --- @return PatrolZone#PATROLZONE self -function AIBALANCER:SetPatrolZone( PatrolZone ) - - self.PatrolZone = PatrolZone -end - ---- @param #AIBALANCER self -function AIBALANCER:_ClientAliveMonitorScheduler() - - self.SetClient:ForEachClient( - --- @param Client#CLIENT Client - function( Client ) - local ClientAIAliveState = Client:GetState( self, 'AIAlive' ) - self:T( ClientAIAliveState ) - if Client:IsAlive() then - if ClientAIAliveState == true then - Client:SetState( self, 'AIAlive', false ) - - local AIGroup = self.AIGroups[Client.UnitName] -- Group#GROUP - --- local PatrolZone = Client:GetState( self, "PatrolZone" ) --- if PatrolZone then --- PatrolZone = nil --- Client:ClearState( self, "PatrolZone" ) --- end - - if self.ToNearestAirbase == false and self.ToHomeAirbase == false then - AIGroup:Destroy() - else - -- We test if there is no other CLIENT within the self.ReturnTresholdRange of the first unit of the AI group. - -- If there is a CLIENT, the AI stays engaged and will not return. - -- If there is no CLIENT within the self.ReturnTresholdRange, then the unit will return to the Airbase return method selected. - - local PlayerInRange = { Value = false } - local RangeZone = ZONE_RADIUS:New( 'RangeZone', AIGroup:GetVec2(), self.ReturnTresholdRange ) - - self:E( RangeZone ) - - _DATABASE:ForEachPlayer( - --- @param Unit#UNIT RangeTestUnit - function( RangeTestUnit, RangeZone, AIGroup, PlayerInRange ) - self:E( { PlayerInRange, RangeTestUnit.UnitName, RangeZone.ZoneName } ) - if RangeTestUnit:IsInZone( RangeZone ) == true then - self:E( "in zone" ) - if RangeTestUnit:GetCoalition() ~= AIGroup:GetCoalition() then - self:E( "in range" ) - PlayerInRange.Value = true - end - end - end, - - --- @param Zone#ZONE_RADIUS RangeZone - -- @param Group#GROUP AIGroup - function( RangeZone, AIGroup, PlayerInRange ) - local AIGroupTemplate = AIGroup:GetTemplate() - if PlayerInRange.Value == false then - if self.ToHomeAirbase == true then - local WayPointCount = #AIGroupTemplate.route.points - local SwitchWayPointCommand = AIGroup:CommandSwitchWayPoint( 1, WayPointCount, 1 ) - AIGroup:SetCommand( SwitchWayPointCommand ) - AIGroup:MessageToRed( "Returning to home base ...", 30 ) - else - -- Okay, we need to send this Group back to the nearest base of the Coalition of the AI. - --TODO: i need to rework the POINT_VEC2 thing. - local PointVec2 = POINT_VEC2:New( AIGroup:GetVec2().x, AIGroup:GetVec2().y ) - local ClosestAirbase = self.ReturnAirbaseSet:FindNearestAirbaseFromPointVec2( PointVec2 ) - self:T( ClosestAirbase.AirbaseName ) - AIGroup:MessageToRed( "Returning to " .. ClosestAirbase:GetName().. " ...", 30 ) - local RTBRoute = AIGroup:RouteReturnToAirbase( ClosestAirbase ) - AIGroupTemplate.route = RTBRoute - AIGroup:Respawn( AIGroupTemplate ) - end - end - end - , RangeZone, AIGroup, PlayerInRange - ) - - end - end - else - if not ClientAIAliveState or ClientAIAliveState == false then - Client:SetState( self, 'AIAlive', true ) - - - -- OK, spawn a new group from the SpawnAI objects provided. - local SpawnAICount = #self.SpawnAI - local SpawnAIIndex = math.random( 1, SpawnAICount ) - local AIGroup = self.SpawnAI[SpawnAIIndex]:Spawn() - AIGroup:E( "spawning new AIGroup" ) - --TODO: need to rework UnitName thing ... - self.AIGroups[Client.UnitName] = AIGroup - - --- Now test if the AIGroup needs to patrol a zone, otherwise let it follow its route... - if self.PatrolZone then - self.PatrolZones[#self.PatrolZones+1] = PATROLZONE:New( - self.PatrolZone.PatrolZone, - self.PatrolZone.PatrolFloorAltitude, - self.PatrolZone.PatrolCeilingAltitude, - self.PatrolZone.PatrolMinSpeed, - self.PatrolZone.PatrolMaxSpeed - ) - - if self.PatrolZone.PatrolManageFuel == true then - self.PatrolZones[#self.PatrolZones]:ManageFuel( self.PatrolZone.PatrolFuelTresholdPercentage, self.PatrolZone.PatrolOutOfFuelOrbitTime ) - end - self.PatrolZones[#self.PatrolZones]:SetGroup( AIGroup ) - - --self.PatrolZones[#self.PatrolZones+1] = PatrolZone - - --Client:SetState( self, "PatrolZone", PatrolZone ) - end - end - end - end - ) - return true -end - - - --- This module contains the AIRBASEPOLICE classes. -- -- === -- --- 1) @{AirbasePolice#AIRBASEPOLICE_BASE} class, extends @{Base#BASE} +-- 1) @{Functional.AirbasePolice#AIRBASEPOLICE_BASE} class, extends @{Core.Base#BASE} -- ================================================================== --- The @{AirbasePolice#AIRBASEPOLICE_BASE} class provides the main methods to monitor CLIENT behaviour at airbases. +-- The @{Functional.AirbasePolice#AIRBASEPOLICE_BASE} class provides the main methods to monitor CLIENT behaviour at airbases. -- CLIENTS should not be allowed to: -- -- * Don't taxi faster than 40 km/h. @@ -24960,7 +21557,7 @@ end -- * Avoid to hit other planes on the airbase. -- * Obey ground control orders. -- --- 2) @{AirbasePolice#AIRBASEPOLICE_CAUCASUS} class, extends @{AirbasePolice#AIRBASEPOLICE_BASE} +-- 2) @{Functional.AirbasePolice#AIRBASEPOLICE_CAUCASUS} class, extends @{Functional.AirbasePolice#AIRBASEPOLICE_BASE} -- ============================================================================================= -- All the airbases on the caucasus map can be monitored using this class. -- If you want to monitor specific airbases, you need to use the @{#AIRBASEPOLICE_BASE.Monitor}() method, which takes a table or airbase names. @@ -24987,7 +21584,7 @@ end -- * TbilisiLochini -- * Vaziani -- --- 3) @{AirbasePolice#AIRBASEPOLICE_NEVADA} class, extends @{AirbasePolice#AIRBASEPOLICE_BASE} +-- 3) @{Functional.AirbasePolice#AIRBASEPOLICE_NEVADA} class, extends @{Functional.AirbasePolice#AIRBASEPOLICE_BASE} -- ============================================================================================= -- All the airbases on the NEVADA map can be monitored using this class. -- If you want to monitor specific airbases, you need to use the @{#AIRBASEPOLICE_BASE.Monitor}() method, which takes a table or airbase names. @@ -25007,8 +21604,8 @@ end --- @type AIRBASEPOLICE_BASE --- @field Set#SET_CLIENT SetClient --- @extends Base#BASE +-- @field Core.Set#SET_CLIENT SetClient +-- @extends Core.Base#BASE AIRBASEPOLICE_BASE = { ClassName = "AIRBASEPOLICE_BASE", @@ -25033,21 +21630,21 @@ function AIRBASEPOLICE_BASE:New( SetClient, Airbases ) self.Airbases = Airbases for AirbaseID, Airbase in pairs( self.Airbases ) do - Airbase.ZoneBoundary = ZONE_POLYGON_BASE:New( "Boundary", Airbase.PointsBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + Airbase.ZoneBoundary = ZONE_POLYGON_BASE:New( "Boundary", Airbase.PointsBoundary ):SmokeZone(SMOKECOLOR.White):Flush() for PointsRunwayID, PointsRunway in pairs( Airbase.PointsRunways ) do - Airbase.ZoneRunways[PointsRunwayID] = ZONE_POLYGON_BASE:New( "Runway " .. PointsRunwayID, PointsRunway ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + Airbase.ZoneRunways[PointsRunwayID] = ZONE_POLYGON_BASE:New( "Runway " .. PointsRunwayID, PointsRunway ):SmokeZone(SMOKECOLOR.Red):Flush() end end -- -- Template -- local TemplateBoundary = GROUP:FindByName( "Template Boundary" ) --- self.Airbases.Template.ZoneBoundary = ZONE_POLYGON:New( "Template Boundary", TemplateBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() +-- self.Airbases.Template.ZoneBoundary = ZONE_POLYGON:New( "Template Boundary", TemplateBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local TemplateRunway1 = GROUP:FindByName( "Template Runway 1" ) --- self.Airbases.Template.ZoneRunways[1] = ZONE_POLYGON:New( "Template Runway 1", TemplateRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() +-- self.Airbases.Template.ZoneRunways[1] = ZONE_POLYGON:New( "Template Runway 1", TemplateRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() self.SetClient:ForEachClient( - --- @param Client#CLIENT Client + --- @param Wrapper.Client#CLIENT Client function( Client ) Client:SetState( self, "Speeding", false ) Client:SetState( self, "Warnings", 0) @@ -25089,7 +21686,7 @@ function AIRBASEPOLICE_BASE:_AirbaseMonitor() self.SetClient:ForEachClientInZone( Airbase.ZoneBoundary, - --- @param Client#CLIENT Client + --- @param Wrapper.Client#CLIENT Client function( Client ) self:E( Client.UnitName ) @@ -25167,7 +21764,7 @@ end --- @type AIRBASEPOLICE_CAUCASUS --- @field Set#SET_CLIENT SetClient +-- @field Core.Set#SET_CLIENT SetClient -- @extends #AIRBASEPOLICE_BASE AIRBASEPOLICE_CAUCASUS = { @@ -25697,197 +22294,197 @@ function AIRBASEPOLICE_CAUCASUS:New( SetClient ) -- -- AnapaVityazevo -- local AnapaVityazevoBoundary = GROUP:FindByName( "AnapaVityazevo Boundary" ) - -- self.Airbases.AnapaVityazevo.ZoneBoundary = ZONE_POLYGON:New( "AnapaVityazevo Boundary", AnapaVityazevoBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.AnapaVityazevo.ZoneBoundary = ZONE_POLYGON:New( "AnapaVityazevo Boundary", AnapaVityazevoBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local AnapaVityazevoRunway1 = GROUP:FindByName( "AnapaVityazevo Runway 1" ) - -- self.Airbases.AnapaVityazevo.ZoneRunways[1] = ZONE_POLYGON:New( "AnapaVityazevo Runway 1", AnapaVityazevoRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.AnapaVityazevo.ZoneRunways[1] = ZONE_POLYGON:New( "AnapaVityazevo Runway 1", AnapaVityazevoRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- Batumi -- local BatumiBoundary = GROUP:FindByName( "Batumi Boundary" ) - -- self.Airbases.Batumi.ZoneBoundary = ZONE_POLYGON:New( "Batumi Boundary", BatumiBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.Batumi.ZoneBoundary = ZONE_POLYGON:New( "Batumi Boundary", BatumiBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local BatumiRunway1 = GROUP:FindByName( "Batumi Runway 1" ) - -- self.Airbases.Batumi.ZoneRunways[1] = ZONE_POLYGON:New( "Batumi Runway 1", BatumiRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.Batumi.ZoneRunways[1] = ZONE_POLYGON:New( "Batumi Runway 1", BatumiRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- Beslan -- local BeslanBoundary = GROUP:FindByName( "Beslan Boundary" ) - -- self.Airbases.Beslan.ZoneBoundary = ZONE_POLYGON:New( "Beslan Boundary", BeslanBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.Beslan.ZoneBoundary = ZONE_POLYGON:New( "Beslan Boundary", BeslanBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local BeslanRunway1 = GROUP:FindByName( "Beslan Runway 1" ) - -- self.Airbases.Beslan.ZoneRunways[1] = ZONE_POLYGON:New( "Beslan Runway 1", BeslanRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.Beslan.ZoneRunways[1] = ZONE_POLYGON:New( "Beslan Runway 1", BeslanRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- Gelendzhik -- local GelendzhikBoundary = GROUP:FindByName( "Gelendzhik Boundary" ) - -- self.Airbases.Gelendzhik.ZoneBoundary = ZONE_POLYGON:New( "Gelendzhik Boundary", GelendzhikBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.Gelendzhik.ZoneBoundary = ZONE_POLYGON:New( "Gelendzhik Boundary", GelendzhikBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local GelendzhikRunway1 = GROUP:FindByName( "Gelendzhik Runway 1" ) - -- self.Airbases.Gelendzhik.ZoneRunways[1] = ZONE_POLYGON:New( "Gelendzhik Runway 1", GelendzhikRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.Gelendzhik.ZoneRunways[1] = ZONE_POLYGON:New( "Gelendzhik Runway 1", GelendzhikRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- Gudauta -- local GudautaBoundary = GROUP:FindByName( "Gudauta Boundary" ) - -- self.Airbases.Gudauta.ZoneBoundary = ZONE_POLYGON:New( "Gudauta Boundary", GudautaBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.Gudauta.ZoneBoundary = ZONE_POLYGON:New( "Gudauta Boundary", GudautaBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local GudautaRunway1 = GROUP:FindByName( "Gudauta Runway 1" ) - -- self.Airbases.Gudauta.ZoneRunways[1] = ZONE_POLYGON:New( "Gudauta Runway 1", GudautaRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.Gudauta.ZoneRunways[1] = ZONE_POLYGON:New( "Gudauta Runway 1", GudautaRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- Kobuleti -- local KobuletiBoundary = GROUP:FindByName( "Kobuleti Boundary" ) - -- self.Airbases.Kobuleti.ZoneBoundary = ZONE_POLYGON:New( "Kobuleti Boundary", KobuletiBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.Kobuleti.ZoneBoundary = ZONE_POLYGON:New( "Kobuleti Boundary", KobuletiBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local KobuletiRunway1 = GROUP:FindByName( "Kobuleti Runway 1" ) - -- self.Airbases.Kobuleti.ZoneRunways[1] = ZONE_POLYGON:New( "Kobuleti Runway 1", KobuletiRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.Kobuleti.ZoneRunways[1] = ZONE_POLYGON:New( "Kobuleti Runway 1", KobuletiRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- KrasnodarCenter -- local KrasnodarCenterBoundary = GROUP:FindByName( "KrasnodarCenter Boundary" ) - -- self.Airbases.KrasnodarCenter.ZoneBoundary = ZONE_POLYGON:New( "KrasnodarCenter Boundary", KrasnodarCenterBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.KrasnodarCenter.ZoneBoundary = ZONE_POLYGON:New( "KrasnodarCenter Boundary", KrasnodarCenterBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local KrasnodarCenterRunway1 = GROUP:FindByName( "KrasnodarCenter Runway 1" ) - -- self.Airbases.KrasnodarCenter.ZoneRunways[1] = ZONE_POLYGON:New( "KrasnodarCenter Runway 1", KrasnodarCenterRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.KrasnodarCenter.ZoneRunways[1] = ZONE_POLYGON:New( "KrasnodarCenter Runway 1", KrasnodarCenterRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- KrasnodarPashkovsky -- local KrasnodarPashkovskyBoundary = GROUP:FindByName( "KrasnodarPashkovsky Boundary" ) - -- self.Airbases.KrasnodarPashkovsky.ZoneBoundary = ZONE_POLYGON:New( "KrasnodarPashkovsky Boundary", KrasnodarPashkovskyBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.KrasnodarPashkovsky.ZoneBoundary = ZONE_POLYGON:New( "KrasnodarPashkovsky Boundary", KrasnodarPashkovskyBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local KrasnodarPashkovskyRunway1 = GROUP:FindByName( "KrasnodarPashkovsky Runway 1" ) - -- self.Airbases.KrasnodarPashkovsky.ZoneRunways[1] = ZONE_POLYGON:New( "KrasnodarPashkovsky Runway 1", KrasnodarPashkovskyRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.KrasnodarPashkovsky.ZoneRunways[1] = ZONE_POLYGON:New( "KrasnodarPashkovsky Runway 1", KrasnodarPashkovskyRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- local KrasnodarPashkovskyRunway2 = GROUP:FindByName( "KrasnodarPashkovsky Runway 2" ) - -- self.Airbases.KrasnodarPashkovsky.ZoneRunways[2] = ZONE_POLYGON:New( "KrasnodarPashkovsky Runway 2", KrasnodarPashkovskyRunway2 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.KrasnodarPashkovsky.ZoneRunways[2] = ZONE_POLYGON:New( "KrasnodarPashkovsky Runway 2", KrasnodarPashkovskyRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- Krymsk -- local KrymskBoundary = GROUP:FindByName( "Krymsk Boundary" ) - -- self.Airbases.Krymsk.ZoneBoundary = ZONE_POLYGON:New( "Krymsk Boundary", KrymskBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.Krymsk.ZoneBoundary = ZONE_POLYGON:New( "Krymsk Boundary", KrymskBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local KrymskRunway1 = GROUP:FindByName( "Krymsk Runway 1" ) - -- self.Airbases.Krymsk.ZoneRunways[1] = ZONE_POLYGON:New( "Krymsk Runway 1", KrymskRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.Krymsk.ZoneRunways[1] = ZONE_POLYGON:New( "Krymsk Runway 1", KrymskRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- Kutaisi -- local KutaisiBoundary = GROUP:FindByName( "Kutaisi Boundary" ) - -- self.Airbases.Kutaisi.ZoneBoundary = ZONE_POLYGON:New( "Kutaisi Boundary", KutaisiBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.Kutaisi.ZoneBoundary = ZONE_POLYGON:New( "Kutaisi Boundary", KutaisiBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local KutaisiRunway1 = GROUP:FindByName( "Kutaisi Runway 1" ) - -- self.Airbases.Kutaisi.ZoneRunways[1] = ZONE_POLYGON:New( "Kutaisi Runway 1", KutaisiRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.Kutaisi.ZoneRunways[1] = ZONE_POLYGON:New( "Kutaisi Runway 1", KutaisiRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- MaykopKhanskaya -- local MaykopKhanskayaBoundary = GROUP:FindByName( "MaykopKhanskaya Boundary" ) - -- self.Airbases.MaykopKhanskaya.ZoneBoundary = ZONE_POLYGON:New( "MaykopKhanskaya Boundary", MaykopKhanskayaBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.MaykopKhanskaya.ZoneBoundary = ZONE_POLYGON:New( "MaykopKhanskaya Boundary", MaykopKhanskayaBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local MaykopKhanskayaRunway1 = GROUP:FindByName( "MaykopKhanskaya Runway 1" ) - -- self.Airbases.MaykopKhanskaya.ZoneRunways[1] = ZONE_POLYGON:New( "MaykopKhanskaya Runway 1", MaykopKhanskayaRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.MaykopKhanskaya.ZoneRunways[1] = ZONE_POLYGON:New( "MaykopKhanskaya Runway 1", MaykopKhanskayaRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- MineralnyeVody -- local MineralnyeVodyBoundary = GROUP:FindByName( "MineralnyeVody Boundary" ) - -- self.Airbases.MineralnyeVody.ZoneBoundary = ZONE_POLYGON:New( "MineralnyeVody Boundary", MineralnyeVodyBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.MineralnyeVody.ZoneBoundary = ZONE_POLYGON:New( "MineralnyeVody Boundary", MineralnyeVodyBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local MineralnyeVodyRunway1 = GROUP:FindByName( "MineralnyeVody Runway 1" ) - -- self.Airbases.MineralnyeVody.ZoneRunways[1] = ZONE_POLYGON:New( "MineralnyeVody Runway 1", MineralnyeVodyRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.MineralnyeVody.ZoneRunways[1] = ZONE_POLYGON:New( "MineralnyeVody Runway 1", MineralnyeVodyRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- Mozdok -- local MozdokBoundary = GROUP:FindByName( "Mozdok Boundary" ) - -- self.Airbases.Mozdok.ZoneBoundary = ZONE_POLYGON:New( "Mozdok Boundary", MozdokBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.Mozdok.ZoneBoundary = ZONE_POLYGON:New( "Mozdok Boundary", MozdokBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local MozdokRunway1 = GROUP:FindByName( "Mozdok Runway 1" ) - -- self.Airbases.Mozdok.ZoneRunways[1] = ZONE_POLYGON:New( "Mozdok Runway 1", MozdokRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.Mozdok.ZoneRunways[1] = ZONE_POLYGON:New( "Mozdok Runway 1", MozdokRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- Nalchik -- local NalchikBoundary = GROUP:FindByName( "Nalchik Boundary" ) - -- self.Airbases.Nalchik.ZoneBoundary = ZONE_POLYGON:New( "Nalchik Boundary", NalchikBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.Nalchik.ZoneBoundary = ZONE_POLYGON:New( "Nalchik Boundary", NalchikBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local NalchikRunway1 = GROUP:FindByName( "Nalchik Runway 1" ) - -- self.Airbases.Nalchik.ZoneRunways[1] = ZONE_POLYGON:New( "Nalchik Runway 1", NalchikRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.Nalchik.ZoneRunways[1] = ZONE_POLYGON:New( "Nalchik Runway 1", NalchikRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- Novorossiysk -- local NovorossiyskBoundary = GROUP:FindByName( "Novorossiysk Boundary" ) - -- self.Airbases.Novorossiysk.ZoneBoundary = ZONE_POLYGON:New( "Novorossiysk Boundary", NovorossiyskBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.Novorossiysk.ZoneBoundary = ZONE_POLYGON:New( "Novorossiysk Boundary", NovorossiyskBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local NovorossiyskRunway1 = GROUP:FindByName( "Novorossiysk Runway 1" ) - -- self.Airbases.Novorossiysk.ZoneRunways[1] = ZONE_POLYGON:New( "Novorossiysk Runway 1", NovorossiyskRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.Novorossiysk.ZoneRunways[1] = ZONE_POLYGON:New( "Novorossiysk Runway 1", NovorossiyskRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- SenakiKolkhi -- local SenakiKolkhiBoundary = GROUP:FindByName( "SenakiKolkhi Boundary" ) - -- self.Airbases.SenakiKolkhi.ZoneBoundary = ZONE_POLYGON:New( "SenakiKolkhi Boundary", SenakiKolkhiBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.SenakiKolkhi.ZoneBoundary = ZONE_POLYGON:New( "SenakiKolkhi Boundary", SenakiKolkhiBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local SenakiKolkhiRunway1 = GROUP:FindByName( "SenakiKolkhi Runway 1" ) - -- self.Airbases.SenakiKolkhi.ZoneRunways[1] = ZONE_POLYGON:New( "SenakiKolkhi Runway 1", SenakiKolkhiRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.SenakiKolkhi.ZoneRunways[1] = ZONE_POLYGON:New( "SenakiKolkhi Runway 1", SenakiKolkhiRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- SochiAdler -- local SochiAdlerBoundary = GROUP:FindByName( "SochiAdler Boundary" ) - -- self.Airbases.SochiAdler.ZoneBoundary = ZONE_POLYGON:New( "SochiAdler Boundary", SochiAdlerBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.SochiAdler.ZoneBoundary = ZONE_POLYGON:New( "SochiAdler Boundary", SochiAdlerBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local SochiAdlerRunway1 = GROUP:FindByName( "SochiAdler Runway 1" ) - -- self.Airbases.SochiAdler.ZoneRunways[1] = ZONE_POLYGON:New( "SochiAdler Runway 1", SochiAdlerRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.SochiAdler.ZoneRunways[1] = ZONE_POLYGON:New( "SochiAdler Runway 1", SochiAdlerRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- local SochiAdlerRunway2 = GROUP:FindByName( "SochiAdler Runway 2" ) - -- self.Airbases.SochiAdler.ZoneRunways[2] = ZONE_POLYGON:New( "SochiAdler Runway 2", SochiAdlerRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.SochiAdler.ZoneRunways[2] = ZONE_POLYGON:New( "SochiAdler Runway 2", SochiAdlerRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- Soganlug -- local SoganlugBoundary = GROUP:FindByName( "Soganlug Boundary" ) - -- self.Airbases.Soganlug.ZoneBoundary = ZONE_POLYGON:New( "Soganlug Boundary", SoganlugBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.Soganlug.ZoneBoundary = ZONE_POLYGON:New( "Soganlug Boundary", SoganlugBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local SoganlugRunway1 = GROUP:FindByName( "Soganlug Runway 1" ) - -- self.Airbases.Soganlug.ZoneRunways[1] = ZONE_POLYGON:New( "Soganlug Runway 1", SoganlugRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.Soganlug.ZoneRunways[1] = ZONE_POLYGON:New( "Soganlug Runway 1", SoganlugRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- SukhumiBabushara -- local SukhumiBabusharaBoundary = GROUP:FindByName( "SukhumiBabushara Boundary" ) - -- self.Airbases.SukhumiBabushara.ZoneBoundary = ZONE_POLYGON:New( "SukhumiBabushara Boundary", SukhumiBabusharaBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.SukhumiBabushara.ZoneBoundary = ZONE_POLYGON:New( "SukhumiBabushara Boundary", SukhumiBabusharaBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local SukhumiBabusharaRunway1 = GROUP:FindByName( "SukhumiBabushara Runway 1" ) - -- self.Airbases.SukhumiBabushara.ZoneRunways[1] = ZONE_POLYGON:New( "SukhumiBabushara Runway 1", SukhumiBabusharaRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.SukhumiBabushara.ZoneRunways[1] = ZONE_POLYGON:New( "SukhumiBabushara Runway 1", SukhumiBabusharaRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- TbilisiLochini -- local TbilisiLochiniBoundary = GROUP:FindByName( "TbilisiLochini Boundary" ) - -- self.Airbases.TbilisiLochini.ZoneBoundary = ZONE_POLYGON:New( "TbilisiLochini Boundary", TbilisiLochiniBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.TbilisiLochini.ZoneBoundary = ZONE_POLYGON:New( "TbilisiLochini Boundary", TbilisiLochiniBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local TbilisiLochiniRunway1 = GROUP:FindByName( "TbilisiLochini Runway 1" ) - -- self.Airbases.TbilisiLochini.ZoneRunways[1] = ZONE_POLYGON:New( "TbilisiLochini Runway 1", TbilisiLochiniRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.TbilisiLochini.ZoneRunways[1] = ZONE_POLYGON:New( "TbilisiLochini Runway 1", TbilisiLochiniRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- local TbilisiLochiniRunway2 = GROUP:FindByName( "TbilisiLochini Runway 2" ) - -- self.Airbases.TbilisiLochini.ZoneRunways[2] = ZONE_POLYGON:New( "TbilisiLochini Runway 2", TbilisiLochiniRunway2 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.TbilisiLochini.ZoneRunways[2] = ZONE_POLYGON:New( "TbilisiLochini Runway 2", TbilisiLochiniRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- -- -- Vaziani -- local VazianiBoundary = GROUP:FindByName( "Vaziani Boundary" ) - -- self.Airbases.Vaziani.ZoneBoundary = ZONE_POLYGON:New( "Vaziani Boundary", VazianiBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.Vaziani.ZoneBoundary = ZONE_POLYGON:New( "Vaziani Boundary", VazianiBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local VazianiRunway1 = GROUP:FindByName( "Vaziani Runway 1" ) - -- self.Airbases.Vaziani.ZoneRunways[1] = ZONE_POLYGON:New( "Vaziani Runway 1", VazianiRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.Vaziani.ZoneRunways[1] = ZONE_POLYGON:New( "Vaziani Runway 1", VazianiRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- @@ -25895,10 +22492,10 @@ function AIRBASEPOLICE_CAUCASUS:New( SetClient ) -- Template -- local TemplateBoundary = GROUP:FindByName( "Template Boundary" ) - -- self.Airbases.Template.ZoneBoundary = ZONE_POLYGON:New( "Template Boundary", TemplateBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() + -- self.Airbases.Template.ZoneBoundary = ZONE_POLYGON:New( "Template Boundary", TemplateBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local TemplateRunway1 = GROUP:FindByName( "Template Runway 1" ) - -- self.Airbases.Template.ZoneRunways[1] = ZONE_POLYGON:New( "Template Runway 1", TemplateRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() + -- self.Airbases.Template.ZoneRunways[1] = ZONE_POLYGON:New( "Template Runway 1", TemplateRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() return self @@ -25908,7 +22505,7 @@ end --- @type AIRBASEPOLICE_NEVADA --- @extends AirbasePolice#AIRBASEPOLICE_BASE +-- @extends Functional.AirbasePolice#AIRBASEPOLICE_BASE AIRBASEPOLICE_NEVADA = { ClassName = "AIRBASEPOLICE_NEVADA", Airbases = { @@ -26091,49 +22688,49 @@ function AIRBASEPOLICE_NEVADA:New( SetClient ) -- -- Nellis -- local NellisBoundary = GROUP:FindByName( "Nellis Boundary" ) --- self.Airbases.Nellis.ZoneBoundary = ZONE_POLYGON:New( "Nellis Boundary", NellisBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() +-- self.Airbases.Nellis.ZoneBoundary = ZONE_POLYGON:New( "Nellis Boundary", NellisBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local NellisRunway1 = GROUP:FindByName( "Nellis Runway 1" ) --- self.Airbases.Nellis.ZoneRunways[1] = ZONE_POLYGON:New( "Nellis Runway 1", NellisRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() +-- self.Airbases.Nellis.ZoneRunways[1] = ZONE_POLYGON:New( "Nellis Runway 1", NellisRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- local NellisRunway2 = GROUP:FindByName( "Nellis Runway 2" ) --- self.Airbases.Nellis.ZoneRunways[2] = ZONE_POLYGON:New( "Nellis Runway 2", NellisRunway2 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() +-- self.Airbases.Nellis.ZoneRunways[2] = ZONE_POLYGON:New( "Nellis Runway 2", NellisRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- McCarran -- local McCarranBoundary = GROUP:FindByName( "McCarran Boundary" ) --- self.Airbases.McCarran.ZoneBoundary = ZONE_POLYGON:New( "McCarran Boundary", McCarranBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() +-- self.Airbases.McCarran.ZoneBoundary = ZONE_POLYGON:New( "McCarran Boundary", McCarranBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local McCarranRunway1 = GROUP:FindByName( "McCarran Runway 1" ) --- self.Airbases.McCarran.ZoneRunways[1] = ZONE_POLYGON:New( "McCarran Runway 1", McCarranRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() +-- self.Airbases.McCarran.ZoneRunways[1] = ZONE_POLYGON:New( "McCarran Runway 1", McCarranRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- local McCarranRunway2 = GROUP:FindByName( "McCarran Runway 2" ) --- self.Airbases.McCarran.ZoneRunways[2] = ZONE_POLYGON:New( "McCarran Runway 2", McCarranRunway2 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() +-- self.Airbases.McCarran.ZoneRunways[2] = ZONE_POLYGON:New( "McCarran Runway 2", McCarranRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- local McCarranRunway3 = GROUP:FindByName( "McCarran Runway 3" ) --- self.Airbases.McCarran.ZoneRunways[3] = ZONE_POLYGON:New( "McCarran Runway 3", McCarranRunway3 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() +-- self.Airbases.McCarran.ZoneRunways[3] = ZONE_POLYGON:New( "McCarran Runway 3", McCarranRunway3 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- local McCarranRunway4 = GROUP:FindByName( "McCarran Runway 4" ) --- self.Airbases.McCarran.ZoneRunways[4] = ZONE_POLYGON:New( "McCarran Runway 4", McCarranRunway4 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() +-- self.Airbases.McCarran.ZoneRunways[4] = ZONE_POLYGON:New( "McCarran Runway 4", McCarranRunway4 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- Creech -- local CreechBoundary = GROUP:FindByName( "Creech Boundary" ) --- self.Airbases.Creech.ZoneBoundary = ZONE_POLYGON:New( "Creech Boundary", CreechBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() +-- self.Airbases.Creech.ZoneBoundary = ZONE_POLYGON:New( "Creech Boundary", CreechBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local CreechRunway1 = GROUP:FindByName( "Creech Runway 1" ) --- self.Airbases.Creech.ZoneRunways[1] = ZONE_POLYGON:New( "Creech Runway 1", CreechRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() +-- self.Airbases.Creech.ZoneRunways[1] = ZONE_POLYGON:New( "Creech Runway 1", CreechRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- local CreechRunway2 = GROUP:FindByName( "Creech Runway 2" ) --- self.Airbases.Creech.ZoneRunways[2] = ZONE_POLYGON:New( "Creech Runway 2", CreechRunway2 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() +-- self.Airbases.Creech.ZoneRunways[2] = ZONE_POLYGON:New( "Creech Runway 2", CreechRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- -- Groom Lake -- local GroomLakeBoundary = GROUP:FindByName( "GroomLake Boundary" ) --- self.Airbases.GroomLake.ZoneBoundary = ZONE_POLYGON:New( "GroomLake Boundary", GroomLakeBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() +-- self.Airbases.GroomLake.ZoneBoundary = ZONE_POLYGON:New( "GroomLake Boundary", GroomLakeBoundary ):SmokeZone(SMOKECOLOR.White):Flush() -- -- local GroomLakeRunway1 = GROUP:FindByName( "GroomLake Runway 1" ) --- self.Airbases.GroomLake.ZoneRunways[1] = ZONE_POLYGON:New( "GroomLake Runway 1", GroomLakeRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() +-- self.Airbases.GroomLake.ZoneRunways[1] = ZONE_POLYGON:New( "GroomLake Runway 1", GroomLakeRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() -- -- local GroomLakeRunway2 = GROUP:FindByName( "GroomLake Runway 2" ) --- self.Airbases.GroomLake.ZoneRunways[2] = ZONE_POLYGON:New( "GroomLake Runway 2", GroomLakeRunway2 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() +-- self.Airbases.GroomLake.ZoneRunways[2] = ZONE_POLYGON:New( "GroomLake Runway 2", GroomLakeRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush() end @@ -26145,14 +22742,14 @@ end -- -- === -- --- 1) @{Detection#DETECTION_BASE} class, extends @{Base#BASE} +-- 1) @{Functional.Detection#DETECTION_BASE} class, extends @{Core.Base#BASE} -- ========================================================== --- The @{Detection#DETECTION_BASE} class defines the core functions to administer detected objects. --- The @{Detection#DETECTION_BASE} class will detect objects within the battle zone for a list of @{Group}s detecting targets following (a) detection method(s). +-- The @{Functional.Detection#DETECTION_BASE} class defines the core functions to administer detected objects. +-- The @{Functional.Detection#DETECTION_BASE} class will detect objects within the battle zone for a list of @{Group}s detecting targets following (a) detection method(s). -- -- 1.1) DETECTION_BASE constructor -- ------------------------------- --- Construct a new DETECTION_BASE instance using the @{Detection#DETECTION_BASE.New}() method. +-- Construct a new DETECTION_BASE instance using the @{Functional.Detection#DETECTION_BASE.New}() method. -- -- 1.2) DETECTION_BASE initialization -- ---------------------------------- @@ -26163,46 +22760,46 @@ end -- -- Use the following functions to report the objects it detected using the methods Visual, Optical, Radar, IRST, RWR, DLINK: -- --- * @{Detection#DETECTION_BASE.InitDetectVisual}(): Detected using Visual. --- * @{Detection#DETECTION_BASE.InitDetectOptical}(): Detected using Optical. --- * @{Detection#DETECTION_BASE.InitDetectRadar}(): Detected using Radar. --- * @{Detection#DETECTION_BASE.InitDetectIRST}(): Detected using IRST. --- * @{Detection#DETECTION_BASE.InitDetectRWR}(): Detected using RWR. --- * @{Detection#DETECTION_BASE.InitDetectDLINK}(): Detected using DLINK. +-- * @{Functional.Detection#DETECTION_BASE.InitDetectVisual}(): Detected using Visual. +-- * @{Functional.Detection#DETECTION_BASE.InitDetectOptical}(): Detected using Optical. +-- * @{Functional.Detection#DETECTION_BASE.InitDetectRadar}(): Detected using Radar. +-- * @{Functional.Detection#DETECTION_BASE.InitDetectIRST}(): Detected using IRST. +-- * @{Functional.Detection#DETECTION_BASE.InitDetectRWR}(): Detected using RWR. +-- * @{Functional.Detection#DETECTION_BASE.InitDetectDLINK}(): Detected using DLINK. -- -- 1.3) Obtain objects detected by DETECTION_BASE -- ---------------------------------------------- --- DETECTION_BASE builds @{Set}s of objects detected. These @{Set#SET_BASE}s can be retrieved using the method @{Detection#DETECTION_BASE.GetDetectedSets}(). --- The method will return a list (table) of @{Set#SET_BASE} objects. +-- DETECTION_BASE builds @{Set}s of objects detected. These @{Core.Set#SET_BASE}s can be retrieved using the method @{Functional.Detection#DETECTION_BASE.GetDetectedSets}(). +-- The method will return a list (table) of @{Core.Set#SET_BASE} objects. -- -- === -- --- 2) @{Detection#DETECTION_AREAS} class, extends @{Detection#DETECTION_BASE} +-- 2) @{Functional.Detection#DETECTION_AREAS} class, extends @{Functional.Detection#DETECTION_BASE} -- =============================================================================== --- The @{Detection#DETECTION_AREAS} class will detect units within the battle zone for a list of @{Group}s detecting targets following (a) detection method(s), --- and will build a list (table) of @{Set#SET_UNIT}s containing the @{Unit#UNIT}s detected. +-- The @{Functional.Detection#DETECTION_AREAS} class will detect units within the battle zone for a list of @{Group}s detecting targets following (a) detection method(s), +-- and will build a list (table) of @{Core.Set#SET_UNIT}s containing the @{Wrapper.Unit#UNIT}s detected. -- The class is group the detected units within zones given a DetectedZoneRange parameter. -- A set with multiple detected zones will be created as there are groups of units detected. -- -- 2.1) Retrieve the Detected Unit sets and Detected Zones -- ------------------------------------------------------- --- The DetectedUnitSets methods are implemented in @{Detection#DECTECTION_BASE} and the DetectedZones methods is implemented in @{Detection#DETECTION_AREAS}. +-- The DetectedUnitSets methods are implemented in @{Functional.Detection#DECTECTION_BASE} and the DetectedZones methods is implemented in @{Functional.Detection#DETECTION_AREAS}. -- --- Retrieve the DetectedUnitSets with the method @{Detection#DETECTION_BASE.GetDetectedSets}(). A table will be return of @{Set#SET_UNIT}s. --- To understand the amount of sets created, use the method @{Detection#DETECTION_BASE.GetDetectedSetCount}(). --- If you want to obtain a specific set from the DetectedSets, use the method @{Detection#DETECTION_BASE.GetDetectedSet}() with a given index. +-- Retrieve the DetectedUnitSets with the method @{Functional.Detection#DETECTION_BASE.GetDetectedSets}(). A table will be return of @{Core.Set#SET_UNIT}s. +-- To understand the amount of sets created, use the method @{Functional.Detection#DETECTION_BASE.GetDetectedSetCount}(). +-- If you want to obtain a specific set from the DetectedSets, use the method @{Functional.Detection#DETECTION_BASE.GetDetectedSet}() with a given index. -- --- Retrieve the formed @{Zone@ZONE_UNIT}s as a result of the grouping the detected units within the DetectionZoneRange, use the method @{Detection#DETECTION_BASE.GetDetectionZones}(). --- To understand the amount of zones created, use the method @{Detection#DETECTION_BASE.GetDetectionZoneCount}(). --- If you want to obtain a specific zone from the DetectedZones, use the method @{Detection#DETECTION_BASE.GetDetectionZone}() with a given index. +-- Retrieve the formed @{Zone@ZONE_UNIT}s as a result of the grouping the detected units within the DetectionZoneRange, use the method @{Functional.Detection#DETECTION_BASE.GetDetectionZones}(). +-- To understand the amount of zones created, use the method @{Functional.Detection#DETECTION_BASE.GetDetectionZoneCount}(). +-- If you want to obtain a specific zone from the DetectedZones, use the method @{Functional.Detection#DETECTION_BASE.GetDetectionZone}() with a given index. -- -- 1.4) Flare or Smoke detected units -- ---------------------------------- --- Use the methods @{Detection#DETECTION_AREAS.FlareDetectedUnits}() or @{Detection#DETECTION_AREAS.SmokeDetectedUnits}() to flare or smoke the detected units when a new detection has taken place. +-- Use the methods @{Functional.Detection#DETECTION_AREAS.FlareDetectedUnits}() or @{Functional.Detection#DETECTION_AREAS.SmokeDetectedUnits}() to flare or smoke the detected units when a new detection has taken place. -- -- 1.5) Flare or Smoke detected zones -- ---------------------------------- --- Use the methods @{Detection#DETECTION_AREAS.FlareDetectedZones}() or @{Detection#DETECTION_AREAS.SmokeDetectedZones}() to flare or smoke the detected zones when a new detection has taken place. +-- Use the methods @{Functional.Detection#DETECTION_AREAS.FlareDetectedZones}() or @{Functional.Detection#DETECTION_AREAS.SmokeDetectedZones}() to flare or smoke the detected zones when a new detection has taken place. -- -- === -- @@ -26220,12 +22817,12 @@ end --- DETECTION_BASE class -- @type DETECTION_BASE --- @field Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Forward Air Controller role. --- @field DCSTypes#Distance DetectionRange The range till which targets are accepted to be detected. +-- @field Core.Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Forward Air Controller role. +-- @field Dcs.DCSTypes#Distance DetectionRange The range till which targets are accepted to be detected. -- @field #DETECTION_BASE.DetectedObjects DetectedObjects The list of detected objects. -- @field #table DetectedObjectsIdentified Map of the DetectedObjects identified. -- @field #number DetectionRun --- @extends Base#BASE +-- @extends Core.Base#BASE DETECTION_BASE = { ClassName = "DETECTION_BASE", DetectionSetGroup = nil, @@ -26247,8 +22844,8 @@ DETECTION_BASE = { --- DETECTION constructor. -- @param #DETECTION_BASE self --- @param Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Forward Air Controller role. --- @param DCSTypes#Distance DetectionRange The range till which targets are accepted to be detected. +-- @param Core.Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Forward Air Controller role. +-- @param Dcs.DCSTypes#Distance DetectionRange The range till which targets are accepted to be detected. -- @return #DETECTION_BASE self function DETECTION_BASE:New( DetectionSetGroup, DetectionRange ) @@ -26388,7 +22985,7 @@ function DETECTION_BASE:GetDetectedObject( ObjectName ) return nil end ---- Get the detected @{Set#SET_BASE}s. +--- Get the detected @{Core.Set#SET_BASE}s. -- @param #DETECTION_BASE self -- @return #DETECTION_BASE.DetectedSets DetectedSets function DETECTION_BASE:GetDetectedSets() @@ -26409,7 +23006,7 @@ end --- Get a SET of detected objects using a given numeric index. -- @param #DETECTION_BASE self -- @param #number Index --- @return Set#SET_BASE +-- @return Core.Set#SET_BASE function DETECTION_BASE:GetDetectedSet( Index ) local DetectionSet = self.DetectedSets[Index] @@ -26422,7 +23019,7 @@ end --- Get the detection Groups. -- @param #DETECTION_BASE self --- @return Group#GROUP +-- @return Wrapper.Group#GROUP function DETECTION_BASE:GetDetectionSetGroup() local DetectionSetGroup = self.DetectionSetGroup @@ -26456,7 +23053,7 @@ function DETECTION_BASE:Schedule( DelayTime, RepeatInterval ) end ---- Form @{Set}s of detected @{Unit#UNIT}s in an array of @{Set#SET_BASE}s. +--- Form @{Set}s of detected @{Wrapper.Unit#UNIT}s in an array of @{Core.Set#SET_BASE}s. -- @param #DETECTION_BASE self function DETECTION_BASE:_DetectionScheduler( SchedulerName ) self:F2( { SchedulerName } ) @@ -26466,7 +23063,7 @@ function DETECTION_BASE:_DetectionScheduler( SchedulerName ) self:UnIdentifyAllDetectedObjects() -- Resets the DetectedObjectsIdentified table for DetectionGroupID, DetectionGroupData in pairs( self.DetectionSetGroup:GetSet() ) do - local DetectionGroup = DetectionGroupData -- Group#GROUP + local DetectionGroup = DetectionGroupData -- Wrapper.Group#GROUP if DetectionGroup:IsAlive() then @@ -26482,7 +23079,7 @@ function DETECTION_BASE:_DetectionScheduler( SchedulerName ) ) for DetectionDetectedTargetID, DetectionDetectedTarget in pairs( DetectionDetectedTargets ) do - local DetectionObject = DetectionDetectedTarget.object -- DCSObject#Object + local DetectionObject = DetectionDetectedTarget.object -- Dcs.DCSWrapper.Object#Object self:T2( DetectionObject ) if DetectionObject and DetectionObject:isExist() and DetectionObject.id_ < 50000000 then @@ -26536,9 +23133,9 @@ end --- DETECTION_AREAS class -- @type DETECTION_AREAS --- @field DCSTypes#Distance DetectionZoneRange The range till which targets are grouped upon the first detected target. +-- @field Dcs.DCSTypes#Distance DetectionZoneRange The range till which targets are grouped upon the first detected target. -- @field #DETECTION_AREAS.DetectedAreas DetectedAreas A list of areas containing the set of @{Unit}s, @{Zone}s, the center @{Unit} within the zone, and ID of each area that was detected within a DetectionZoneRange. --- @extends Detection#DETECTION_BASE +-- @extends Functional.Detection#DETECTION_BASE DETECTION_AREAS = { ClassName = "DETECTION_AREAS", DetectedAreas = { n = 0 }, @@ -26549,21 +23146,21 @@ DETECTION_AREAS = { -- @list <#DETECTION_AREAS.DetectedArea> --- @type DETECTION_AREAS.DetectedArea --- @field Set#SET_UNIT Set -- The Set of Units in the detected area. --- @field Zone#ZONE_UNIT Zone -- The Zone of the detected area. +-- @field Core.Set#SET_UNIT Set -- The Set of Units in the detected area. +-- @field Core.Zone#ZONE_UNIT Zone -- The Zone of the detected area. -- @field #boolean Changed Documents if the detected area has changes. -- @field #table Changes A list of the changes reported on the detected area. (It is up to the user of the detected area to consume those changes). -- @field #number AreaID -- The identifier of the detected area. -- @field #boolean FriendliesNearBy Indicates if there are friendlies within the detected area. --- @field Unit#UNIT NearestFAC The nearest FAC near the Area. +-- @field Wrapper.Unit#UNIT NearestFAC The nearest FAC near the Area. --- DETECTION_AREAS constructor. --- @param Detection#DETECTION_AREAS self --- @param Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Forward Air Controller role. --- @param DCSTypes#Distance DetectionRange The range till which targets are accepted to be detected. --- @param DCSTypes#Distance DetectionZoneRange The range till which targets are grouped upon the first detected target. --- @return Detection#DETECTION_AREAS self +-- @param Functional.Detection#DETECTION_AREAS self +-- @param Core.Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Forward Air Controller role. +-- @param Dcs.DCSTypes#Distance DetectionRange The range till which targets are accepted to be detected. +-- @param Dcs.DCSTypes#Distance DetectionZoneRange The range till which targets are grouped upon the first detected target. +-- @return Functional.Detection#DETECTION_AREAS self function DETECTION_AREAS:New( DetectionSetGroup, DetectionRange, DetectionZoneRange ) -- Inherits from DETECTION_BASE @@ -26582,8 +23179,8 @@ function DETECTION_AREAS:New( DetectionSetGroup, DetectionRange, DetectionZoneRa end --- Add a detected @{#DETECTION_AREAS.DetectedArea}. --- @param Set#SET_UNIT Set -- The Set of Units in the detected area. --- @param Zone#ZONE_UNIT Zone -- The Zone of the detected area. +-- @param Core.Set#SET_UNIT Set -- The Set of Units in the detected area. +-- @param Core.Zone#ZONE_UNIT Zone -- The Zone of the detected area. -- @return #DETECTION_AREAS.DetectedArea DetectedArea function DETECTION_AREAS:AddDetectedArea( Set, Zone ) local DetectedAreas = self:GetDetectedAreas() @@ -26630,10 +23227,10 @@ function DETECTION_AREAS:GetDetectedAreaCount() return DetectedAreaCount end ---- Get the @{Set#SET_UNIT} of a detecttion area using a given numeric index. +--- Get the @{Core.Set#SET_UNIT} of a detecttion area using a given numeric index. -- @param #DETECTION_AREAS self -- @param #number Index --- @return Set#SET_UNIT DetectedSet +-- @return Core.Set#SET_UNIT DetectedSet function DETECTION_AREAS:GetDetectedSet( Index ) local DetectedSetUnit = self.DetectedAreas[Index].Set @@ -26644,10 +23241,10 @@ function DETECTION_AREAS:GetDetectedSet( Index ) return nil end ---- Get the @{Zone#ZONE_UNIT} of a detection area using a given numeric index. +--- Get the @{Core.Zone#ZONE_UNIT} of a detection area using a given numeric index. -- @param #DETECTION_AREAS self -- @param #number Index --- @return Zone#ZONE_UNIT DetectedZone +-- @return Core.Zone#ZONE_UNIT DetectedZone function DETECTION_AREAS:GetDetectedZone( Index ) local DetectedZone = self.DetectedAreas[Index].Zone @@ -26660,11 +23257,11 @@ end --- Background worker function to determine if there are friendlies nearby ... -- @param #DETECTION_AREAS self --- @param Unit#UNIT ReportUnit +-- @param Wrapper.Unit#UNIT ReportUnit function DETECTION_AREAS:ReportFriendliesNearBy( ReportGroupData ) self:F2() - local DetectedArea = ReportGroupData.DetectedArea -- Detection#DETECTION_AREAS.DetectedArea + local DetectedArea = ReportGroupData.DetectedArea -- Functional.Detection#DETECTION_AREAS.DetectedArea local DetectedSet = ReportGroupData.DetectedArea.Set local DetectedZone = ReportGroupData.DetectedArea.Zone local DetectedZoneUnit = DetectedZone.ZoneUNIT @@ -26680,15 +23277,15 @@ function DETECTION_AREAS:ReportFriendliesNearBy( ReportGroupData ) } - --- @param DCSUnit#Unit FoundDCSUnit - -- @param Group#GROUP ReportGroup + --- @param Dcs.DCSWrapper.Unit#Unit FoundDCSUnit + -- @param Wrapper.Group#GROUP ReportGroup -- @param Set#SET_GROUP ReportSetGroup local FindNearByFriendlies = function( FoundDCSUnit, ReportGroupData ) - local DetectedArea = ReportGroupData.DetectedArea -- Detection#DETECTION_AREAS.DetectedArea + local DetectedArea = ReportGroupData.DetectedArea -- Functional.Detection#DETECTION_AREAS.DetectedArea local DetectedSet = ReportGroupData.DetectedArea.Set local DetectedZone = ReportGroupData.DetectedArea.Zone - local DetectedZoneUnit = DetectedZone.ZoneUNIT -- Unit#UNIT + local DetectedZoneUnit = DetectedZone.ZoneUNIT -- Wrapper.Unit#UNIT local ReportSetGroup = ReportGroupData.ReportSetGroup local EnemyCoalition = DetectedZoneUnit:GetCoalition() @@ -26731,7 +23328,7 @@ function DETECTION_AREAS:CalculateThreatLevelA2G( DetectedArea ) local MaxThreatLevelA2G = 0 for UnitName, UnitData in pairs( DetectedArea.Set:GetSet() ) do - local ThreatUnit = UnitData -- Unit#UNIT + local ThreatUnit = UnitData -- Wrapper.Unit#UNIT local ThreatLevelA2G = ThreatUnit:GetThreatLevel() if ThreatLevelA2G > MaxThreatLevelA2G then MaxThreatLevelA2G = ThreatLevelA2G @@ -26746,7 +23343,7 @@ end --- Find the nearest FAC of the DetectedArea. -- @param #DETECTION_AREAS self -- @param #DETECTION_AREAS.DetectedArea DetectedArea --- @return Unit#UNIT The nearest FAC unit +-- @return Wrapper.Unit#UNIT The nearest FAC unit function DETECTION_AREAS:NearestFAC( DetectedArea ) local NearestFAC = nil @@ -26754,7 +23351,7 @@ function DETECTION_AREAS:NearestFAC( DetectedArea ) for FACGroupName, FACGroupData in pairs( self.DetectionSetGroup:GetSet() ) do for FACUnit, FACUnitData in pairs( FACGroupData:GetUnits() ) do - local FACUnit = FACUnitData -- Unit#UNIT + local FACUnit = FACUnitData -- Wrapper.Unit#UNIT if FACUnit:IsActive() then local Vec3 = FACUnit:GetVec3() local PointVec3 = POINT_VEC3:NewFromVec3( Vec3 ) @@ -26972,7 +23569,7 @@ function DETECTION_AREAS:CreateDetectionSets() -- Then search for a new center area unit within the set. Note that the new area unit candidate must be within the area range. for DetectedUnitName, DetectedUnitData in pairs( DetectedSet:GetSet() ) do - local DetectedUnit = DetectedUnitData -- Unit#UNIT + local DetectedUnit = DetectedUnitData -- Wrapper.Unit#UNIT local DetectedObject = self:GetDetectedObject( DetectedUnit.UnitName ) -- The DetectedObject can be nil when the DetectedUnit is not alive anymore or it is not in the DetectedObjects map. @@ -27002,7 +23599,7 @@ function DETECTION_AREAS:CreateDetectionSets() -- If a unit was not found in the set, remove it from the set. This may be added later to other existing or new sets. for DetectedUnitName, DetectedUnitData in pairs( DetectedSet:GetSet() ) do - local DetectedUnit = DetectedUnitData -- Unit#UNIT + local DetectedUnit = DetectedUnitData -- Wrapper.Unit#UNIT local DetectedObject = nil if DetectedUnit:IsAlive() then --self:E(DetectedUnit:GetName()) @@ -27052,7 +23649,7 @@ function DETECTION_AREAS:CreateDetectionSets() if DetectedObject then -- We found an unidentified unit outside of any existing detection area. - local DetectedUnit = UNIT:FindByName( DetectedUnitName ) -- Unit#UNIT + local DetectedUnit = UNIT:FindByName( DetectedUnitName ) -- Wrapper.Unit#UNIT local AddedToDetectionArea = false @@ -27102,7 +23699,7 @@ function DETECTION_AREAS:CreateDetectionSets() DetectedZone.ZoneUNIT:SmokeRed() end DetectedSet:ForEachUnit( - --- @param Unit#UNIT DetectedUnit + --- @param Wrapper.Unit#UNIT DetectedUnit function( DetectedUnit ) if DetectedUnit:IsAlive() then self:T( "Detected Set #" .. DetectedArea.AreaID .. ":" .. DetectedUnit:GetName() ) @@ -27116,51 +23713,4938 @@ function DETECTION_AREAS:CreateDetectionSets() end ) if DETECTION_AREAS._FlareDetectedZones or self._FlareDetectedZones then - DetectedZone:FlareZone( POINT_VEC3.SmokeColor.White, 30, math.random( 0,90 ) ) + DetectedZone:FlareZone( SMOKECOLOR.White, 30, math.random( 0,90 ) ) end if DETECTION_AREAS._SmokeDetectedZones or self._SmokeDetectedZones then - DetectedZone:SmokeZone( POINT_VEC3.SmokeColor.White, 30 ) + DetectedZone:SmokeZone( SMOKECOLOR.White, 30 ) end end end ---- This module contains the DETECTION_MANAGER class and derived classes. +--- This module contains the AI_BALANCER class. -- -- === -- --- 1) @{DetectionManager#DETECTION_MANAGER} class, extends @{Base#BASE} +-- 1) @{AI.AI_Balancer#AI_BALANCER} class, extends @{Core.Fsm#FSM_SET} +-- =================================================================================== +-- The @{AI.AI_Balancer#AI_BALANCER} class monitors and manages as many AI GROUPS as there are +-- CLIENTS in a SET_CLIENT collection not occupied by players. +-- The AI_BALANCER class manages internally a collection of AI management objects, which govern the behaviour +-- of the underlying AI GROUPS. +-- +-- The parent class @{Core.Fsm#FSM_SET} manages the functionality to control the Finite State Machine (FSM) +-- and calls for each event the state transition methods providing the internal @{Core.Fsm#FSM_SET.Set} object containing the +-- SET_GROUP and additional event parameters provided during the event. +-- +-- 1.1) AI_BALANCER construction method +-- --------------------------------------- +-- Create a new AI_BALANCER object with the @{#AI_BALANCER.New} method: +-- +-- * @{#AI_BALANCER.New}: Creates a new AI_BALANCER object. +-- +-- 1.2) +-- ---- +-- * Add +-- * Remove +-- +-- 1.2) AI_BALANCER returns AI to Airbases +-- ------------------------------------------ +-- You can configure to have the AI to return to: +-- +-- * @{#AI_BALANCER.ReturnToHomeAirbase}: Returns the AI to the home @{Wrapper.Airbase#AIRBASE}. +-- * @{#AI_BALANCER.ReturnToNearestAirbases}: Returns the AI to the nearest friendly @{Wrapper.Airbase#AIRBASE}. +-- -- +-- === +-- +-- **API CHANGE HISTORY** +-- ====================== +-- +-- The underlying change log documents the API changes. Please read this carefully. The following notation is used: +-- +-- * **Added** parts are expressed in bold type face. +-- * _Removed_ parts are expressed in italic type face. +-- +-- Hereby the change log: +-- +-- 2016-08-17: SPAWN:**InitCleanUp**( SpawnCleanUpInterval ) replaces SPAWN:_CleanUp_( SpawnCleanUpInterval ) +-- +-- * Want to ensure that the methods starting with **Init** are the first called methods before any _Spawn_ method is called! +-- * This notation makes it now more clear which methods are initialization methods and which methods are Spawn enablement methods. +-- +-- === +-- +-- AUTHORS and CONTRIBUTIONS +-- ========================= +-- +-- ### Contributions: +-- +-- * **Dutch_Baron (James)**: Who you can search on the Eagle Dynamics Forums. +-- Working together with James has resulted in the creation of the AI_BALANCER class. +-- James has shared his ideas on balancing AI with air units, and together we made a first design which you can use now :-) +-- +-- * **SNAFU**: +-- Had a couple of mails with the guys to validate, if the same concept in the GCI/CAP script could be reworked within MOOSE. +-- None of the script code has been used however within the new AI_BALANCER moose class. +-- +-- ### Authors: +-- +-- * FlightControl: Framework Design & Programming +-- +-- @module AI_Balancer + + + +--- AI_BALANCER class +-- @type AI_BALANCER +-- @field Core.Set#SET_CLIENT SetClient +-- @extends Core.Fsm#FSM_SET +AI_BALANCER = { + ClassName = "AI_BALANCER", + PatrolZones = {}, + AIGroups = {}, +} + +--- Creates a new AI_BALANCER object +-- @param #AI_BALANCER self +-- @param Core.Set#SET_CLIENT SetClient A SET\_CLIENT object that will contain the CLIENT objects to be monitored if they are alive or not (joined by a player). +-- @param Functional.Spawn#SPAWN SpawnAI The default Spawn object to spawn new AI Groups when needed. +-- @return #AI_BALANCER +-- @usage +-- -- Define a new AI_BALANCER Object. +function AI_BALANCER:New( SetClient, SpawnAI ) + + -- Inherits from BASE + local self = BASE:Inherit( self, FSM_SET:New( SET_GROUP:New() ) ) -- Core.Fsm#FSM_SET + + self:SetStartState( "None" ) + self:AddTransition( "*", "Start", "Monitoring" ) + self:AddTransition( "*", "Monitor", "Monitoring" ) + self:AddTransition( "*", "Spawn", "Spawning" ) + self:AddTransition( "Spawning", "Spawned", "Spawned" ) + self:AddTransition( "*", "Destroy", "Destroying" ) + self:AddTransition( "*", "Return", "Returning" ) + self:AddTransition( "*", "End", "End" ) + self:AddTransition( "*", "Dead", "End" ) + + + + self.SetClient = SetClient + self.SpawnAI = SpawnAI + self.ToNearestAirbase = false + self.ToHomeAirbase = false + + self:__Start( 1 ) + + return self +end + +--- Returns the AI to the nearest friendly @{Wrapper.Airbase#AIRBASE}. +-- @param #AI_BALANCER self +-- @param Dcs.DCSTypes#Distance ReturnTresholdRange If there is an enemy @{Wrapper.Client#CLIENT} within the ReturnTresholdRange given in meters, the AI will not return to the nearest @{Wrapper.Airbase#AIRBASE}. +-- @param Core.Set#SET_AIRBASE ReturnAirbaseSet The SET of @{Core.Set#SET_AIRBASE}s to evaluate where to return to. +function AI_BALANCER:ReturnToNearestAirbases( ReturnTresholdRange, ReturnAirbaseSet ) + + self.ToNearestAirbase = true + self.ReturnTresholdRange = ReturnTresholdRange + self.ReturnAirbaseSet = ReturnAirbaseSet +end + +--- Returns the AI to the home @{Wrapper.Airbase#AIRBASE}. +-- @param #AI_BALANCER self +-- @param Dcs.DCSTypes#Distance ReturnTresholdRange If there is an enemy @{Wrapper.Client#CLIENT} within the ReturnTresholdRange given in meters, the AI will not return to the nearest @{Wrapper.Airbase#AIRBASE}. +function AI_BALANCER:ReturnToHomeAirbase( ReturnTresholdRange ) + + self.ToHomeAirbase = true + self.ReturnTresholdRange = ReturnTresholdRange +end + +--- @param #AI_BALANCER self +-- @param Core.Set#SET_GROUP SetGroup +-- @param #string ClientName +-- @param Wrapper.Group#GROUP AIGroup +function AI_BALANCER:onenterSpawning( SetGroup, Event, From, To, ClientName ) + + -- OK, Spawn a new group from the default SpawnAI object provided. + local AIGroup = self.SpawnAI:Spawn() + AIGroup:E( "Spawning new AIGroup" ) + --TODO: need to rework UnitName thing ... + + SetGroup:Add( ClientName, AIGroup ) + + -- Fire the Spawned event. The first parameter is the AIGroup just Spawned. + -- Mission designers can catch this event to bind further actions to the AIGroup. + self:Spawned( AIGroup ) +end + +--- @param #AI_BALANCER self +-- @param Core.Set#SET_GROUP SetGroup +-- @param Wrapper.Group#GROUP AIGroup +function AI_BALANCER:onenterDestroying( SetGroup, Event, From, To, AIGroup ) + + AIGroup:Destroy() +end + +--- @param #AI_BALANCER self +-- @param Core.Set#SET_GROUP SetGroup +-- @param Wrapper.Group#GROUP AIGroup +function AI_BALANCER:onenterReturning( SetGroup, Event, From, To, AIGroup ) + + local AIGroupTemplate = AIGroup:GetTemplate() + if self.ToHomeAirbase == true then + local WayPointCount = #AIGroupTemplate.route.points + local SwitchWayPointCommand = AIGroup:CommandSwitchWayPoint( 1, WayPointCount, 1 ) + AIGroup:SetCommand( SwitchWayPointCommand ) + AIGroup:MessageToRed( "Returning to home base ...", 30 ) + else + -- Okay, we need to send this Group back to the nearest base of the Coalition of the AI. + --TODO: i need to rework the POINT_VEC2 thing. + local PointVec2 = POINT_VEC2:New( AIGroup:GetVec2().x, AIGroup:GetVec2().y ) + local ClosestAirbase = self.ReturnAirbaseSet:FindNearestAirbaseFromPointVec2( PointVec2 ) + self:T( ClosestAirbase.AirbaseName ) + AIGroup:MessageToRed( "Returning to " .. ClosestAirbase:GetName().. " ...", 30 ) + local RTBRoute = AIGroup:RouteReturnToAirbase( ClosestAirbase ) + AIGroupTemplate.route = RTBRoute + AIGroup:Respawn( AIGroupTemplate ) + end + +end + + +--- @param #AI_BALANCER self +function AI_BALANCER:onenterMonitoring( SetGroup ) + + self.SetClient:ForEachClient( + --- @param Wrapper.Client#CLIENT Client + function( Client ) + self:E(Client.ClientName) + + local AIGroup = self.Set:Get( Client.UnitName ) -- Wrapper.Group#GROUP + if Client:IsAlive() then + + if AIGroup and AIGroup:IsAlive() == true then + + if self.ToNearestAirbase == false and self.ToHomeAirbase == false then + self:Destroy( AIGroup ) + else + -- We test if there is no other CLIENT within the self.ReturnTresholdRange of the first unit of the AI group. + -- If there is a CLIENT, the AI stays engaged and will not return. + -- If there is no CLIENT within the self.ReturnTresholdRange, then the unit will return to the Airbase return method selected. + + local PlayerInRange = { Value = false } + local RangeZone = ZONE_RADIUS:New( 'RangeZone', AIGroup:GetVec2(), self.ReturnTresholdRange ) + + self:E( RangeZone ) + + _DATABASE:ForEachPlayer( + --- @param Wrapper.Unit#UNIT RangeTestUnit + function( RangeTestUnit, RangeZone, AIGroup, PlayerInRange ) + self:E( { PlayerInRange, RangeTestUnit.UnitName, RangeZone.ZoneName } ) + if RangeTestUnit:IsInZone( RangeZone ) == true then + self:E( "in zone" ) + if RangeTestUnit:GetCoalition() ~= AIGroup:GetCoalition() then + self:E( "in range" ) + PlayerInRange.Value = true + end + end + end, + + --- @param Core.Zone#ZONE_RADIUS RangeZone + -- @param Wrapper.Group#GROUP AIGroup + function( RangeZone, AIGroup, PlayerInRange ) + if PlayerInRange.Value == false then + self:Return( AIGroup ) + end + end + , RangeZone, AIGroup, PlayerInRange + ) + + end + self.Set:Remove( Client.UnitName ) + end + else + if not AIGroup or not AIGroup:IsAlive() == true then + self:E("client not alive") + self:Spawn( Client.UnitName ) + self:E("text after spawn") + end + end + return true + end + ) + + self:__Monitor( 10 ) +end + + + +--- (AI) (FSM) Make AI patrol routes or zones. +-- +-- === +-- +-- 1) @{#AI_PATROLZONE} class, extends @{Core.Fsm#FSM_CONTROLLABLE} +-- ================================================================ +-- The @{#AI_PATROLZONE} class implements the core functions to patrol a @{Zone} by an AIR @{Controllable} @{Group}. +-- The patrol algorithm works that for each airplane patrolling, upon arrival at the patrol zone, +-- a random point is selected as the route point within the 3D space, within the given boundary limits. +-- The airplane will fly towards the random 3D point within the patrol zone, using a random speed within the given altitude and speed limits. +-- Upon arrival at the random 3D point, a new 3D random point will be selected within the patrol zone using the given limits. +-- This cycle will continue until a fuel treshold has been reached by the airplane. +-- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land. +-- +-- 1.1) AI_PATROLZONE constructor: +-- ---------------------------- +-- +-- * @{#AI_PATROLZONE.New}(): Creates a new AI_PATROLZONE object. +-- +-- 1.2) AI_PATROLZONE state machine: +-- ---------------------------------- +-- The AI_PATROLZONE is a state machine: it manages the different events and states of the AIControllable it is controlling. +-- +-- ### 1.2.1) AI_PATROLZONE Events: +-- +-- * @{#AI_PATROLZONE.Route}( AIControllable ): A new 3D route point is selected and the AIControllable will fly towards that point with the given speed. +-- * @{#AI_PATROLZONE.Patrol}( AIControllable ): The AIControllable reports it is patrolling. This event is called every 30 seconds. +-- * @{#AI_PATROLZONE.RTB}( AIControllable ): The AIControllable will report return to base. +-- * @{#AI_PATROLZONE.End}( AIControllable ): The end of the AI_PATROLZONE process. +-- * @{#AI_PATROLZONE.Dead}( AIControllable ): The AIControllable is dead. The AI_PATROLZONE process will be ended. +-- +-- ### 1.2.2) AI_PATROLZONE States: +-- +-- * **Route**: A new 3D route point is selected and the AIControllable will fly towards that point with the given speed. +-- * **Patrol**: The AIControllable is patrolling. This state is set every 30 seconds, so every 30 seconds, a state transition method can be used. +-- * **RTB**: The AIControllable reports it wants to return to the base. +-- * **Dead**: The AIControllable is dead ... +-- * **End**: The process has come to an end. +-- +-- ### 1.2.3) AI_PATROLZONE state transition methods: +-- +-- State transition functions can be set **by the mission designer** customizing or improving the behaviour of the state. +-- There are 2 moments when state transition methods will be called by the state machine: +-- +-- * **Before** the state transition. +-- The state transition method needs to start with the name **OnBefore + the name of the state**. +-- If the state transition method returns false, then the processing of the state transition will not be done! +-- If you want to change the behaviour of the AIControllable at this event, return false, +-- but then you'll need to specify your own logic using the AIControllable! +-- +-- * **After** the state transition. +-- The state transition method needs to start with the name **OnAfter + the name of the state**. +-- These state transition methods need to provide a return value, which is specified at the function description. +-- +-- An example how to manage a state transition for an AI_PATROLZONE object **Patrol** for the state **RTB**: +-- +-- local PatrolZoneGroup = GROUP:FindByName( "Patrol Zone" ) +-- local PatrolZone = ZONE_POLYGON:New( "PatrolZone", PatrolZoneGroup ) +-- +-- local PatrolSpawn = SPAWN:New( "Patrol Group" ) +-- local PatrolGroup = PatrolSpawn:Spawn() +-- +-- local Patrol = AI_PATROLZONE:New( PatrolZone, 3000, 6000, 300, 600 ) +-- Patrol:SetControllable( PatrolGroup ) +-- Patrol:ManageFuel( 0.2, 60 ) +-- +-- **OnBefore**RTB( AIGroup ) will be called by the AI_PATROLZONE object when the AIGroup reports RTB, but **before** the RTB default action is processed by the AI_PATROLZONE object. +-- +-- --- State transition function for the AI_PATROLZONE **Patrol** object +-- -- @param #AI_PATROLZONE self +-- -- @param Wrapper.Controllable#CONTROLLABLE AIGroup +-- -- @return #boolean If false is returned, then the OnAfter state transition method will not be called. +-- function Patrol:OnBeforeRTB( AIGroup ) +-- AIGroup:MessageToRed( "Returning to base", 20 ) +-- end +-- +-- **OnAfter**RTB( AIGroup ) will be called by the AI_PATROLZONE object when the AIGroup reports RTB, but **after** the RTB default action was processed by the AI_PATROLZONE object. +-- +-- --- State transition function for the AI_PATROLZONE **Patrol** object +-- -- @param #AI_PATROLZONE self +-- -- @param Wrapper.Controllable#CONTROLLABLE AIGroup +-- -- @return #Wrapper.Controllable#CONTROLLABLE The new AIGroup object that is set to be patrolling the zone. +-- function Patrol:OnAfterRTB( AIGroup ) +-- return PatrolSpawn:Spawn() +-- end +-- +-- 1.3) Manage the AI_PATROLZONE parameters: +-- ------------------------------------------ +-- The following methods are available to modify the parameters of a AI_PATROLZONE object: +-- +-- * @{#AI_PATROLZONE.SetControllable}(): Set the AIControllable. +-- * @{#AI_PATROLZONE.GetControllable}(): Get the AIControllable. +-- * @{#AI_PATROLZONE.SetSpeed}(): Set the patrol speed of the AI, for the next patrol. +-- * @{#AI_PATROLZONE.SetAltitude}(): Set altitude of the AI, for the next patrol. +-- +-- 1.3) Manage the out of fuel in the AI_PATROLZONE: +-- ---------------------------------------------- +-- When the AIControllable is out of fuel, it is required that a new AIControllable is started, before the old AIControllable can return to the home base. +-- Therefore, with a parameter and a calculation of the distance to the home base, the fuel treshold is calculated. +-- When the fuel treshold is reached, the AIControllable will continue for a given time its patrol task in orbit, while a new AIControllable is targetted to the AI_PATROLZONE. +-- Once the time is finished, the old AIControllable will return to the base. +-- Use the method @{#AI_PATROLZONE.ManageFuel}() to have this proces in place. +-- +-- ==== +-- +-- **API CHANGE HISTORY** +-- ====================== +-- +-- The underlying change log documents the API changes. Please read this carefully. The following notation is used: +-- +-- * **Added** parts are expressed in bold type face. +-- * _Removed_ parts are expressed in italic type face. +-- +-- Hereby the change log: +-- +-- 2016-09-01: Initial class and API. +-- +-- === +-- +-- AUTHORS and CONTRIBUTIONS +-- ========================= +-- +-- ### Contributions: +-- +-- * **DutchBaron**: Testing. +-- * **Pikey**: Testing and API concept review. +-- +-- ### Authors: +-- +-- * **FlightControl**: Design & Programming. +-- +-- +-- @module Patrol + +-- State Transition Functions + +--- OnBefore State Transition Function +-- @function [parent=#AI_PATROLZONE] OnBeforeRoute +-- @param #AI_PATROLZONE self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable +-- @return #boolean + +--- OnAfter State Transition Function +-- @function [parent=#AI_PATROLZONE] OnAfterRoute +-- @param #AI_PATROLZONE self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable + + + +--- AI_PATROLZONE class +-- @type AI_PATROLZONE +-- @field Wrapper.Controllable#CONTROLLABLE AIControllable The @{Controllable} patrolling. +-- @field Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed. +-- @field Dcs.DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. +-- @field Dcs.DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. +-- @field Dcs.DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Controllable} in km/h. +-- @field Dcs.DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Controllable} in km/h. +-- @extends Core.Fsm#FSM_CONTROLLABLE +AI_PATROLZONE = { + ClassName = "AI_PATROLZONE", +} + + + +--- Creates a new AI_PATROLZONE object +-- @param #AI_PATROLZONE self +-- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed. +-- @param Dcs.DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. +-- @param Dcs.DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. +-- @param Dcs.DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Controllable} in km/h. +-- @param Dcs.DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Controllable} in km/h. +-- @return #AI_PATROLZONE self +-- @usage +-- -- Define a new AI_PATROLZONE Object. This PatrolArea will patrol an AIControllable within PatrolZone between 3000 and 6000 meters, with a variying speed between 600 and 900 km/h. +-- PatrolZone = ZONE:New( 'PatrolZone' ) +-- PatrolSpawn = SPAWN:New( 'Patrol Group' ) +-- PatrolArea = AI_PATROLZONE:New( PatrolZone, 3000, 6000, 600, 900 ) +function AI_PATROLZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed ) + + -- Inherits from BASE + local self = BASE:Inherit( self, FSM_CONTROLLABLE:New() ) -- Core.Fsm#FSM_CONTROLLABLE + + self:SetStartState( "None" ) + self:AddTransition( "*", "Start", "Route" ) + self:AddTransition( "*", "Route", "Route" ) + self:AddTransition( { "Patrol", "Route" }, "Patrol", "Patrol" ) + self:AddTransition( "Patrol", "RTB", "RTB" ) + self:AddTransition( "*", "End", "End" ) + self:AddTransition( "*", "Dead", "End" ) + + self.PatrolZone = PatrolZone + self.PatrolFloorAltitude = PatrolFloorAltitude + self.PatrolCeilingAltitude = PatrolCeilingAltitude + self.PatrolMinSpeed = PatrolMinSpeed + self.PatrolMaxSpeed = PatrolMaxSpeed + + return self +end + + + + +--- Sets (modifies) the minimum and maximum speed of the patrol. +-- @param #AI_PATROLZONE self +-- @param Dcs.DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Controllable} in km/h. +-- @param Dcs.DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Controllable} in km/h. +-- @return #AI_PATROLZONE self +function AI_PATROLZONE:SetSpeed( PatrolMinSpeed, PatrolMaxSpeed ) + self:F2( { PatrolMinSpeed, PatrolMaxSpeed } ) + + self.PatrolMinSpeed = PatrolMinSpeed + self.PatrolMaxSpeed = PatrolMaxSpeed +end + + + +--- Sets the floor and ceiling altitude of the patrol. +-- @param #AI_PATROLZONE self +-- @param Dcs.DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. +-- @param Dcs.DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. +-- @return #AI_PATROLZONE self +function AI_PATROLZONE:SetAltitude( PatrolFloorAltitude, PatrolCeilingAltitude ) + self:F2( { PatrolFloorAltitude, PatrolCeilingAltitude } ) + + self.PatrolFloorAltitude = PatrolFloorAltitude + self.PatrolCeilingAltitude = PatrolCeilingAltitude +end + + + +--- @param Wrapper.Controllable#CONTROLLABLE AIControllable +function _NewPatrolRoute( AIControllable ) + + AIControllable:T( "NewPatrolRoute" ) + local PatrolZone = AIControllable:GetState( AIControllable, "PatrolZone" ) -- PatrolCore.Zone#AI_PATROLZONE + PatrolZone:__Route( 1 ) +end + + + + +--- When the AIControllable is out of fuel, it is required that a new AIControllable is started, before the old AIControllable can return to the home base. +-- Therefore, with a parameter and a calculation of the distance to the home base, the fuel treshold is calculated. +-- When the fuel treshold is reached, the AIControllable will continue for a given time its patrol task in orbit, while a new AIControllable is targetted to the AI_PATROLZONE. +-- Once the time is finished, the old AIControllable will return to the base. +-- @param #AI_PATROLZONE self +-- @param #number PatrolFuelTresholdPercentage The treshold in percentage (between 0 and 1) when the AIControllable is considered to get out of fuel. +-- @param #number PatrolOutOfFuelOrbitTime The amount of seconds the out of fuel AIControllable will orbit before returning to the base. +-- @return #AI_PATROLZONE self +function AI_PATROLZONE:ManageFuel( PatrolFuelTresholdPercentage, PatrolOutOfFuelOrbitTime ) + + self.PatrolManageFuel = true + self.PatrolFuelTresholdPercentage = PatrolFuelTresholdPercentage + self.PatrolOutOfFuelOrbitTime = PatrolOutOfFuelOrbitTime + + return self +end + +--- Defines a new patrol route using the @{Process_PatrolZone} parameters and settings. +-- @param #AI_PATROLZONE self +-- @return #AI_PATROLZONE self +function AI_PATROLZONE:onenterRoute() + + self:F2() + + local PatrolRoute = {} + + if self.Controllable:IsAlive() then + --- Determine if the AIControllable is within the PatrolZone. + -- If not, make a waypoint within the to that the AIControllable will fly at maximum speed to that point. + +-- --- Calculate the current route point. +-- local CurrentVec2 = self.Controllable:GetVec2() +-- local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude() +-- local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) +-- local CurrentRoutePoint = CurrentPointVec3:RoutePointAir( +-- POINT_VEC3.RoutePointAltType.BARO, +-- POINT_VEC3.RoutePointType.TurningPoint, +-- POINT_VEC3.RoutePointAction.TurningPoint, +-- ToPatrolZoneSpeed, +-- true +-- ) +-- +-- PatrolRoute[#PatrolRoute+1] = CurrentRoutePoint + + self:T2( PatrolRoute ) + + if self.Controllable:IsNotInZone( self.PatrolZone ) then + --- Find a random 2D point in PatrolZone. + local ToPatrolZoneVec2 = self.PatrolZone:GetRandomVec2() + self:T2( ToPatrolZoneVec2 ) + + --- Define Speed and Altitude. + local ToPatrolZoneAltitude = math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ) + local ToPatrolZoneSpeed = self.PatrolMaxSpeed + self:T2( ToPatrolZoneSpeed ) + + --- Obtain a 3D @{Point} from the 2D point + altitude. + local ToPatrolZonePointVec3 = POINT_VEC3:New( ToPatrolZoneVec2.x, ToPatrolZoneAltitude, ToPatrolZoneVec2.y ) + + --- Create a route point of type air. + local ToPatrolZoneRoutePoint = ToPatrolZonePointVec3:RoutePointAir( + POINT_VEC3.RoutePointAltType.BARO, + POINT_VEC3.RoutePointType.TurningPoint, + POINT_VEC3.RoutePointAction.TurningPoint, + ToPatrolZoneSpeed, + true + ) + + PatrolRoute[#PatrolRoute+1] = ToPatrolZoneRoutePoint + + end + + --- Define a random point in the @{Zone}. The AI will fly to that point within the zone. + + --- Find a random 2D point in PatrolZone. + local ToTargetVec2 = self.PatrolZone:GetRandomVec2() + self:T2( ToTargetVec2 ) + + --- Define Speed and Altitude. + local ToTargetAltitude = math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ) + local ToTargetSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed ) + self:T2( { self.PatrolMinSpeed, self.PatrolMaxSpeed, ToTargetSpeed } ) + + --- Obtain a 3D @{Point} from the 2D point + altitude. + local ToTargetPointVec3 = POINT_VEC3:New( ToTargetVec2.x, ToTargetAltitude, ToTargetVec2.y ) + + --- Create a route point of type air. + local ToTargetRoutePoint = ToTargetPointVec3:RoutePointAir( + POINT_VEC3.RoutePointAltType.BARO, + POINT_VEC3.RoutePointType.TurningPoint, + POINT_VEC3.RoutePointAction.TurningPoint, + ToTargetSpeed, + true + ) + + --ToTargetPointVec3:SmokeRed() + + PatrolRoute[#PatrolRoute+1] = ToTargetRoutePoint + + --- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable... + self.Controllable:WayPointInitialize( PatrolRoute ) + + --- Do a trick, link the NewPatrolRoute function of the PATROLGROUP object to the AIControllable in a temporary variable ... + self.Controllable:SetState( self.Controllable, "PatrolZone", self ) + self.Controllable:WayPointFunction( #PatrolRoute, 1, "_NewPatrolRoute" ) + + --- NOW ACT_ROUTE THE GROUP! + self.Controllable:WayPointExecute( 1 ) + + self:__Patrol( 30 ) + end + +end + + +--- @param #AI_PATROLZONE self +function AI_PATROLZONE:onenterPatrol() + self:F2() + + if self.Controllable and self.Controllable:IsAlive() then + + local Fuel = self.Controllable:GetUnit(1):GetFuel() + if Fuel < self.PatrolFuelTresholdPercentage then + local OldAIControllable = self.Controllable + local AIControllableTemplate = self.Controllable:GetTemplate() + + local OrbitTask = OldAIControllable:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed ) + local TimedOrbitTask = OldAIControllable:TaskControlled( OrbitTask, OldAIControllable:TaskCondition(nil,nil,nil,nil,self.PatrolOutOfFuelOrbitTime,nil ) ) + OldAIControllable:SetTask( TimedOrbitTask, 10 ) + + self:RTB() + else + self:__Patrol( 30 ) -- Execute the Patrol event after 30 seconds. + end + end + +end +--- Management of logical cargo objects, that can be transported from and to transportation carriers. +-- +-- === +-- +-- Cargo can be of various forms, always are composed out of ONE object ( one unit or one static or one slingload crate ): +-- +-- * AI_CARGO_UNIT, represented by a @{Unit} in a @{Group}: Cargo can be represented by a Unit in a Group. Destruction of the Unit will mean that the cargo is lost. +-- * CARGO_STATIC, represented by a @{Static}: Cargo can be represented by a Static. Destruction of the Static will mean that the cargo is lost. +-- * AI_CARGO_PACKAGE, contained in a @{Unit} of a @{Group}: Cargo can be contained within a Unit of a Group. The cargo can be **delivered** by the @{Unit}. If the Unit is destroyed, the cargo will be destroyed also. +-- * AI_CARGO_PACKAGE, Contained in a @{Static}: Cargo can be contained within a Static. The cargo can be **collected** from the @Static. If the @{Static} is destroyed, the cargo will be destroyed. +-- * CARGO_SLINGLOAD, represented by a @{Cargo} that is transportable: Cargo can be represented by a Cargo object that is transportable. Destruction of the Cargo will mean that the cargo is lost. +-- +-- * AI_CARGO_GROUPED, represented by a Group of CARGO_UNITs. +-- +-- 1) @{AI.AI_Cargo#AI_CARGO} class, extends @{Core.Fsm#FSM_PROCESS} +-- ========================================================================== +-- The @{#AI_CARGO} class defines the core functions that defines a cargo object within MOOSE. +-- A cargo is a logical object defined that is available for transport, and has a life status within a simulation. +-- +-- The AI_CARGO is a state machine: it manages the different events and states of the cargo. +-- All derived classes from AI_CARGO follow the same state machine, expose the same cargo event functions, and provide the same cargo states. +-- +-- ## 1.2.1) AI_CARGO Events: +-- +-- * @{#AI_CARGO.Board}( ToCarrier ): Boards the cargo to a carrier. +-- * @{#AI_CARGO.Load}( ToCarrier ): Loads the cargo into a carrier, regardless of its position. +-- * @{#AI_CARGO.UnBoard}( ToPointVec2 ): UnBoard the cargo from a carrier. This will trigger a movement of the cargo to the option ToPointVec2. +-- * @{#AI_CARGO.UnLoad}( ToPointVec2 ): UnLoads the cargo from a carrier. +-- * @{#AI_CARGO.Dead}( Controllable ): The cargo is dead. The cargo process will be ended. +-- +-- ## 1.2.2) AI_CARGO States: +-- +-- * **UnLoaded**: The cargo is unloaded from a carrier. +-- * **Boarding**: The cargo is currently boarding (= running) into a carrier. +-- * **Loaded**: The cargo is loaded into a carrier. +-- * **UnBoarding**: The cargo is currently unboarding (=running) from a carrier. +-- * **Dead**: The cargo is dead ... +-- * **End**: The process has come to an end. +-- +-- ## 1.2.3) AI_CARGO state transition methods: +-- +-- State transition functions can be set **by the mission designer** customizing or improving the behaviour of the state. +-- There are 2 moments when state transition methods will be called by the state machine: +-- +-- * **Before** the state transition. +-- The state transition method needs to start with the name **OnBefore + the name of the state**. +-- If the state transition method returns false, then the processing of the state transition will not be done! +-- If you want to change the behaviour of the AIControllable at this event, return false, +-- but then you'll need to specify your own logic using the AIControllable! +-- +-- * **After** the state transition. +-- The state transition method needs to start with the name **OnAfter + the name of the state**. +-- These state transition methods need to provide a return value, which is specified at the function description. +-- +-- 2) #AI_CARGO_UNIT class +-- ==================== +-- The AI_CARGO_UNIT class defines a cargo that is represented by a UNIT object within the simulator, and can be transported by a carrier. +-- Use the event functions as described above to Load, UnLoad, Board, UnBoard the AI_CARGO_UNIT objects to and from carriers. +-- +-- 5) #AI_CARGO_GROUPED class +-- ======================= +-- The AI_CARGO_GROUPED class defines a cargo that is represented by a group of UNIT objects within the simulator, and can be transported by a carrier. +-- Use the event functions as described above to Load, UnLoad, Board, UnBoard the AI_CARGO_UNIT objects to and from carriers. +-- +-- This module is still under construction, but is described above works already, and will keep working ... +-- +-- @module Cargo + +-- Events + +-- Board + +--- Boards the cargo to a Carrier. The event will create a movement (= running or driving) of the cargo to the Carrier. +-- The cargo must be in the **UnLoaded** state. +-- @function [parent=#AI_CARGO] Board +-- @param #AI_CARGO self +-- @param Wrapper.Controllable#CONTROLLABLE ToCarrier The Carrier that will hold the cargo. + +--- Boards the cargo to a Carrier. The event will create a movement (= running or driving) of the cargo to the Carrier. +-- The cargo must be in the **UnLoaded** state. +-- @function [parent=#AI_CARGO] __Board +-- @param #AI_CARGO self +-- @param #number DelaySeconds The amount of seconds to delay the action. +-- @param Wrapper.Controllable#CONTROLLABLE ToCarrier The Carrier that will hold the cargo. + + +-- UnBoard + +--- UnBoards the cargo to a Carrier. The event will create a movement (= running or driving) of the cargo from the Carrier. +-- The cargo must be in the **Loaded** state. +-- @function [parent=#AI_CARGO] UnBoard +-- @param #AI_CARGO self +-- @param Core.Point#POINT_VEC2 ToPointVec2 (optional) @{Core.Point#POINT_VEC2) to where the cargo should run after onboarding. If not provided, the cargo will run to 60 meters behind the Carrier location. + +--- UnBoards the cargo to a Carrier. The event will create a movement (= running or driving) of the cargo from the Carrier. +-- The cargo must be in the **Loaded** state. +-- @function [parent=#AI_CARGO] __UnBoard +-- @param #AI_CARGO self +-- @param #number DelaySeconds The amount of seconds to delay the action. +-- @param Core.Point#POINT_VEC2 ToPointVec2 (optional) @{Core.Point#POINT_VEC2) to where the cargo should run after onboarding. If not provided, the cargo will run to 60 meters behind the Carrier location. + + +-- Load + +--- Loads the cargo to a Carrier. The event will load the cargo into the Carrier regardless of its position. There will be no movement simulated of the cargo loading. +-- The cargo must be in the **UnLoaded** state. +-- @function [parent=#AI_CARGO] Load +-- @param #AI_CARGO self +-- @param Wrapper.Controllable#CONTROLLABLE ToCarrier The Carrier that will hold the cargo. + +--- Loads the cargo to a Carrier. The event will load the cargo into the Carrier regardless of its position. There will be no movement simulated of the cargo loading. +-- The cargo must be in the **UnLoaded** state. +-- @function [parent=#AI_CARGO] __Load +-- @param #AI_CARGO self +-- @param #number DelaySeconds The amount of seconds to delay the action. +-- @param Wrapper.Controllable#CONTROLLABLE ToCarrier The Carrier that will hold the cargo. + + +-- UnLoad + +--- UnLoads the cargo to a Carrier. The event will unload the cargo from the Carrier. There will be no movement simulated of the cargo loading. +-- The cargo must be in the **Loaded** state. +-- @function [parent=#AI_CARGO] UnLoad +-- @param #AI_CARGO self +-- @param Core.Point#POINT_VEC2 ToPointVec2 (optional) @{Core.Point#POINT_VEC2) to where the cargo will be placed after unloading. If not provided, the cargo will be placed 60 meters behind the Carrier location. + +--- UnLoads the cargo to a Carrier. The event will unload the cargo from the Carrier. There will be no movement simulated of the cargo loading. +-- The cargo must be in the **Loaded** state. +-- @function [parent=#AI_CARGO] __UnLoad +-- @param #AI_CARGO self +-- @param #number DelaySeconds The amount of seconds to delay the action. +-- @param Core.Point#POINT_VEC2 ToPointVec2 (optional) @{Core.Point#POINT_VEC2) to where the cargo will be placed after unloading. If not provided, the cargo will be placed 60 meters behind the Carrier location. + +-- State Transition Functions + +-- UnLoaded + +--- @function [parent=#AI_CARGO] OnBeforeUnLoaded +-- @param #AI_CARGO self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable +-- @return #boolean + +--- @function [parent=#AI_CARGO] OnAfterUnLoaded +-- @param #AI_CARGO self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable + +-- Loaded + +--- @function [parent=#AI_CARGO] OnBeforeLoaded +-- @param #AI_CARGO self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable +-- @return #boolean + +--- @function [parent=#AI_CARGO] OnAfterLoaded +-- @param #AI_CARGO self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable + +-- Boarding + +--- @function [parent=#AI_CARGO] OnBeforeBoarding +-- @param #AI_CARGO self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable +-- @return #boolean + +--- @function [parent=#AI_CARGO] OnAfterBoarding +-- @param #AI_CARGO self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable + +-- UnBoarding + +--- @function [parent=#AI_CARGO] OnBeforeUnBoarding +-- @param #AI_CARGO self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable +-- @return #boolean + +--- @function [parent=#AI_CARGO] OnAfterUnBoarding +-- @param #AI_CARGO self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable + + +-- TODO: Find all Carrier objects and make the type of the Carriers Wrapper.Unit#UNIT in the documentation. + +CARGOS = {} + +do -- AI_CARGO + + --- @type AI_CARGO + -- @extends Core.Fsm#FSM_PROCESS + -- @field #string Type A string defining the type of the cargo. eg. Engineers, Equipment, Screwdrivers. + -- @field #string Name A string defining the name of the cargo. The name is the unique identifier of the cargo. + -- @field #number Weight A number defining the weight of the cargo. The weight is expressed in kg. + -- @field #number ReportRadius (optional) A number defining the radius in meters when the cargo is signalling or reporting to a Carrier. + -- @field #number NearRadius (optional) A number defining the radius in meters when the cargo is near to a Carrier, so that it can be loaded. + -- @field Wrapper.Controllable#CONTROLLABLE CargoObject The alive DCS object representing the cargo. This value can be nil, meaning, that the cargo is not represented anywhere... + -- @field Wrapper.Controllable#CONTROLLABLE CargoCarrier The alive DCS object carrying the cargo. This value can be nil, meaning, that the cargo is not contained anywhere... + -- @field #boolean Slingloadable This flag defines if the cargo can be slingloaded. + -- @field #boolean Moveable This flag defines if the cargo is moveable. + -- @field #boolean Representable This flag defines if the cargo can be represented by a DCS Unit. + -- @field #boolean Containable This flag defines if the cargo can be contained within a DCS Unit. + AI_CARGO = { + ClassName = "AI_CARGO", + Type = nil, + Name = nil, + Weight = nil, + CargoObject = nil, + CargoCarrier = nil, + Representable = false, + Slingloadable = false, + Moveable = false, + Containable = false, + } + +--- @type AI_CARGO.CargoObjects +-- @map < #string, Wrapper.Positionable#POSITIONABLE > The alive POSITIONABLE objects representing the the cargo. + + +--- AI_CARGO Constructor. This class is an abstract class and should not be instantiated. +-- @param #AI_CARGO self +-- @param #string Type +-- @param #string Name +-- @param #number Weight +-- @param #number ReportRadius (optional) +-- @param #number NearRadius (optional) +-- @return #AI_CARGO +function AI_CARGO:New( Type, Name, Weight, ReportRadius, NearRadius ) + + local self = BASE:Inherit( self, FSM:New() ) -- Core.Fsm#FSM_CONTROLLABLE + self:F( { Type, Name, Weight, ReportRadius, NearRadius } ) + + self:SetStartState( "UnLoaded" ) + self:AddTransition( "UnLoaded", "Board", "Boarding" ) + self:AddTransition( "Boarding", "Boarding", "Boarding" ) + self:AddTransition( "Boarding", "Load", "Loaded" ) + self:AddTransition( "UnLoaded", "Load", "Loaded" ) + self:AddTransition( "Loaded", "UnBoard", "UnBoarding" ) + self:AddTransition( "UnBoarding", "UnBoarding", "UnBoarding" ) + self:AddTransition( "UnBoarding", "UnLoad", "UnLoaded" ) + self:AddTransition( "Loaded", "UnLoad", "UnLoaded" ) + + + self.Type = Type + self.Name = Name + self.Weight = Weight + self.ReportRadius = ReportRadius + self.NearRadius = NearRadius + self.CargoObject = nil + self.CargoCarrier = nil + self.Representable = false + self.Slingloadable = false + self.Moveable = false + self.Containable = false + + + self.CargoScheduler = SCHEDULER:New() + + CARGOS[self.Name] = self + + return self +end + + +--- Template method to spawn a new representation of the AI_CARGO in the simulator. +-- @param #AI_CARGO self +-- @return #AI_CARGO +function AI_CARGO:Spawn( PointVec2 ) + self:F() + +end + + +--- Check if CargoCarrier is near the Cargo to be Loaded. +-- @param #AI_CARGO self +-- @param Core.Point#POINT_VEC2 PointVec2 +-- @return #boolean +function AI_CARGO:IsNear( PointVec2 ) + self:F( { PointVec2 } ) + + local Distance = PointVec2:DistanceFromPointVec2( self.CargoObject:GetPointVec2() ) + self:T( Distance ) + + if Distance <= self.NearRadius then + return true + else + return false + end +end + +end + +do -- AI_CARGO_REPRESENTABLE + + --- @type AI_CARGO_REPRESENTABLE + -- @extends #AI_CARGO + AI_CARGO_REPRESENTABLE = { + ClassName = "AI_CARGO_REPRESENTABLE" + } + +--- AI_CARGO_REPRESENTABLE Constructor. +-- @param #AI_CARGO_REPRESENTABLE self +-- @param Wrapper.Controllable#Controllable CargoObject +-- @param #string Type +-- @param #string Name +-- @param #number Weight +-- @param #number ReportRadius (optional) +-- @param #number NearRadius (optional) +-- @return #AI_CARGO_REPRESENTABLE +function AI_CARGO_REPRESENTABLE:New( CargoObject, Type, Name, Weight, ReportRadius, NearRadius ) + local self = BASE:Inherit( self, AI_CARGO:New( Type, Name, Weight, ReportRadius, NearRadius ) ) -- #AI_CARGO + self:F( { Type, Name, Weight, ReportRadius, NearRadius } ) + + return self +end + +--- Route a cargo unit to a PointVec2. +-- @param #AI_CARGO_REPRESENTABLE self +-- @param Core.Point#POINT_VEC2 ToPointVec2 +-- @param #number Speed +-- @return #AI_CARGO_REPRESENTABLE +function AI_CARGO_REPRESENTABLE:RouteTo( ToPointVec2, Speed ) + self:F2( ToPointVec2 ) + + local Points = {} + + local PointStartVec2 = self.CargoObject:GetPointVec2() + + Points[#Points+1] = PointStartVec2:RoutePointGround( Speed ) + Points[#Points+1] = ToPointVec2:RoutePointGround( Speed ) + + local TaskRoute = self.CargoObject:TaskRoute( Points ) + self.CargoObject:SetTask( TaskRoute, 2 ) + return self +end + +end -- AI_CARGO + +do -- AI_CARGO_UNIT + + --- @type AI_CARGO_UNIT + -- @extends #AI_CARGO_REPRESENTABLE + AI_CARGO_UNIT = { + ClassName = "AI_CARGO_UNIT" + } + +--- AI_CARGO_UNIT Constructor. +-- @param #AI_CARGO_UNIT self +-- @param Wrapper.Unit#UNIT CargoUnit +-- @param #string Type +-- @param #string Name +-- @param #number Weight +-- @param #number ReportRadius (optional) +-- @param #number NearRadius (optional) +-- @return #AI_CARGO_UNIT +function AI_CARGO_UNIT:New( CargoUnit, Type, Name, Weight, ReportRadius, NearRadius ) + local self = BASE:Inherit( self, AI_CARGO_REPRESENTABLE:New( CargoUnit, Type, Name, Weight, ReportRadius, NearRadius ) ) -- #AI_CARGO_UNIT + self:F( { Type, Name, Weight, ReportRadius, NearRadius } ) + + self:T( CargoUnit ) + self.CargoObject = CargoUnit + + self:T( self.ClassName ) + + return self +end + +--- Enter UnBoarding State. +-- @param #AI_CARGO_UNIT self +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Core.Point#POINT_VEC2 ToPointVec2 +function AI_CARGO_UNIT:onenterUnBoarding( Event, From, To, ToPointVec2 ) + self:F() + + local Angle = 180 + local Speed = 10 + local DeployDistance = 5 + local RouteDistance = 60 + + if From == "Loaded" then + + local CargoCarrierPointVec2 = self.CargoCarrier:GetPointVec2() + local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. + local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) + local CargoDeployPointVec2 = CargoCarrierPointVec2:Translate( DeployDistance, CargoDeployHeading ) + local CargoRoutePointVec2 = CargoCarrierPointVec2:Translate( RouteDistance, CargoDeployHeading ) + + -- if there is no ToPointVec2 given, then use the CargoRoutePointVec2 + ToPointVec2 = ToPointVec2 or CargoRoutePointVec2 + + local FromPointVec2 = CargoCarrierPointVec2 + + -- Respawn the group... + if self.CargoObject then + self.CargoObject:ReSpawn( CargoDeployPointVec2:GetVec3(), CargoDeployHeading ) + self.CargoCarrier = nil + + local Points = {} + Points[#Points+1] = FromPointVec2:RoutePointGround( Speed ) + Points[#Points+1] = ToPointVec2:RoutePointGround( Speed ) + + local TaskRoute = self.CargoObject:TaskRoute( Points ) + self.CargoObject:SetTask( TaskRoute, 1 ) + + self:__UnBoarding( 1, ToPointVec2 ) + end + end + +end + +--- Leave UnBoarding State. +-- @param #AI_CARGO_UNIT self +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Core.Point#POINT_VEC2 ToPointVec2 +function AI_CARGO_UNIT:onleaveUnBoarding( Event, From, To, ToPointVec2 ) + self:F( { ToPointVec2, Event, From, To } ) + + local Angle = 180 + local Speed = 10 + local Distance = 5 + + if From == "UnBoarding" then + if self:IsNear( ToPointVec2 ) then + return true + else + self:__UnBoarding( 1, ToPointVec2 ) + end + return false + end + +end + +--- UnBoard Event. +-- @param #AI_CARGO_UNIT self +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Core.Point#POINT_VEC2 ToPointVec2 +function AI_CARGO_UNIT:onafterUnBoarding( Event, From, To, ToPointVec2 ) + self:F( { ToPointVec2, Event, From, To } ) + + self.CargoInAir = self.CargoObject:InAir() + + self:T( self.CargoInAir ) + + -- Only unboard the cargo when the carrier is not in the air. + -- (eg. cargo can be on a oil derrick, moving the cargo on the oil derrick will drop the cargo on the sea). + if not self.CargoInAir then + + end + + self:__UnLoad( 1, ToPointVec2 ) + +end + + + +--- Enter UnLoaded State. +-- @param #AI_CARGO_UNIT self +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Core.Point#POINT_VEC2 +function AI_CARGO_UNIT:onenterUnLoaded( Event, From, To, ToPointVec2 ) + self:F( { ToPointVec2, Event, From, To } ) + + local Angle = 180 + local Speed = 10 + local Distance = 5 + + if From == "Loaded" then + local StartPointVec2 = self.CargoCarrier:GetPointVec2() + local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. + local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) + local CargoDeployPointVec2 = StartPointVec2:Translate( Distance, CargoDeployHeading ) + + ToPointVec2 = ToPointVec2 or POINT_VEC2:New( CargoDeployPointVec2:GetX(), CargoDeployPointVec2:GetY() ) + + -- Respawn the group... + if self.CargoObject then + self.CargoObject:ReSpawn( ToPointVec2:GetVec3(), 0 ) + self.CargoCarrier = nil + end + + end + + if self.OnUnLoadedCallBack then + self.OnUnLoadedCallBack( self, unpack( self.OnUnLoadedParameters ) ) + self.OnUnLoadedCallBack = nil + end + +end + + + +--- Enter Boarding State. +-- @param #AI_CARGO_UNIT self +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Wrapper.Unit#UNIT CargoCarrier +function AI_CARGO_UNIT:onenterBoarding( Event, From, To, CargoCarrier ) + self:F( { CargoCarrier.UnitName, Event, From, To } ) + + local Speed = 10 + local Angle = 180 + local Distance = 5 + + if From == "UnLoaded" then + local CargoCarrierPointVec2 = CargoCarrier:GetPointVec2() + local CargoCarrierHeading = CargoCarrier:GetHeading() -- Get Heading of object in degrees. + local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) + local CargoDeployPointVec2 = CargoCarrierPointVec2:Translate( Distance, CargoDeployHeading ) + + local Points = {} + + local PointStartVec2 = self.CargoObject:GetPointVec2() + + Points[#Points+1] = PointStartVec2:RoutePointGround( Speed ) + Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) + + local TaskRoute = self.CargoObject:TaskRoute( Points ) + self.CargoObject:SetTask( TaskRoute, 2 ) + end + +end + +--- Leave Boarding State. +-- @param #AI_CARGO_UNIT self +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Wrapper.Unit#UNIT CargoCarrier +function AI_CARGO_UNIT:onleaveBoarding( Event, From, To, CargoCarrier ) + self:F( { CargoCarrier.UnitName, Event, From, To } ) + + if self:IsNear( CargoCarrier:GetPointVec2() ) then + self:__Load( 1, CargoCarrier ) + return true + else + self:__Boarding( 1, CargoCarrier ) + end + return false +end + +--- Loaded State. +-- @param #AI_CARGO_UNIT self +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Wrapper.Unit#UNIT CargoCarrier +function AI_CARGO_UNIT:onenterLoaded( Event, From, To, CargoCarrier ) + self:F() + + self.CargoCarrier = CargoCarrier + + -- Only destroy the CargoObject is if there is a CargoObject (packages don't have CargoObjects). + if self.CargoObject then + self:T("Destroying") + self.CargoObject:Destroy() + end +end + + +--- Board Event. +-- @param #AI_CARGO_UNIT self +-- @param #string Event +-- @param #string From +-- @param #string To +function AI_CARGO_UNIT:onafterBoard( Event, From, To, CargoCarrier ) + self:F() + + self.CargoInAir = self.CargoObject:InAir() + + self:T( self.CargoInAir ) + + -- Only move the group to the carrier when the cargo is not in the air + -- (eg. cargo can be on a oil derrick, moving the cargo on the oil derrick will drop the cargo on the sea). + if not self.CargoInAir then + self:Load( CargoCarrier ) + end + +end + +end + +do -- AI_CARGO_PACKAGE + + --- @type AI_CARGO_PACKAGE + -- @extends #AI_CARGO_REPRESENTABLE + AI_CARGO_PACKAGE = { + ClassName = "AI_CARGO_PACKAGE" + } + +--- AI_CARGO_PACKAGE Constructor. +-- @param #AI_CARGO_PACKAGE self +-- @param Wrapper.Unit#UNIT CargoCarrier The UNIT carrying the package. +-- @param #string Type +-- @param #string Name +-- @param #number Weight +-- @param #number ReportRadius (optional) +-- @param #number NearRadius (optional) +-- @return #AI_CARGO_PACKAGE +function AI_CARGO_PACKAGE:New( CargoCarrier, Type, Name, Weight, ReportRadius, NearRadius ) + local self = BASE:Inherit( self, AI_CARGO_REPRESENTABLE:New( CargoCarrier, Type, Name, Weight, ReportRadius, NearRadius ) ) -- #AI_CARGO_PACKAGE + self:F( { Type, Name, Weight, ReportRadius, NearRadius } ) + + self:T( CargoCarrier ) + self.CargoCarrier = CargoCarrier + + return self +end + +--- Board Event. +-- @param #AI_CARGO_PACKAGE self +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Wrapper.Unit#UNIT CargoCarrier +-- @param #number Speed +-- @param #number BoardDistance +-- @param #number Angle +function AI_CARGO_PACKAGE:onafterOnBoard( Event, From, To, CargoCarrier, Speed, BoardDistance, LoadDistance, Angle ) + self:F() + + self.CargoInAir = self.CargoCarrier:InAir() + + self:T( self.CargoInAir ) + + -- Only move the CargoCarrier to the New CargoCarrier when the New CargoCarrier is not in the air. + if not self.CargoInAir then + + local Points = {} + + local StartPointVec2 = self.CargoCarrier:GetPointVec2() + local CargoCarrierHeading = CargoCarrier:GetHeading() -- Get Heading of object in degrees. + local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) + self:T( { CargoCarrierHeading, CargoDeployHeading } ) + local CargoDeployPointVec2 = CargoCarrier:GetPointVec2():Translate( BoardDistance, CargoDeployHeading ) + + Points[#Points+1] = StartPointVec2:RoutePointGround( Speed ) + Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) + + local TaskRoute = self.CargoCarrier:TaskRoute( Points ) + self.CargoCarrier:SetTask( TaskRoute, 1 ) + end + + self:Boarded( CargoCarrier, Speed, BoardDistance, LoadDistance, Angle ) + +end + +--- Check if CargoCarrier is near the Cargo to be Loaded. +-- @param #AI_CARGO_PACKAGE self +-- @param Wrapper.Unit#UNIT CargoCarrier +-- @return #boolean +function AI_CARGO_PACKAGE:IsNear( CargoCarrier ) + self:F() + + local CargoCarrierPoint = CargoCarrier:GetPointVec2() + + local Distance = CargoCarrierPoint:DistanceFromPointVec2( self.CargoCarrier:GetPointVec2() ) + self:T( Distance ) + + if Distance <= self.NearRadius then + return true + else + return false + end +end + +--- Boarded Event. +-- @param #AI_CARGO_PACKAGE self +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Wrapper.Unit#UNIT CargoCarrier +function AI_CARGO_PACKAGE:onafterOnBoarded( Event, From, To, CargoCarrier, Speed, BoardDistance, LoadDistance, Angle ) + self:F() + + if self:IsNear( CargoCarrier ) then + self:__Load( 1, CargoCarrier, Speed, LoadDistance, Angle ) + else + self:__Boarded( 1, CargoCarrier, Speed, BoardDistance, LoadDistance, Angle ) + end +end + +--- UnBoard Event. +-- @param #AI_CARGO_PACKAGE self +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param #number Speed +-- @param #number UnLoadDistance +-- @param #number UnBoardDistance +-- @param #number Radius +-- @param #number Angle +function AI_CARGO_PACKAGE:onafterUnBoard( Event, From, To, CargoCarrier, Speed, UnLoadDistance, UnBoardDistance, Radius, Angle ) + self:F() + + self.CargoInAir = self.CargoCarrier:InAir() + + self:T( self.CargoInAir ) + + -- Only unboard the cargo when the carrier is not in the air. + -- (eg. cargo can be on a oil derrick, moving the cargo on the oil derrick will drop the cargo on the sea). + if not self.CargoInAir then + + self:_Next( self.FsmP.UnLoad, UnLoadDistance, Angle ) + + local Points = {} + + local StartPointVec2 = CargoCarrier:GetPointVec2() + local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. + local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) + self:T( { CargoCarrierHeading, CargoDeployHeading } ) + local CargoDeployPointVec2 = StartPointVec2:Translate( UnBoardDistance, CargoDeployHeading ) + + Points[#Points+1] = StartPointVec2:RoutePointGround( Speed ) + Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) + + local TaskRoute = CargoCarrier:TaskRoute( Points ) + CargoCarrier:SetTask( TaskRoute, 1 ) + end + + self:__UnBoarded( 1 , CargoCarrier, Speed ) + +end + +--- UnBoarded Event. +-- @param #AI_CARGO_PACKAGE self +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Wrapper.Unit#UNIT CargoCarrier +function AI_CARGO_PACKAGE:onafterUnBoarded( Event, From, To, CargoCarrier, Speed ) + self:F() + + if self:IsNear( CargoCarrier ) then + self:__UnLoad( 1, CargoCarrier, Speed ) + else + self:__UnBoarded( 1, CargoCarrier, Speed ) + end +end + +--- Load Event. +-- @param #AI_CARGO_PACKAGE self +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Wrapper.Unit#UNIT CargoCarrier +-- @param #number Speed +-- @param #number LoadDistance +-- @param #number Angle +function AI_CARGO_PACKAGE:onafterLoad( Event, From, To, CargoCarrier, Speed, LoadDistance, Angle ) + self:F() + + self.CargoCarrier = CargoCarrier + + local StartPointVec2 = self.CargoCarrier:GetPointVec2() + local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. + local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) + local CargoDeployPointVec2 = StartPointVec2:Translate( LoadDistance, CargoDeployHeading ) + + local Points = {} + Points[#Points+1] = StartPointVec2:RoutePointGround( Speed ) + Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) + + local TaskRoute = self.CargoCarrier:TaskRoute( Points ) + self.CargoCarrier:SetTask( TaskRoute, 1 ) + +end + +--- UnLoad Event. +-- @param #AI_CARGO_PACKAGE self +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param #number Distance +-- @param #number Angle +function AI_CARGO_PACKAGE:onafterUnLoad( Event, From, To, CargoCarrier, Speed, Distance, Angle ) + self:F() + + local StartPointVec2 = self.CargoCarrier:GetPointVec2() + local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. + local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) + local CargoDeployPointVec2 = StartPointVec2:Translate( Distance, CargoDeployHeading ) + + self.CargoCarrier = CargoCarrier + + local Points = {} + Points[#Points+1] = StartPointVec2:RoutePointGround( Speed ) + Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) + + local TaskRoute = self.CargoCarrier:TaskRoute( Points ) + self.CargoCarrier:SetTask( TaskRoute, 1 ) + +end + + +end + +do -- AI_CARGO_GROUP + + --- @type AI_CARGO_GROUP + -- @extends AI.AI_Cargo#AI_CARGO + -- @field Set#SET_BASE CargoSet A set of cargo objects. + -- @field #string Name A string defining the name of the cargo group. The name is the unique identifier of the cargo. + AI_CARGO_GROUP = { + ClassName = "AI_CARGO_GROUP", + } + +--- AI_CARGO_GROUP constructor. +-- @param #AI_CARGO_GROUP self +-- @param Core.Set#Set_BASE CargoSet +-- @param #string Type +-- @param #string Name +-- @param #number Weight +-- @param #number ReportRadius (optional) +-- @param #number NearRadius (optional) +-- @return #AI_CARGO_GROUP +function AI_CARGO_GROUP:New( CargoSet, Type, Name, ReportRadius, NearRadius ) + local self = BASE:Inherit( self, AI_CARGO:New( Type, Name, 0, ReportRadius, NearRadius ) ) -- #AI_CARGO_GROUP + self:F( { Type, Name, ReportRadius, NearRadius } ) + + self.CargoSet = CargoSet + + + return self +end + +end -- AI_CARGO_GROUP + +do -- AI_CARGO_GROUPED + + --- @type AI_CARGO_GROUPED + -- @extends AI.AI_Cargo#AI_CARGO_GROUP + AI_CARGO_GROUPED = { + ClassName = "AI_CARGO_GROUPED", + } + +--- AI_CARGO_GROUPED constructor. +-- @param #AI_CARGO_GROUPED self +-- @param Core.Set#Set_BASE CargoSet +-- @param #string Type +-- @param #string Name +-- @param #number Weight +-- @param #number ReportRadius (optional) +-- @param #number NearRadius (optional) +-- @return #AI_CARGO_GROUPED +function AI_CARGO_GROUPED:New( CargoSet, Type, Name, ReportRadius, NearRadius ) + local self = BASE:Inherit( self, AI_CARGO_GROUP:New( CargoSet, Type, Name, ReportRadius, NearRadius ) ) -- #AI_CARGO_GROUPED + self:F( { Type, Name, ReportRadius, NearRadius } ) + + return self +end + +--- Enter Boarding State. +-- @param #AI_CARGO_GROUPED self +-- @param Wrapper.Unit#UNIT CargoCarrier +-- @param #string Event +-- @param #string From +-- @param #string To +function AI_CARGO_GROUPED:onenterBoarding( Event, From, To, CargoCarrier ) + self:F( { CargoCarrier.UnitName, Event, From, To } ) + + if From == "UnLoaded" then + + -- For each Cargo object within the AI_CARGO_GROUPED, route each object to the CargoLoadPointVec2 + self.CargoSet:ForEach( + function( Cargo ) + Cargo:__Board( 1, CargoCarrier ) + end + ) + + self:__Boarding( 1, CargoCarrier ) + end + +end + +--- Enter Loaded State. +-- @param #AI_CARGO_GROUPED self +-- @param Wrapper.Unit#UNIT CargoCarrier +-- @param #string Event +-- @param #string From +-- @param #string To +function AI_CARGO_GROUPED:onenterLoaded( Event, From, To, CargoCarrier ) + self:F( { CargoCarrier.UnitName, Event, From, To } ) + + if From == "UnLoaded" then + -- For each Cargo object within the AI_CARGO_GROUPED, load each cargo to the CargoCarrier. + for CargoID, Cargo in pairs( self.CargoSet:GetSet() ) do + Cargo:Load( CargoCarrier ) + end + end +end + +--- Leave Boarding State. +-- @param #AI_CARGO_GROUPED self +-- @param Wrapper.Unit#UNIT CargoCarrier +-- @param #string Event +-- @param #string From +-- @param #string To +function AI_CARGO_GROUPED:onleaveBoarding( Event, From, To, CargoCarrier ) + self:F( { CargoCarrier.UnitName, Event, From, To } ) + + local Boarded = true + + -- For each Cargo object within the AI_CARGO_GROUPED, route each object to the CargoLoadPointVec2 + for CargoID, Cargo in pairs( self.CargoSet:GetSet() ) do + self:T( Cargo.current ) + if not Cargo:is( "Loaded" ) then + Boarded = false + end + end + + if not Boarded then + self:__Boarding( 1, CargoCarrier ) + else + self:__Load( 1, CargoCarrier ) + end + return Boarded +end + +--- Enter UnBoarding State. +-- @param #AI_CARGO_GROUPED self +-- @param Core.Point#POINT_VEC2 ToPointVec2 +-- @param #string Event +-- @param #string From +-- @param #string To +function AI_CARGO_GROUPED:onenterUnBoarding( Event, From, To, ToPointVec2 ) + self:F() + + local Timer = 1 + + if From == "Loaded" then + + -- For each Cargo object within the AI_CARGO_GROUPED, route each object to the CargoLoadPointVec2 + self.CargoSet:ForEach( + function( Cargo ) + Cargo:__UnBoard( Timer, ToPointVec2 ) + Timer = Timer + 10 + end + ) + + self:__UnBoarding( 1, ToPointVec2 ) + end + +end + +--- Leave UnBoarding State. +-- @param #AI_CARGO_GROUPED self +-- @param Core.Point#POINT_VEC2 ToPointVec2 +-- @param #string Event +-- @param #string From +-- @param #string To +function AI_CARGO_GROUPED:onleaveUnBoarding( Event, From, To, ToPointVec2 ) + self:F( { ToPointVec2, Event, From, To } ) + + local Angle = 180 + local Speed = 10 + local Distance = 5 + + if From == "UnBoarding" then + local UnBoarded = true + + -- For each Cargo object within the AI_CARGO_GROUPED, route each object to the CargoLoadPointVec2 + for CargoID, Cargo in pairs( self.CargoSet:GetSet() ) do + self:T( Cargo.current ) + if not Cargo:is( "UnLoaded" ) then + UnBoarded = false + end + end + + if UnBoarded then + return true + else + self:__UnBoarding( 1, ToPointVec2 ) + end + + return false + end + +end + +--- UnBoard Event. +-- @param #AI_CARGO_GROUPED self +-- @param Core.Point#POINT_VEC2 ToPointVec2 +-- @param #string Event +-- @param #string From +-- @param #string To +function AI_CARGO_GROUPED:onafterUnBoarding( Event, From, To, ToPointVec2 ) + self:F( { ToPointVec2, Event, From, To } ) + + self:__UnLoad( 1, ToPointVec2 ) +end + + + +--- Enter UnLoaded State. +-- @param #AI_CARGO_GROUPED self +-- @param Core.Point#POINT_VEC2 +-- @param #string Event +-- @param #string From +-- @param #string To +function AI_CARGO_GROUPED:onenterUnLoaded( Event, From, To, ToPointVec2 ) + self:F( { ToPointVec2, Event, From, To } ) + + if From == "Loaded" then + + -- For each Cargo object within the AI_CARGO_GROUPED, route each object to the CargoLoadPointVec2 + self.CargoSet:ForEach( + function( Cargo ) + Cargo:UnLoad( ToPointVec2 ) + end + ) + + end + +end + +end -- AI_CARGO_GROUPED + + + +--- (SP) (MP) (FSM) Accept or reject process for player (task) assignments. +-- +-- === +-- +-- # @{#ACT_ASSIGN} FSM template class, extends @{Core.Fsm#FSM_PROCESS} +-- +-- ## ACT_ASSIGN state machine: +-- +-- This class is a state machine: it manages a process that is triggered by events causing state transitions to occur. +-- All derived classes from this class will start with the class name, followed by a \_. See the relevant derived class descriptions below. +-- Each derived class follows exactly the same process, using the same events and following the same state transitions, +-- but will have **different implementation behaviour** upon each event or state transition. +-- +-- ### ACT_ASSIGN **Events**: +-- +-- These are the events defined in this class: +-- +-- * **Start**: Start the tasking acceptance process. +-- * **Assign**: Assign the task. +-- * **Reject**: Reject the task.. +-- +-- ### ACT_ASSIGN **Event methods**: +-- +-- Event methods are available (dynamically allocated by the state machine), that accomodate for state transitions occurring in the process. +-- There are two types of event methods, which you can use to influence the normal mechanisms in the state machine: +-- +-- * **Immediate**: The event method has exactly the name of the event. +-- * **Delayed**: The event method starts with a __ + the name of the event. The first parameter of the event method is a number value, expressing the delay in seconds when the event will be executed. +-- +-- ### ACT_ASSIGN **States**: +-- +-- * **UnAssigned**: The player has not accepted the task. +-- * **Assigned (*)**: The player has accepted the task. +-- * **Rejected (*)**: The player has not accepted the task. +-- * **Waiting**: The process is awaiting player feedback. +-- * **Failed (*)**: The process has failed. +-- +-- (*) End states of the process. +-- +-- ### ACT_ASSIGN state transition methods: +-- +-- State transition functions can be set **by the mission designer** customizing or improving the behaviour of the state. +-- There are 2 moments when state transition methods will be called by the state machine: +-- +-- * **Before** the state transition. +-- The state transition method needs to start with the name **OnBefore + the name of the state**. +-- If the state transition method returns false, then the processing of the state transition will not be done! +-- If you want to change the behaviour of the AIControllable at this event, return false, +-- but then you'll need to specify your own logic using the AIControllable! +-- +-- * **After** the state transition. +-- The state transition method needs to start with the name **OnAfter + the name of the state**. +-- These state transition methods need to provide a return value, which is specified at the function description. +-- +-- === +-- +-- # 1) @{#ACT_ASSIGN_ACCEPT} class, extends @{Fsm.Assign#ACT_ASSIGN} +-- +-- The ACT_ASSIGN_ACCEPT class accepts by default a task for a player. No player intervention is allowed to reject the task. +-- +-- ## 1.1) ACT_ASSIGN_ACCEPT constructor: +-- +-- * @{#ACT_ASSIGN_ACCEPT.New}(): Creates a new ACT_ASSIGN_ACCEPT object. +-- +-- === +-- +-- # 2) @{#ACT_ASSIGN_MENU_ACCEPT} class, extends @{Fsm.Assign#ACT_ASSIGN} +-- +-- The ACT_ASSIGN_MENU_ACCEPT class accepts a task when the player accepts the task through an added menu option. +-- This assignment type is useful to conditionally allow the player to choose whether or not he would accept the task. +-- The assignment type also allows to reject the task. +-- +-- ## 2.1) ACT_ASSIGN_MENU_ACCEPT constructor: +-- ----------------------------------------- +-- +-- * @{#ACT_ASSIGN_MENU_ACCEPT.New}(): Creates a new ACT_ASSIGN_MENU_ACCEPT object. +-- +-- === +-- +-- @module Assign + + +do -- ACT_ASSIGN + + --- ACT_ASSIGN class + -- @type ACT_ASSIGN + -- @field Tasking.Task#TASK Task + -- @field Wrapper.Unit#UNIT ProcessUnit + -- @field Core.Zone#ZONE_BASE TargetZone + -- @extends Core.Fsm#FSM_PROCESS + ACT_ASSIGN = { + ClassName = "ACT_ASSIGN", + } + + + --- Creates a new task assignment state machine. The process will accept the task by default, no player intervention accepted. + -- @param #ACT_ASSIGN self + -- @return #ACT_ASSIGN The task acceptance process. + function ACT_ASSIGN:New() + + -- Inherits from BASE + local self = BASE:Inherit( self, FSM_PROCESS:New( "ACT_ASSIGN" ) ) -- Core.Fsm#FSM_PROCESS + + self:AddTransition( "UnAssigned", "Start", "Waiting" ) + self:AddTransition( "Waiting", "Assign", "Assigned" ) + self:AddTransition( "Waiting", "Reject", "Rejected" ) + self:AddTransition( "*", "Fail", "Failed" ) + + self:AddEndState( "Assigned" ) + self:AddEndState( "Rejected" ) + self:AddEndState( "Failed" ) + + self:SetStartState( "UnAssigned" ) + + return self + end + +end -- ACT_ASSIGN + + + +do -- ACT_ASSIGN_ACCEPT + + --- ACT_ASSIGN_ACCEPT class + -- @type ACT_ASSIGN_ACCEPT + -- @field Tasking.Task#TASK Task + -- @field Wrapper.Unit#UNIT ProcessUnit + -- @field Core.Zone#ZONE_BASE TargetZone + -- @extends #ACT_ASSIGN + ACT_ASSIGN_ACCEPT = { + ClassName = "ACT_ASSIGN_ACCEPT", + } + + + --- Creates a new task assignment state machine. The process will accept the task by default, no player intervention accepted. + -- @param #ACT_ASSIGN_ACCEPT self + -- @param #string TaskBriefing + function ACT_ASSIGN_ACCEPT:New( TaskBriefing ) + + local self = BASE:Inherit( self, ACT_ASSIGN:New() ) -- #ACT_ASSIGN_ACCEPT + + self.TaskBriefing = TaskBriefing + + return self + end + + function ACT_ASSIGN_ACCEPT:Init( FsmAssign ) + + self.TaskBriefing = FsmAssign.TaskBriefing + end + + --- StateMachine callback function + -- @param #ACT_ASSIGN_ACCEPT self + -- @param Wrapper.Unit#UNIT ProcessUnit + -- @param #string Event + -- @param #string From + -- @param #string To + function ACT_ASSIGN_ACCEPT:onafterStart( ProcessUnit, Event, From, To ) + self:E( { ProcessUnit, Event, From, To } ) + + self:__Assign( 1 ) + end + + --- StateMachine callback function + -- @param #ACT_ASSIGN_ACCEPT self + -- @param Wrapper.Unit#UNIT ProcessUnit + -- @param #string Event + -- @param #string From + -- @param #string To + function ACT_ASSIGN_ACCEPT:onenterAssigned( ProcessUnit, Event, From, To ) + env.info( "in here" ) + self:E( { ProcessUnit, Event, From, To } ) + + local ProcessGroup = ProcessUnit:GetGroup() + + self:Message( "You are assigned to the task " .. self.Task:GetName() ) + + self.Task:Assign() + end + +end -- ACT_ASSIGN_ACCEPT + + +do -- ACT_ASSIGN_MENU_ACCEPT + + --- ACT_ASSIGN_MENU_ACCEPT class + -- @type ACT_ASSIGN_MENU_ACCEPT + -- @field Tasking.Task#TASK Task + -- @field Wrapper.Unit#UNIT ProcessUnit + -- @field Core.Zone#ZONE_BASE TargetZone + -- @extends #ACT_ASSIGN + ACT_ASSIGN_MENU_ACCEPT = { + ClassName = "ACT_ASSIGN_MENU_ACCEPT", + } + + --- Init. + -- @param #ACT_ASSIGN_MENU_ACCEPT self + -- @param #string TaskName + -- @param #string TaskBriefing + -- @return #ACT_ASSIGN_MENU_ACCEPT self + function ACT_ASSIGN_MENU_ACCEPT:New( TaskName, TaskBriefing ) + + -- Inherits from BASE + local self = BASE:Inherit( self, ACT_ASSIGN:New() ) -- #ACT_ASSIGN_MENU_ACCEPT + + self.TaskName = TaskName + self.TaskBriefing = TaskBriefing + + return self + end + + function ACT_ASSIGN_MENU_ACCEPT:Init( FsmAssign ) + + self.TaskName = FsmAssign.TaskName + self.TaskBriefing = FsmAssign.TaskBriefing + end + + + --- Creates a new task assignment state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator. + -- @param #ACT_ASSIGN_MENU_ACCEPT self + -- @param #string TaskName + -- @param #string TaskBriefing + -- @return #ACT_ASSIGN_MENU_ACCEPT self + function ACT_ASSIGN_MENU_ACCEPT:Init( TaskName, TaskBriefing ) + + self.TaskBriefing = TaskBriefing + self.TaskName = TaskName + + return self + end + + --- StateMachine callback function + -- @param #ACT_ASSIGN_MENU_ACCEPT self + -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit + -- @param #string Event + -- @param #string From + -- @param #string To + function ACT_ASSIGN_MENU_ACCEPT:onafterStart( ProcessUnit, Event, From, To ) + self:E( { ProcessUnit, Event, From, To } ) + + self:Message( "Access the radio menu to accept the task. You have 30 seconds or the assignment will be cancelled." ) + + local ProcessGroup = ProcessUnit:GetGroup() + + self.Menu = MENU_GROUP:New( ProcessGroup, "Task " .. self.TaskName .. " acceptance" ) + self.MenuAcceptTask = MENU_GROUP_COMMAND:New( ProcessGroup, "Accept task " .. self.TaskName, self.Menu, self.MenuAssign, self ) + self.MenuRejectTask = MENU_GROUP_COMMAND:New( ProcessGroup, "Reject task " .. self.TaskName, self.Menu, self.MenuReject, self ) + end + + --- Menu function. + -- @param #ACT_ASSIGN_MENU_ACCEPT self + function ACT_ASSIGN_MENU_ACCEPT:MenuAssign() + self:E( ) + + self:__Assign( 1 ) + end + + --- Menu function. + -- @param #ACT_ASSIGN_MENU_ACCEPT self + function ACT_ASSIGN_MENU_ACCEPT:MenuReject() + self:E( ) + + self:__Reject( 1 ) + end + + --- StateMachine callback function + -- @param #ACT_ASSIGN_MENU_ACCEPT self + -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit + -- @param #string Event + -- @param #string From + -- @param #string To + function ACT_ASSIGN_MENU_ACCEPT:onafterAssign( ProcessUnit, Event, From, To ) + self:E( { ProcessUnit.UnitNameEvent, From, To } ) + + self.Menu:Remove() + end + + --- StateMachine callback function + -- @param #ACT_ASSIGN_MENU_ACCEPT self + -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit + -- @param #string Event + -- @param #string From + -- @param #string To + function ACT_ASSIGN_MENU_ACCEPT:onafterReject( ProcessUnit, Event, From, To ) + self:E( { ProcessUnit.UnitName, Event, From, To } ) + + self.Menu:Remove() + --TODO: need to resolve this problem ... it has to do with the events ... + --self.Task:UnAssignFromUnit( ProcessUnit )needs to become a callback funtion call upon the event + ProcessUnit:Destroy() + end + +end -- ACT_ASSIGN_MENU_ACCEPT +--- (SP) (MP) (FSM) Route AI or players through waypoints or to zones. +-- +-- === +-- +-- # @{#ACT_ROUTE} FSM class, extends @{Core.Fsm#FSM_PROCESS} +-- +-- ## ACT_ROUTE state machine: +-- +-- This class is a state machine: it manages a process that is triggered by events causing state transitions to occur. +-- All derived classes from this class will start with the class name, followed by a \_. See the relevant derived class descriptions below. +-- Each derived class follows exactly the same process, using the same events and following the same state transitions, +-- but will have **different implementation behaviour** upon each event or state transition. +-- +-- ### ACT_ROUTE **Events**: +-- +-- These are the events defined in this class: +-- +-- * **Start**: The process is started. The process will go into the Report state. +-- * **Report**: The process is reporting to the player the route to be followed. +-- * **Route**: The process is routing the controllable. +-- * **Pause**: The process is pausing the route of the controllable. +-- * **Arrive**: The controllable has arrived at a route point. +-- * **More**: There are more route points that need to be followed. The process will go back into the Report state. +-- * **NoMore**: There are no more route points that need to be followed. The process will go into the Success state. +-- +-- ### ACT_ROUTE **Event methods**: +-- +-- Event methods are available (dynamically allocated by the state machine), that accomodate for state transitions occurring in the process. +-- There are two types of event methods, which you can use to influence the normal mechanisms in the state machine: +-- +-- * **Immediate**: The event method has exactly the name of the event. +-- * **Delayed**: The event method starts with a __ + the name of the event. The first parameter of the event method is a number value, expressing the delay in seconds when the event will be executed. +-- +-- ### ACT_ROUTE **States**: +-- +-- * **None**: The controllable did not receive route commands. +-- * **Arrived (*)**: The controllable has arrived at a route point. +-- * **Aborted (*)**: The controllable has aborted the route path. +-- * **Routing**: The controllable is understay to the route point. +-- * **Pausing**: The process is pausing the routing. AI air will go into hover, AI ground will stop moving. Players can fly around. +-- * **Success (*)**: All route points were reached. +-- * **Failed (*)**: The process has failed. +-- +-- (*) End states of the process. +-- +-- ### ACT_ROUTE state transition methods: +-- +-- State transition functions can be set **by the mission designer** customizing or improving the behaviour of the state. +-- There are 2 moments when state transition methods will be called by the state machine: +-- +-- * **Before** the state transition. +-- The state transition method needs to start with the name **OnBefore + the name of the state**. +-- If the state transition method returns false, then the processing of the state transition will not be done! +-- If you want to change the behaviour of the AIControllable at this event, return false, +-- but then you'll need to specify your own logic using the AIControllable! +-- +-- * **After** the state transition. +-- The state transition method needs to start with the name **OnAfter + the name of the state**. +-- These state transition methods need to provide a return value, which is specified at the function description. +-- +-- === +-- +-- # 1) @{#ACT_ROUTE_ZONE} class, extends @{Fsm.Route#ACT_ROUTE} +-- +-- The ACT_ROUTE_ZONE class implements the core functions to route an AIR @{Controllable} player @{Unit} to a @{Zone}. +-- The player receives on perioding times messages with the coordinates of the route to follow. +-- Upon arrival at the zone, a confirmation of arrival is sent, and the process will be ended. +-- +-- # 1.1) ACT_ROUTE_ZONE constructor: +-- +-- * @{#ACT_ROUTE_ZONE.New}(): Creates a new ACT_ROUTE_ZONE object. +-- +-- === +-- +-- @module Route + + +do -- ACT_ROUTE + + --- ACT_ROUTE class + -- @type ACT_ROUTE + -- @field Tasking.Task#TASK TASK + -- @field Wrapper.Unit#UNIT ProcessUnit + -- @field Core.Zone#ZONE_BASE TargetZone + -- @extends Core.Fsm#FSM_PROCESS + ACT_ROUTE = { + ClassName = "ACT_ROUTE", + } + + + --- Creates a new routing state machine. The process will route a CLIENT to a ZONE until the CLIENT is within that ZONE. + -- @param #ACT_ROUTE self + -- @return #ACT_ROUTE self + function ACT_ROUTE:New() + + -- Inherits from BASE + local self = BASE:Inherit( self, FSM_PROCESS:New( "ACT_ROUTE" ) ) -- Core.Fsm#FSM_PROCESS + + self:AddTransition( "None", "Start", "Routing" ) + self:AddTransition( "*", "Report", "Reporting" ) + self:AddTransition( "*", "Route", "Routing" ) + self:AddTransition( "Routing", "Pause", "Pausing" ) + self:AddTransition( "*", "Abort", "Aborted" ) + self:AddTransition( "Routing", "Arrive", "Arrived" ) + self:AddTransition( "Arrived", "Success", "Success" ) + self:AddTransition( "*", "Fail", "Failed" ) + self:AddTransition( "", "", "" ) + self:AddTransition( "", "", "" ) + + self:AddEndState( "Arrived" ) + self:AddEndState( "Failed" ) + + self:SetStartState( "None" ) + + return self + end + + --- Task Events + + --- StateMachine callback function + -- @param #ACT_ROUTE self + -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit + -- @param #string Event + -- @param #string From + -- @param #string To + function ACT_ROUTE:onafterStart( ProcessUnit, Event, From, To ) + + + self:__Route( 1 ) + end + + --- Check if the controllable has arrived. + -- @param #ACT_ROUTE self + -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit + -- @return #boolean + function ACT_ROUTE:onfuncHasArrived( ProcessUnit ) + return false + end + + --- StateMachine callback function + -- @param #ACT_ROUTE self + -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit + -- @param #string Event + -- @param #string From + -- @param #string To + function ACT_ROUTE:onbeforeRoute( ProcessUnit, Event, From, To ) + + if ProcessUnit:IsAlive() then + local HasArrived = self:onfuncHasArrived( ProcessUnit ) -- Polymorphic + if self.DisplayCount >= self.DisplayInterval then + self:T( { HasArrived = HasArrived } ) + if not HasArrived then + self:__Report( 1 ) + end + self.DisplayCount = 1 + else + self.DisplayCount = self.DisplayCount + 1 + end + + self:T( { DisplayCount = self.DisplayCount } ) + + if HasArrived then + self:__Arrive( 1 ) + else + self:__Route( 1 ) + end + + return HasArrived -- if false, then the event will not be executed... + end + + return false + + end + +end -- ACT_ROUTE + + + +do -- ACT_ROUTE_ZONE + + --- ACT_ROUTE_ZONE class + -- @type ACT_ROUTE_ZONE + -- @field Tasking.Task#TASK TASK + -- @field Wrapper.Unit#UNIT ProcessUnit + -- @field Core.Zone#ZONE_BASE TargetZone + -- @extends #ACT_ROUTE + ACT_ROUTE_ZONE = { + ClassName = "ACT_ROUTE_ZONE", + } + + + --- Creates a new routing state machine. The task will route a controllable to a ZONE until the controllable is within that ZONE. + -- @param #ACT_ROUTE_ZONE self + -- @param Core.Zone#ZONE_BASE TargetZone + function ACT_ROUTE_ZONE:New( TargetZone ) + local self = BASE:Inherit( self, ACT_ROUTE:New() ) -- #ACT_ROUTE_ZONE + + self.TargetZone = TargetZone + + self.DisplayInterval = 30 + self.DisplayCount = 30 + self.DisplayMessage = true + self.DisplayTime = 10 -- 10 seconds is the default + + return self + end + + function ACT_ROUTE_ZONE:Init( FsmRoute ) + + self.TargetZone = FsmRoute.TargetZone + self.DisplayInterval = 30 + self.DisplayCount = 30 + self.DisplayMessage = true + self.DisplayTime = 10 -- 10 seconds is the default + end + + --- Method override to check if the controllable has arrived. + -- @param #ACT_ROUTE self + -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit + -- @return #boolean + function ACT_ROUTE_ZONE:onfuncHasArrived( ProcessUnit ) + + if ProcessUnit:IsInZone( self.TargetZone ) then + local RouteText = "You have arrived within the zone." + self:Message( RouteText ) + end + + return ProcessUnit:IsInZone( self.TargetZone ) + end + + --- Task Events + + --- StateMachine callback function + -- @param #ACT_ROUTE_ZONE self + -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit + -- @param #string Event + -- @param #string From + -- @param #string To + function ACT_ROUTE_ZONE:onenterReporting( ProcessUnit, Event, From, To ) + + local ZoneVec2 = self.TargetZone:GetVec2() + local ZonePointVec2 = POINT_VEC2:New( ZoneVec2.x, ZoneVec2.y ) + local TaskUnitVec2 = ProcessUnit:GetVec2() + local TaskUnitPointVec2 = POINT_VEC2:New( TaskUnitVec2.x, TaskUnitVec2.y ) + local RouteText = "Route to " .. TaskUnitPointVec2:GetBRText( ZonePointVec2 ) .. " km to target." + self:Message( RouteText ) + end + +end -- ACT_ROUTE_ZONE +--- (SP) (MP) (FSM) Account for (Detect, count and report) DCS events occuring on DCS objects (units). +-- +-- === +-- +-- # @{#ACT_ACCOUNT} FSM class, extends @{Core.Fsm#FSM_PROCESS} +-- +-- ## ACT_ACCOUNT state machine: +-- +-- This class is a state machine: it manages a process that is triggered by events causing state transitions to occur. +-- All derived classes from this class will start with the class name, followed by a \_. See the relevant derived class descriptions below. +-- Each derived class follows exactly the same process, using the same events and following the same state transitions, +-- but will have **different implementation behaviour** upon each event or state transition. +-- +-- ### ACT_ACCOUNT **Events**: +-- +-- These are the events defined in this class: +-- +-- * **Start**: The process is started. The process will go into the Report state. +-- * **Event**: A relevant event has occured that needs to be accounted for. The process will go into the Account state. +-- * **Report**: The process is reporting to the player the accounting status of the DCS events. +-- * **More**: There are more DCS events that need to be accounted for. The process will go back into the Report state. +-- * **NoMore**: There are no more DCS events that need to be accounted for. The process will go into the Success state. +-- +-- ### ACT_ACCOUNT **Event methods**: +-- +-- Event methods are available (dynamically allocated by the state machine), that accomodate for state transitions occurring in the process. +-- There are two types of event methods, which you can use to influence the normal mechanisms in the state machine: +-- +-- * **Immediate**: The event method has exactly the name of the event. +-- * **Delayed**: The event method starts with a __ + the name of the event. The first parameter of the event method is a number value, expressing the delay in seconds when the event will be executed. +-- +-- ### ACT_ACCOUNT **States**: +-- +-- * **Assigned**: The player is assigned to the task. This is the initialization state for the process. +-- * **Waiting**: the process is waiting for a DCS event to occur within the simulator. This state is set automatically. +-- * **Report**: The process is Reporting to the players in the group of the unit. This state is set automatically every 30 seconds. +-- * **Account**: The relevant DCS event has occurred, and is accounted for. +-- * **Success (*)**: All DCS events were accounted for. +-- * **Failed (*)**: The process has failed. +-- +-- (*) End states of the process. +-- +-- ### ACT_ACCOUNT state transition methods: +-- +-- State transition functions can be set **by the mission designer** customizing or improving the behaviour of the state. +-- There are 2 moments when state transition methods will be called by the state machine: +-- +-- * **Before** the state transition. +-- The state transition method needs to start with the name **OnBefore + the name of the state**. +-- If the state transition method returns false, then the processing of the state transition will not be done! +-- If you want to change the behaviour of the AIControllable at this event, return false, +-- but then you'll need to specify your own logic using the AIControllable! +-- +-- * **After** the state transition. +-- The state transition method needs to start with the name **OnAfter + the name of the state**. +-- These state transition methods need to provide a return value, which is specified at the function description. +-- +-- # 1) @{#ACT_ACCOUNT_DEADS} FSM class, extends @{Fsm.Account#ACT_ACCOUNT} +-- +-- The ACT_ACCOUNT_DEADS class accounts (detects, counts and reports) successful kills of DCS units. +-- The process is given a @{Set} of units that will be tracked upon successful destruction. +-- The process will end after each target has been successfully destroyed. +-- Each successful dead will trigger an Account state transition that can be scored, modified or administered. +-- +-- +-- ## ACT_ACCOUNT_DEADS constructor: +-- +-- * @{#ACT_ACCOUNT_DEADS.New}(): Creates a new ACT_ACCOUNT_DEADS object. +-- +-- === +-- +-- @module Account + + +do -- ACT_ACCOUNT + + --- ACT_ACCOUNT class + -- @type ACT_ACCOUNT + -- @field Set#SET_UNIT TargetSetUnit + -- @extends Core.Fsm#FSM_PROCESS + ACT_ACCOUNT = { + ClassName = "ACT_ACCOUNT", + TargetSetUnit = nil, + } + + --- Creates a new DESTROY process. + -- @param #ACT_ACCOUNT self + -- @return #ACT_ACCOUNT + function ACT_ACCOUNT:New() + + -- Inherits from BASE + local self = BASE:Inherit( self, FSM_PROCESS:New() ) -- Core.Fsm#FSM_PROCESS + + self:AddTransition( "Assigned", "Start", "Waiting") + self:AddTransition( "*", "Wait", "Waiting") + self:AddTransition( "*", "Report", "Report") + self:AddTransition( "*", "Event", "Account") + self:AddTransition( "Account", "More", "Wait") + self:AddTransition( "Account", "NoMore", "Accounted") + self:AddTransition( "*", "Fail", "Failed") + + self:AddEndState( "Accounted" ) + self:AddEndState( "Failed" ) + + self:SetStartState( "Assigned" ) + + return self + end + + --- Process Events + + --- StateMachine callback function + -- @param #ACT_ACCOUNT self + -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit + -- @param #string Event + -- @param #string From + -- @param #string To + function ACT_ACCOUNT:onafterStart( ProcessUnit, Event, From, To ) + + self:EventOnDead( self.onfuncEventDead ) + + self:__Wait( 1 ) + end + + + --- StateMachine callback function + -- @param #ACT_ACCOUNT self + -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit + -- @param #string Event + -- @param #string From + -- @param #string To + function ACT_ACCOUNT:onenterWaiting( ProcessUnit, Event, From, To ) + + if self.DisplayCount >= self.DisplayInterval then + self:Report() + self.DisplayCount = 1 + else + self.DisplayCount = self.DisplayCount + 1 + end + + return true -- Process always the event. + end + + --- StateMachine callback function + -- @param #ACT_ACCOUNT self + -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit + -- @param #string Event + -- @param #string From + -- @param #string To + function ACT_ACCOUNT:onafterEvent( ProcessUnit, Event, From, To, Event ) + + self:__NoMore( 1 ) + end + +end -- ACT_ACCOUNT + +do -- ACT_ACCOUNT_DEADS + + --- ACT_ACCOUNT_DEADS class + -- @type ACT_ACCOUNT_DEADS + -- @field Set#SET_UNIT TargetSetUnit + -- @extends #ACT_ACCOUNT + ACT_ACCOUNT_DEADS = { + ClassName = "ACT_ACCOUNT_DEADS", + TargetSetUnit = nil, + } + + + --- Creates a new DESTROY process. + -- @param #ACT_ACCOUNT_DEADS self + -- @param Set#SET_UNIT TargetSetUnit + -- @param #string TaskName + function ACT_ACCOUNT_DEADS:New( TargetSetUnit, TaskName ) + -- Inherits from BASE + local self = BASE:Inherit( self, ACT_ACCOUNT:New() ) -- #ACT_ACCOUNT_DEADS + + self.TargetSetUnit = TargetSetUnit + self.TaskName = TaskName + + self.DisplayInterval = 30 + self.DisplayCount = 30 + self.DisplayMessage = true + self.DisplayTime = 10 -- 10 seconds is the default + self.DisplayCategory = "HQ" -- Targets is the default display category + + return self + end + + function ACT_ACCOUNT_DEADS:Init( FsmAccount ) + + self.TargetSetUnit = FsmAccount.TargetSetUnit + self.TaskName = FsmAccount.TaskName + end + + + + function ACT_ACCOUNT_DEADS:_Destructor() + self:E("_Destructor") + + self:EventRemoveAll() + + end + + --- Process Events + + --- StateMachine callback function + -- @param #ACT_ACCOUNT_DEADS self + -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit + -- @param #string Event + -- @param #string From + -- @param #string To + function ACT_ACCOUNT_DEADS:onenterReport( ProcessUnit, Event, From, To ) + self:E( { ProcessUnit, Event, From, To } ) + + self:Message( "Your group with assigned " .. self.TaskName .. " task has " .. self.TargetSetUnit:GetUnitTypesText() .. " targets left to be destroyed." ) + end + + + --- StateMachine callback function + -- @param #ACT_ACCOUNT_DEADS self + -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit + -- @param #string Event + -- @param #string From + -- @param #string To + function ACT_ACCOUNT_DEADS:onenterAccount( ProcessUnit, Event, From, To, EventData ) + self:T( { ProcessUnit, EventData, Event, From, To } ) + + self:T({self.Controllable}) + + self.TargetSetUnit:Flush() + + if self.TargetSetUnit:FindUnit( EventData.IniUnitName ) then + local TaskGroup = ProcessUnit:GetGroup() + self.TargetSetUnit:RemoveUnitsByName( EventData.IniUnitName ) + self:Message( "You hit a target. Your group with assigned " .. self.TaskName .. " task has " .. self.TargetSetUnit:Count() .. " targets ( " .. self.TargetSetUnit:GetUnitTypesText() .. " ) left to be destroyed." ) + end + end + + --- StateMachine callback function + -- @param #ACT_ACCOUNT_DEADS self + -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit + -- @param #string Event + -- @param #string From + -- @param #string To + function ACT_ACCOUNT_DEADS:onafterEvent( ProcessUnit, Event, From, To, EventData ) + + if self.TargetSetUnit:Count() > 0 then + self:__More( 1 ) + else + self:__NoMore( 1 ) + end + end + + --- DCS Events + + --- @param #ACT_ACCOUNT_DEADS self + -- @param Event#EVENTDATA EventData + function ACT_ACCOUNT_DEADS:onfuncEventDead( EventData ) + self:T( { "EventDead", EventData } ) + + if EventData.IniDCSUnit then + self:__Event( 1, EventData ) + end + end + +end -- ACT_ACCOUNT DEADS +--- (SP) (MP) (FSM) Route AI or players through waypoints or to zones. +-- +-- === +-- +-- # @{#ACT_ASSIST} FSM class, extends @{Core.Fsm#FSM_PROCESS} +-- +-- ## ACT_ASSIST state machine: +-- +-- This class is a state machine: it manages a process that is triggered by events causing state transitions to occur. +-- All derived classes from this class will start with the class name, followed by a \_. See the relevant derived class descriptions below. +-- Each derived class follows exactly the same process, using the same events and following the same state transitions, +-- but will have **different implementation behaviour** upon each event or state transition. +-- +-- ### ACT_ASSIST **Events**: +-- +-- These are the events defined in this class: +-- +-- * **Start**: The process is started. +-- * **Next**: The process is smoking the targets in the given zone. +-- +-- ### ACT_ASSIST **Event methods**: +-- +-- Event methods are available (dynamically allocated by the state machine), that accomodate for state transitions occurring in the process. +-- There are two types of event methods, which you can use to influence the normal mechanisms in the state machine: +-- +-- * **Immediate**: The event method has exactly the name of the event. +-- * **Delayed**: The event method starts with a __ + the name of the event. The first parameter of the event method is a number value, expressing the delay in seconds when the event will be executed. +-- +-- ### ACT_ASSIST **States**: +-- +-- * **None**: The controllable did not receive route commands. +-- * **AwaitSmoke (*)**: The process is awaiting to smoke the targets in the zone. +-- * **Smoking (*)**: The process is smoking the targets in the zone. +-- * **Failed (*)**: The process has failed. +-- +-- (*) End states of the process. +-- +-- ### ACT_ASSIST state transition methods: +-- +-- State transition functions can be set **by the mission designer** customizing or improving the behaviour of the state. +-- There are 2 moments when state transition methods will be called by the state machine: +-- +-- * **Before** the state transition. +-- The state transition method needs to start with the name **OnBefore + the name of the state**. +-- If the state transition method returns false, then the processing of the state transition will not be done! +-- If you want to change the behaviour of the AIControllable at this event, return false, +-- but then you'll need to specify your own logic using the AIControllable! +-- +-- * **After** the state transition. +-- The state transition method needs to start with the name **OnAfter + the name of the state**. +-- These state transition methods need to provide a return value, which is specified at the function description. +-- +-- === +-- +-- # 1) @{#ACT_ASSIST_SMOKE_TARGETS_ZONE} class, extends @{Fsm.Route#ACT_ASSIST} +-- +-- The ACT_ASSIST_SMOKE_TARGETS_ZONE class implements the core functions to smoke targets in a @{Zone}. +-- The targets are smoked within a certain range around each target, simulating a realistic smoking behaviour. +-- At random intervals, a new target is smoked. +-- +-- # 1.1) ACT_ASSIST_SMOKE_TARGETS_ZONE constructor: +-- +-- * @{#ACT_ASSIST_SMOKE_TARGETS_ZONE.New}(): Creates a new ACT_ASSIST_SMOKE_TARGETS_ZONE object. +-- +-- === +-- +-- @module Smoke + +do -- ACT_ASSIST + + --- ACT_ASSIST class + -- @type ACT_ASSIST + -- @extends Core.Fsm#FSM_PROCESS + ACT_ASSIST = { + ClassName = "ACT_ASSIST", + } + + --- Creates a new target smoking state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator. + -- @param #ACT_ASSIST self + -- @return #ACT_ASSIST + function ACT_ASSIST:New() + + -- Inherits from BASE + local self = BASE:Inherit( self, FSM_PROCESS:New( "ACT_ASSIST" ) ) -- Core.Fsm#FSM_PROCESS + + self:AddTransition( "None", "Start", "AwaitSmoke" ) + self:AddTransition( "AwaitSmoke", "Next", "Smoking" ) + self:AddTransition( "Smoking", "Next", "AwaitSmoke" ) + self:AddTransition( "*", "Stop", "Success" ) + self:AddTransition( "*", "Fail", "Failed" ) + + self:AddEndState( "Failed" ) + self:AddEndState( "Success" ) + + self:SetStartState( "None" ) + + return self + end + + --- Task Events + + --- StateMachine callback function + -- @param #ACT_ASSIST self + -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit + -- @param #string Event + -- @param #string From + -- @param #string To + function ACT_ASSIST:onafterStart( ProcessUnit, Event, From, To ) + + local ProcessGroup = ProcessUnit:GetGroup() + local MissionMenu = self:GetMission():GetMissionMenu( ProcessGroup ) + + local function MenuSmoke( MenuParam ) + self:E( MenuParam ) + local self = MenuParam.self + local SmokeColor = MenuParam.SmokeColor + self.SmokeColor = SmokeColor + self:__Next( 1 ) + end + + self.Menu = MENU_GROUP:New( ProcessGroup, "Target acquisition", MissionMenu ) + self.MenuSmokeBlue = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop blue smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Blue } ) + self.MenuSmokeGreen = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop green smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Green } ) + self.MenuSmokeOrange = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop Orange smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Orange } ) + self.MenuSmokeRed = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop Red smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Red } ) + self.MenuSmokeWhite = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop White smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.White } ) + end + +end + +do -- ACT_ASSIST_SMOKE_TARGETS_ZONE + + --- ACT_ASSIST_SMOKE_TARGETS_ZONE class + -- @type ACT_ASSIST_SMOKE_TARGETS_ZONE + -- @field Set#SET_UNIT TargetSetUnit + -- @field Core.Zone#ZONE_BASE TargetZone + -- @extends #ACT_ASSIST + ACT_ASSIST_SMOKE_TARGETS_ZONE = { + ClassName = "ACT_ASSIST_SMOKE_TARGETS_ZONE", + } + +-- function ACT_ASSIST_SMOKE_TARGETS_ZONE:_Destructor() +-- self:E("_Destructor") +-- +-- self.Menu:Remove() +-- self:EventRemoveAll() +-- end + + --- Creates a new target smoking state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator. + -- @param #ACT_ASSIST_SMOKE_TARGETS_ZONE self + -- @param Set#SET_UNIT TargetSetUnit + -- @param Core.Zone#ZONE_BASE TargetZone + function ACT_ASSIST_SMOKE_TARGETS_ZONE:New( TargetSetUnit, TargetZone ) + local self = BASE:Inherit( self, ACT_ASSIST:New() ) -- #ACT_ASSIST + + self.TargetSetUnit = TargetSetUnit + self.TargetZone = TargetZone + + return self + end + + function ACT_ASSIST_SMOKE_TARGETS_ZONE:Init( FsmSmoke ) + + self.TargetSetUnit = FsmSmoke.TargetSetUnit + self.TargetZone = FsmSmoke.TargetZone + end + + --- Creates a new target smoking state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator. + -- @param #ACT_ASSIST_SMOKE_TARGETS_ZONE self + -- @param Set#SET_UNIT TargetSetUnit + -- @param Core.Zone#ZONE_BASE TargetZone + -- @return #ACT_ASSIST_SMOKE_TARGETS_ZONE self + function ACT_ASSIST_SMOKE_TARGETS_ZONE:Init( TargetSetUnit, TargetZone ) + + self.TargetSetUnit = TargetSetUnit + self.TargetZone = TargetZone + + return self + end + + --- StateMachine callback function + -- @param #ACT_ASSIST_SMOKE_TARGETS_ZONE self + -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit + -- @param #string Event + -- @param #string From + -- @param #string To + function ACT_ASSIST_SMOKE_TARGETS_ZONE:onenterSmoking( ProcessUnit, Event, From, To ) + + self.TargetSetUnit:ForEachUnit( + --- @param Wrapper.Unit#UNIT SmokeUnit + function( SmokeUnit ) + if math.random( 1, ( 100 * self.TargetSetUnit:Count() ) / 4 ) <= 100 then + SCHEDULER:New( self, + function() + if SmokeUnit:IsAlive() then + SmokeUnit:Smoke( self.SmokeColor, 150 ) + end + end, {}, math.random( 10, 60 ) + ) + end + end + ) + + end + +end--- A COMMANDCENTER is the owner of multiple missions within MOOSE. +-- A COMMANDCENTER governs multiple missions, the tasking and the reporting. +-- @module CommandCenter + + + +--- The REPORT class +-- @type REPORT +-- @extends Core.Base#BASE +REPORT = { + ClassName = "REPORT", +} + +--- Create a new REPORT. +-- @param #REPORT self +-- @param #string Title +-- @return #REPORT +function REPORT:New( Title ) + + local self = BASE:Inherit( self, BASE:New() ) + + self.Report = {} + self.Report[#self.Report+1] = Title + + return self +end + +--- Add a new line to a REPORT. +-- @param #REPORT self +-- @param #string Text +-- @return #REPORT +function REPORT:Add( Text ) + self.Report[#self.Report+1] = Text + return self.Report[#self.Report+1] +end + +function REPORT:Text() + return table.concat( self.Report, "\n" ) +end + +--- The COMMANDCENTER class +-- @type COMMANDCENTER +-- @field Wrapper.Group#GROUP HQ +-- @field Dcs.DCSCoalitionWrapper.Object#coalition CommandCenterCoalition +-- @list Missions +-- @extends Core.Base#BASE +COMMANDCENTER = { + ClassName = "COMMANDCENTER", + CommandCenterName = "", + CommandCenterCoalition = nil, + CommandCenterPositionable = nil, + Name = "", +} +--- The constructor takes an IDENTIFIABLE as the HQ command center. +-- @param #COMMANDCENTER self +-- @param Wrapper.Positionable#POSITIONABLE CommandCenterPositionable +-- @param #string CommandCenterName +-- @return #COMMANDCENTER +function COMMANDCENTER:New( CommandCenterPositionable, CommandCenterName ) + + local self = BASE:Inherit( self, BASE:New() ) + + self.CommandCenterPositionable = CommandCenterPositionable + self.CommandCenterName = CommandCenterName or CommandCenterPositionable:GetName() + self.CommandCenterCoalition = CommandCenterPositionable:GetCoalition() + + self.Missions = setmetatable( {}, { __mode = "v" } ) + + self:EventOnBirth( + --- @param #COMMANDCENTER self + --- @param Core.Event#EVENTDATA EventData + function( self, EventData ) + self:E( { EventData } ) + local EventGroup = GROUP:Find( EventData.IniDCSGroup ) + if EventGroup and self:HasGroup( EventGroup ) then + local MenuReporting = MENU_GROUP:New( EventGroup, "Reporting", self.CommandCenterMenu ) + local MenuMissionsSummary = MENU_GROUP_COMMAND:New( EventGroup, "Missions Summary Report", MenuReporting, self.ReportSummary, self, EventGroup ) + local MenuMissionsDetails = MENU_GROUP_COMMAND:New( EventGroup, "Missions Details Report", MenuReporting, self.ReportDetails, self, EventGroup ) + self:ReportSummary( EventGroup ) + end + local PlayerUnit = EventData.IniUnit + for MissionID, Mission in pairs( self:GetMissions() ) do + local Mission = Mission -- Tasking.Mission#MISSION + Mission:JoinUnit( PlayerUnit ) + Mission:ReportDetails() + end + + end + ) + + -- When a player enters a client or a unit, the CommandCenter will check for each Mission and each Task in the Mission if the player has things to do. + -- For these elements, it will= + -- - Set the correct menu. + -- - Assign the PlayerUnit to the Task if required. + -- - Send a message to the other players in the group that this player has joined. + self:EventOnPlayerEnterUnit( + --- @param #COMMANDCENTER self + -- @param Core.Event#EVENTDATA EventData + function( self, EventData ) + local PlayerUnit = EventData.IniUnit + for MissionID, Mission in pairs( self:GetMissions() ) do + local Mission = Mission -- Tasking.Mission#MISSION + Mission:JoinUnit( PlayerUnit ) + Mission:ReportDetails() + end + end + ) + + -- Handle when a player leaves a slot and goes back to spectators ... + -- The PlayerUnit will be UnAssigned from the Task. + -- When there is no Unit left running the Task, the Task goes into Abort... + self:EventOnPlayerLeaveUnit( + --- @param #TASK self + -- @param Core.Event#EVENTDATA EventData + function( self, EventData ) + local PlayerUnit = EventData.IniUnit + for MissionID, Mission in pairs( self:GetMissions() ) do + Mission:AbortUnit( PlayerUnit ) + end + end + ) + + -- Handle when a player leaves a slot and goes back to spectators ... + -- The PlayerUnit will be UnAssigned from the Task. + -- When there is no Unit left running the Task, the Task goes into Abort... + self:EventOnCrash( + --- @param #TASK self + -- @param Core.Event#EVENTDATA EventData + function( self, EventData ) + local PlayerUnit = EventData.IniUnit + for MissionID, Mission in pairs( self:GetMissions() ) do + Mission:CrashUnit( PlayerUnit ) + end + end + ) + + return self +end + +--- Gets the name of the HQ command center. +-- @param #COMMANDCENTER self +-- @return #string +function COMMANDCENTER:GetName() + + return self.HQName +end + +--- Gets the POSITIONABLE of the HQ command center. +-- @param #COMMANDCENTER self +-- @return Wrapper.Positionable#POSITIONABLE +function COMMANDCENTER:GetPositionable() + return self.CommandCenterPositionable +end + +--- Get the Missions governed by the HQ command center. +-- @param #COMMANDCENTER self +-- @return #list +function COMMANDCENTER:GetMissions() + + return self.Missions +end + +--- Add a MISSION to be governed by the HQ command center. +-- @param #COMMANDCENTER self +-- @param Tasking.Mission#MISSION Mission +-- @return Tasking.Mission#MISSION +function COMMANDCENTER:AddMission( Mission ) + + self.Missions[Mission] = Mission + + return Mission +end + +--- Removes a MISSION to be governed by the HQ command center. +-- The given Mission is not nilified. +-- @param #COMMANDCENTER self +-- @param Tasking.Mission#MISSION Mission +-- @return Tasking.Mission#MISSION +function COMMANDCENTER:RemoveMission( Mission ) + + self.Missions[Mission] = nil + + return Mission +end + +--- Sets the menu structure of the Missions governed by the HQ command center. +-- @param #COMMANDCENTER self +function COMMANDCENTER:SetMenu() + self:F() + + self.CommandCenterMenu = self.CommandCenterMenu or MENU_COALITION:New( self.CommandCenterCoalition, "HQ" ) + + for MissionID, Mission in pairs( self:GetMissions() ) do + local Mission = Mission -- Tasking.Mission#MISSION + Mission:SetMenu() + end +end + + +--- Checks of the COMMANDCENTER has a GROUP. +-- @param #COMMANDCENTER self +-- @param Wrapper.Group#GROUP +-- @return #boolean +function COMMANDCENTER:HasGroup( MissionGroup ) + + local Has = false + + for MissionID, Mission in pairs( self.Missions ) do + local Mission = Mission -- Tasking.Mission#MISSION + if Mission:HasGroup( MissionGroup ) then + Has = true + break + end + end + + return Has +end + +--- Send a CC message to a GROUP. +-- @param #COMMANDCENTER self +function COMMANDCENTER:MessageToGroup( Message, TaskGroup ) + + self:GetPositionable():MessageToGroup( Message , 20, TaskGroup ) + +end + +--- Send a CC message to the coalition of the CC. +-- @param #COMMANDCENTER self +function COMMANDCENTER:MessageToCoalition( Message ) + + local CCCoalition = self:GetPositionable():GetCoalition() + self:GetPositionable():MessageToBlue( Message , 20, CCCoalition ) + +end + +--- Report the status of all MISSIONs to a GROUP. +-- Each Mission is listed, with an indication how many Tasks are still to be completed. +-- @param #COMMANDCENTER self +function COMMANDCENTER:ReportSummary( ReportGroup ) + self:E( ReportGroup ) + + local Report = REPORT:New() + + for MissionID, Mission in pairs( self.Missions ) do + local Mission = Mission -- Tasking.Mission#MISSION + Report:Add( " - " .. Mission:ReportOverview() ) + end + + self:GetPositionable():MessageToGroup( Report:Text(), 30, ReportGroup ) + +end + +--- Report the status of a Task to a Group. +-- Report the details of a Mission, listing the Mission, and all the Task details. +-- @param #COMMANDCENTER self +function COMMANDCENTER:ReportDetails( ReportGroup, Task ) + self:E( ReportGroup ) + + local Report = REPORT:New() + + for MissionID, Mission in pairs( self.Missions ) do + local Mission = Mission -- Tasking.Mission#MISSION + Report:Add( " - " .. Mission:ReportDetails() ) + end + + self:GetPositionable():MessageToGroup( Report:Text(), 30, ReportGroup ) +end + +--- A MISSION is the main owner of a Mission orchestration within MOOSE . The Mission framework orchestrates @{CLIENT}s, @{TASK}s, @{STAGE}s etc. +-- A @{CLIENT} needs to be registered within the @{MISSION} through the function @{AddClient}. A @{TASK} needs to be registered within the @{MISSION} through the function @{AddTask}. +-- @module Mission + +--- The MISSION class +-- @type MISSION +-- @field #MISSION.Clients _Clients +-- @field Core.Menu#MENU_COALITION MissionMenu +-- @field #string MissionBriefing +-- @extends Core.Fsm#FSM +MISSION = { + ClassName = "MISSION", + Name = "", + MissionStatus = "PENDING", + _Clients = {}, + TaskMenus = {}, + TaskCategoryMenus = {}, + TaskTypeMenus = {}, + _ActiveTasks = {}, + GoalFunction = nil, + MissionReportTrigger = 0, + MissionProgressTrigger = 0, + MissionReportShow = false, + MissionReportFlash = false, + MissionTimeInterval = 0, + MissionCoalition = "", + SUCCESS = 1, + FAILED = 2, + REPEAT = 3, + _GoalTasks = {} +} + +--- This is the main MISSION declaration method. Each Mission is like the master or a Mission orchestration between, Clients, Tasks, Stages etc. +-- @param #MISSION self +-- @param Tasking.CommandCenter#COMMANDCENTER CommandCenter +-- @param #string MissionName is the name of the mission. This name will be used to reference the status of each mission by the players. +-- @param #string MissionPriority is a string indicating the "priority" of the Mission. f.e. "Primary", "Secondary" or "First", "Second". It is free format and up to the Mission designer to choose. There are no rules behind this field. +-- @param #string MissionBriefing is a string indicating the mission briefing to be shown when a player joins a @{CLIENT}. +-- @param Dcs.DCSCoalitionWrapper.Object#coalition MissionCoalition is a string indicating the coalition or party to which this mission belongs to. It is free format and can be chosen freely by the mission designer. Note that this field is not to be confused with the coalition concept of the ME. Examples of a Mission Coalition could be "NATO", "CCCP", "Intruders", "Terrorists"... +-- @return #MISSION self +function MISSION:New( CommandCenter, MissionName, MissionPriority, MissionBriefing, MissionCoalition ) + + local self = BASE:Inherit( self, FSM:New() ) -- Core.Fsm#FSM + + self:SetStartState( "Idle" ) + + self:AddTransition( "Idle", "Start", "Ongoing" ) + self:AddTransition( "Ongoing", "Stop", "Idle" ) + self:AddTransition( "Ongoing", "Complete", "Completed" ) + self:AddTransition( "*", "Fail", "Failed" ) + + self:T( { MissionName, MissionPriority, MissionBriefing, MissionCoalition } ) + + self.CommandCenter = CommandCenter + CommandCenter:AddMission( self ) + + self.Name = MissionName + self.MissionPriority = MissionPriority + self.MissionBriefing = MissionBriefing + self.MissionCoalition = MissionCoalition + + self.Tasks = {} + + return self +end + +--- FSM function for a MISSION +-- @param #MISSION self +-- @param #string Event +-- @param #string From +-- @param #string To +function MISSION:onbeforeComplete( Event, From, To ) + + for TaskID, Task in pairs( self:GetTasks() ) do + local Task = Task -- Tasking.Task#TASK + if not Task:IsStateSuccess() and not Task:IsStateFailed() and not Task:IsStateAborted() and not Task:IsStateCancelled() then + return false -- Mission cannot be completed. Other Tasks are still active. + end + end + return true -- Allow Mission completion. +end + +--- FSM function for a MISSION +-- @param #MISSION self +-- @param #string Event +-- @param #string From +-- @param #string To +function MISSION:onenterCompleted( Event, From, To ) + + self:GetCommandCenter():MessageToCoalition( "Mission " .. self:GetName() .. " has been completed! Good job guys!" ) +end + +--- Gets the mission name. +-- @param #MISSION self +-- @return #MISSION self +function MISSION:GetName() + return self.Name +end + +--- Add a Unit to join the Mission. +-- For each Task within the Mission, the Unit is joined with the Task. +-- If the Unit was not part of a Task in the Mission, false is returned. +-- If the Unit is part of a Task in the Mission, true is returned. +-- @param #MISSION self +-- @param Wrapper.Unit#UNIT PlayerUnit The CLIENT or UNIT of the Player joining the Mission. +-- @return #boolean true if Unit is part of a Task in the Mission. +function MISSION:JoinUnit( PlayerUnit ) + self:F( { PlayerUnit = PlayerUnit } ) + + local PlayerUnitAdded = false + + for TaskID, Task in pairs( self:GetTasks() ) do + local Task = Task -- Tasking.Task#TASK + if Task:JoinUnit( PlayerUnit ) then + PlayerUnitAdded = true + end + end + + return PlayerUnitAdded +end + +--- Aborts a PlayerUnit from the Mission. +-- For each Task within the Mission, the PlayerUnit is removed from Task where it is assigned. +-- If the Unit was not part of a Task in the Mission, false is returned. +-- If the Unit is part of a Task in the Mission, true is returned. +-- @param #MISSION self +-- @param Wrapper.Unit#UNIT PlayerUnit The CLIENT or UNIT of the Player joining the Mission. +-- @return #boolean true if Unit is part of a Task in the Mission. +function MISSION:AbortUnit( PlayerUnit ) + self:F( { PlayerUnit = PlayerUnit } ) + + local PlayerUnitRemoved = false + + for TaskID, Task in pairs( self:GetTasks() ) do + if Task:AbortUnit( PlayerUnit ) then + PlayerUnitRemoved = true + end + end + + return PlayerUnitRemoved +end + +--- Handles a crash of a PlayerUnit from the Mission. +-- For each Task within the Mission, the PlayerUnit is removed from Task where it is assigned. +-- If the Unit was not part of a Task in the Mission, false is returned. +-- If the Unit is part of a Task in the Mission, true is returned. +-- @param #MISSION self +-- @param Wrapper.Unit#UNIT PlayerUnit The CLIENT or UNIT of the Player crashing. +-- @return #boolean true if Unit is part of a Task in the Mission. +function MISSION:CrashUnit( PlayerUnit ) + self:F( { PlayerUnit = PlayerUnit } ) + + local PlayerUnitRemoved = false + + for TaskID, Task in pairs( self:GetTasks() ) do + if Task:CrashUnit( PlayerUnit ) then + PlayerUnitRemoved = true + end + end + + return PlayerUnitRemoved +end + +--- Add a scoring to the mission. +-- @param #MISSION self +-- @return #MISSION self +function MISSION:AddScoring( Scoring ) + self.Scoring = Scoring + return self +end + +--- Get the scoring object of a mission. +-- @param #MISSION self +-- @return #SCORING Scoring +function MISSION:GetScoring() + return self.Scoring +end + +--- Get the groups for which TASKS are given in the mission +-- @param #MISSION self +-- @return Core.Set#SET_GROUP +function MISSION:GetGroups() + + local SetGroup = SET_GROUP:New() + + for TaskID, Task in pairs( self:GetTasks() ) do + local Task = Task -- Tasking.Task#TASK + local GroupSet = Task:GetGroups() + GroupSet:ForEachGroup( + function( TaskGroup ) + SetGroup:Add( TaskGroup, TaskGroup ) + end + ) + end + + return SetGroup + +end + + +--- Sets the Planned Task menu. +-- @param #MISSION self +-- @param Core.Menu#MENU_COALITION CommandCenterMenu +function MISSION:SetMenu() + self:F() + + for _, Task in pairs( self:GetTasks() ) do + local Task = Task -- Tasking.Task#TASK + Task:SetMenu() + end +end + + +--- Gets the COMMANDCENTER. +-- @param #MISSION self +-- @return Tasking.CommandCenter#COMMANDCENTER +function MISSION:GetCommandCenter() + return self.CommandCenter +end + +--- Sets the Assigned Task menu. +-- @param #MISSION self +-- @param Tasking.Task#TASK Task +-- @param #string MenuText The menu text. +-- @return #MISSION self +function MISSION:SetAssignedMenu( Task ) + + for _, Task in pairs( self.Tasks ) do + local Task = Task -- Tasking.Task#TASK + Task:RemoveMenu() + Task:SetAssignedMenu() + end + +end + +--- Removes a Task menu. +-- @param #MISSION self +-- @param Tasking.Task#TASK Task +-- @return #MISSION self +function MISSION:RemoveTaskMenu( Task ) + + Task:RemoveMenu() +end + + +--- Gets the mission menu for the coalition. +-- @param #MISSION self +-- @param Wrapper.Group#GROUP TaskGroup +-- @return Core.Menu#MENU_COALITION self +function MISSION:GetMissionMenu( TaskGroup ) + + local CommandCenter = self:GetCommandCenter() + local CommandCenterMenu = CommandCenter.CommandCenterMenu + + local MissionName = self:GetName() + + local TaskGroupName = TaskGroup:GetName() + local MissionMenu = MENU_GROUP:New( TaskGroup, MissionName, CommandCenterMenu ) + + return MissionMenu +end + + +--- Clears the mission menu for the coalition. +-- @param #MISSION self +-- @return #MISSION self +function MISSION:ClearMissionMenu() + self.MissionMenu:Remove() + self.MissionMenu = nil +end + +--- Get the TASK identified by the TaskNumber from the Mission. This function is useful in GoalFunctions. +-- @param #string TaskName The Name of the @{Task} within the @{Mission}. +-- @return Tasking.Task#TASK The Task +-- @return #nil Returns nil if no task was found. +function MISSION:GetTask( TaskName ) + self:F( { TaskName } ) + + return self.Tasks[TaskName] +end + + +--- Register a @{Task} to be completed within the @{Mission}. +-- Note that there can be multiple @{Task}s registered to be completed. +-- Each Task can be set a certain Goals. The Mission will not be completed until all Goals are reached. +-- @param #MISSION self +-- @param Tasking.Task#TASK Task is the @{Task} object. +-- @return Tasking.Task#TASK The task added. +function MISSION:AddTask( Task ) + + local TaskName = Task:GetTaskName() + self:F( TaskName ) + + self.Tasks[TaskName] = self.Tasks[TaskName] or { n = 0 } + + self.Tasks[TaskName] = Task + + self:GetCommandCenter():SetMenu() + + return Task +end + +--- Removes a @{Task} to be completed within the @{Mission}. +-- Note that there can be multiple @{Task}s registered to be completed. +-- Each Task can be set a certain Goals. The Mission will not be completed until all Goals are reached. +-- @param #MISSION self +-- @param Tasking.Task#TASK Task is the @{Task} object. +-- @return #nil The cleaned Task reference. +function MISSION:RemoveTask( Task ) + + local TaskName = Task:GetTaskName() + + self:F( TaskName ) + self.Tasks[TaskName] = self.Tasks[TaskName] or { n = 0 } + + -- Ensure everything gets garbarge collected. + self.Tasks[TaskName] = nil + Task = nil + + collectgarbage() + + self:GetCommandCenter():SetMenu() + + return nil +end + +--- Return the next @{Task} ID to be completed within the @{Mission}. +-- @param #MISSION self +-- @param Tasking.Task#TASK Task is the @{Task} object. +-- @return Tasking.Task#TASK The task added. +function MISSION:GetNextTaskID( Task ) + + local TaskName = Task:GetTaskName() + self:F( TaskName ) + self.Tasks[TaskName] = self.Tasks[TaskName] or { n = 0 } + + self.Tasks[TaskName].n = self.Tasks[TaskName].n + 1 + + return self.Tasks[TaskName].n +end + + + +--- old stuff + +--- Returns if a Mission has completed. +-- @return bool +function MISSION:IsCompleted() + self:F() + return self.MissionStatus == "ACCOMPLISHED" +end + +--- Set a Mission to completed. +function MISSION:Completed() + self:F() + self.MissionStatus = "ACCOMPLISHED" + self:StatusToClients() +end + +--- Returns if a Mission is ongoing. +-- treturn bool +function MISSION:IsOngoing() + self:F() + return self.MissionStatus == "ONGOING" +end + +--- Set a Mission to ongoing. +function MISSION:Ongoing() + self:F() + self.MissionStatus = "ONGOING" + --self:StatusToClients() +end + +--- Returns if a Mission is pending. +-- treturn bool +function MISSION:IsPending() + self:F() + return self.MissionStatus == "PENDING" +end + +--- Set a Mission to pending. +function MISSION:Pending() + self:F() + self.MissionStatus = "PENDING" + self:StatusToClients() +end + +--- Returns if a Mission has failed. +-- treturn bool +function MISSION:IsFailed() + self:F() + return self.MissionStatus == "FAILED" +end + +--- Set a Mission to failed. +function MISSION:Failed() + self:F() + self.MissionStatus = "FAILED" + self:StatusToClients() +end + +--- Send the status of the MISSION to all Clients. +function MISSION:StatusToClients() + self:F() + if self.MissionReportFlash then + for ClientID, Client in pairs( self._Clients ) do + Client:Message( self.MissionCoalition .. ' "' .. self.Name .. '": ' .. self.MissionStatus .. '! ( ' .. self.MissionPriority .. ' mission ) ', 10, "Mission Command: Mission Status") + end + end +end + +function MISSION:HasGroup( TaskGroup ) + local Has = false + + for TaskID, Task in pairs( self:GetTasks() ) do + local Task = Task -- Tasking.Task#TASK + if Task:HasGroup( TaskGroup ) then + Has = true + break + end + end + + return Has +end + +--- Create a summary report of the Mission (one line). +-- @param #MISSION self +-- @return #string +function MISSION:ReportSummary() + + local Report = REPORT:New() + + -- List the name of the mission. + local Name = self:GetName() + + -- Determine the status of the mission. + local Status = self:GetState() + + -- Determine how many tasks are remaining. + local TasksRemaining = 0 + for TaskID, Task in pairs( self:GetTasks() ) do + local Task = Task -- Tasking.Task#TASK + if Task:IsStateSuccess() or Task:IsStateFailed() then + else + TasksRemaining = TasksRemaining + 1 + end + end + + Report:Add( "Mission " .. Name .. " - " .. Status .. " - " .. TasksRemaining .. " tasks remaining." ) + + return Report:Text() +end + +--- Create a overview report of the Mission (multiple lines). +-- @param #MISSION self +-- @return #string +function MISSION:ReportOverview() + + local Report = REPORT:New() + + -- List the name of the mission. + local Name = self:GetName() + + -- Determine the status of the mission. + local Status = self:GetState() + + Report:Add( "Mission " .. Name .. " - State '" .. Status .. "'" ) + + -- Determine how many tasks are remaining. + local TasksRemaining = 0 + for TaskID, Task in pairs( self:GetTasks() ) do + local Task = Task -- Tasking.Task#TASK + Report:Add( "- " .. Task:ReportSummary() ) + end + + return Report:Text() +end + +--- Create a detailed report of the Mission, listing all the details of the Task. +-- @param #MISSION self +-- @return #string +function MISSION:ReportDetails() + + local Report = REPORT:New() + + -- List the name of the mission. + local Name = self:GetName() + + -- Determine the status of the mission. + local Status = self:GetState() + + Report:Add( "Mission " .. Name .. " - State '" .. Status .. "'" ) + + -- Determine how many tasks are remaining. + local TasksRemaining = 0 + for TaskID, Task in pairs( self:GetTasks() ) do + local Task = Task -- Tasking.Task#TASK + Report:Add( Task:ReportDetails() ) + end + + return Report:Text() +end + +--- Report the status of all MISSIONs to all active Clients. +function MISSION:ReportToAll() + self:F() + + local AlivePlayers = '' + for ClientID, Client in pairs( self._Clients ) do + if Client:GetDCSGroup() then + if Client:GetClientGroupDCSUnit() then + if Client:GetClientGroupDCSUnit():getLife() > 0.0 then + if AlivePlayers == '' then + AlivePlayers = ' Players: ' .. Client:GetClientGroupDCSUnit():getPlayerName() + else + AlivePlayers = AlivePlayers .. ' / ' .. Client:GetClientGroupDCSUnit():getPlayerName() + end + end + end + end + end + local Tasks = self:GetTasks() + local TaskText = "" + for TaskID, TaskData in pairs( Tasks ) do + TaskText = TaskText .. " - Task " .. TaskID .. ": " .. TaskData.Name .. ": " .. TaskData:GetGoalProgress() .. "\n" + end + MESSAGE:New( self.MissionCoalition .. ' "' .. self.Name .. '": ' .. self.MissionStatus .. ' ( ' .. self.MissionPriority .. ' mission )' .. AlivePlayers .. "\n" .. TaskText:gsub("\n$",""), 10, "Mission Command: Mission Report" ):ToAll() +end + + +--- Add a goal function to a MISSION. Goal functions are called when a @{TASK} within a mission has been completed. +-- @param function GoalFunction is the function defined by the mission designer to evaluate whether a certain goal has been reached after a @{TASK} finishes within the @{MISSION}. A GoalFunction must accept 2 parameters: Mission, Client, which contains the current MISSION object and the current CLIENT object respectively. +-- @usage +-- PatriotActivation = { +-- { "US SAM Patriot Zerti", false }, +-- { "US SAM Patriot Zegduleti", false }, +-- { "US SAM Patriot Gvleti", false } +-- } +-- +-- function DeployPatriotTroopsGoal( Mission, Client ) +-- +-- +-- -- Check if the cargo is all deployed for mission success. +-- for CargoID, CargoData in pairs( Mission._Cargos ) do +-- if Group.getByName( CargoData.CargoGroupName ) then +-- CargoGroup = Group.getByName( CargoData.CargoGroupName ) +-- if CargoGroup then +-- -- Check if the cargo is ready to activate +-- CurrentLandingZoneID = routines.IsUnitInZones( CargoGroup:getUnits()[1], Mission:GetTask( 2 ).LandingZones ) -- The second task is the Deploytask to measure mission success upon +-- if CurrentLandingZoneID then +-- if PatriotActivation[CurrentLandingZoneID][2] == false then +-- -- Now check if this is a new Mission Task to be completed... +-- trigger.action.setGroupAIOn( Group.getByName( PatriotActivation[CurrentLandingZoneID][1] ) ) +-- PatriotActivation[CurrentLandingZoneID][2] = true +-- MessageToBlue( "Mission Command: Message to all airborne units! The " .. PatriotActivation[CurrentLandingZoneID][1] .. " is armed. Our air defenses are now stronger.", 60, "BLUE/PatriotDefense" ) +-- MessageToRed( "Mission Command: Our satellite systems are detecting additional NATO air defenses. To all airborne units: Take care!!!", 60, "RED/PatriotDefense" ) +-- Mission:GetTask( 2 ):AddGoalCompletion( "Patriots activated", PatriotActivation[CurrentLandingZoneID][1], 1 ) -- Register Patriot activation as part of mission goal. +-- end +-- end +-- end +-- end +-- end +-- end +-- +-- local Mission = MISSIONSCHEDULER.AddMission( 'NATO Transport Troops', 'Operational', 'Transport 3 groups of air defense engineers from our barracks "Gold" and "Titan" to each patriot battery control center to activate our air defenses.', 'NATO' ) +-- Mission:AddGoalFunction( DeployPatriotTroopsGoal ) +function MISSION:AddGoalFunction( GoalFunction ) + self:F() + self.GoalFunction = GoalFunction +end + +--- Register a new @{CLIENT} to participate within the mission. +-- @param CLIENT Client is the @{CLIENT} object. The object must have been instantiated with @{CLIENT:New}. +-- @return CLIENT +-- @usage +-- Add a number of Client objects to the Mission. +-- Mission:AddClient( CLIENT:FindByName( 'US UH-1H*HOT-Deploy Troops 1', 'Transport 3 groups of air defense engineers from our barracks "Gold" and "Titan" to each patriot battery control center to activate our air defenses.' ):Transport() ) +-- Mission:AddClient( CLIENT:FindByName( 'US UH-1H*RAMP-Deploy Troops 3', 'Transport 3 groups of air defense engineers from our barracks "Gold" and "Titan" to each patriot battery control center to activate our air defenses.' ):Transport() ) +-- Mission:AddClient( CLIENT:FindByName( 'US UH-1H*HOT-Deploy Troops 2', 'Transport 3 groups of air defense engineers from our barracks "Gold" and "Titan" to each patriot battery control center to activate our air defenses.' ):Transport() ) +-- Mission:AddClient( CLIENT:FindByName( 'US UH-1H*RAMP-Deploy Troops 4', 'Transport 3 groups of air defense engineers from our barracks "Gold" and "Titan" to each patriot battery control center to activate our air defenses.' ):Transport() ) +function MISSION:AddClient( Client ) + self:F( { Client } ) + + local Valid = true + + if Valid then + self._Clients[Client.ClientName] = Client + end + + return Client +end + +--- Find a @{CLIENT} object within the @{MISSION} by its ClientName. +-- @param CLIENT ClientName is a string defining the Client Group as defined within the ME. +-- @return CLIENT +-- @usage +-- -- Seach for Client "Bomber" within the Mission. +-- local BomberClient = Mission:FindClient( "Bomber" ) +function MISSION:FindClient( ClientName ) + self:F( { self._Clients[ClientName] } ) + return self._Clients[ClientName] +end + + +--- Get all the TASKs from the Mission. This function is useful in GoalFunctions. +-- @return {TASK,...} Structure of TASKS with the @{TASK} number as the key. +-- @usage +-- -- Get Tasks from the Mission. +-- Tasks = Mission:GetTasks() +-- env.info( "Task 2 Completion = " .. Tasks[2]:GetGoalPercentage() .. "%" ) +function MISSION:GetTasks() + self:F() + + return self.Tasks +end + + +--[[ + _TransportExecuteStage: Defines the different stages of Transport unload/load execution. This table is internal and is used to control the validity of Transport load/unload timing. + + - _TransportExecuteStage.EXECUTING + - _TransportExecuteStage.SUCCESS + - _TransportExecuteStage.FAILED + +--]] +_TransportExecuteStage = { + NONE = 0, + EXECUTING = 1, + SUCCESS = 2, + FAILED = 3 +} + + +--- The MISSIONSCHEDULER is an OBJECT and is the main scheduler of ALL active MISSIONs registered within this scheduler. It's workings are considered internal and is automatically created when the Mission.lua file is included. +-- @type MISSIONSCHEDULER +-- @field #MISSIONSCHEDULER.MISSIONS Missions +MISSIONSCHEDULER = { + Missions = {}, + MissionCount = 0, + TimeIntervalCount = 0, + TimeIntervalShow = 150, + TimeSeconds = 14400, + TimeShow = 5 +} + +--- @type MISSIONSCHEDULER.MISSIONS +-- @list <#MISSION> Mission + +--- This is the main MISSIONSCHEDULER Scheduler function. It is considered internal and is automatically created when the Mission.lua file is included. +function MISSIONSCHEDULER.Scheduler() + + + -- loop through the missions in the TransportTasks + for MissionName, MissionData in pairs( MISSIONSCHEDULER.Missions ) do + + local Mission = MissionData -- #MISSION + + if not Mission:IsCompleted() then + + -- This flag will monitor if for this mission, there are clients alive. If this flag is still false at the end of the loop, the mission status will be set to Pending (if not Failed or Completed). + local ClientsAlive = false + + for ClientID, ClientData in pairs( Mission._Clients ) do + + local Client = ClientData -- Wrapper.Client#CLIENT + + if Client:IsAlive() then + + -- There is at least one Client that is alive... So the Mission status is set to Ongoing. + ClientsAlive = true + + -- If this Client was not registered as Alive before: + -- 1. We register the Client as Alive. + -- 2. We initialize the Client Tasks and make a link to the original Mission Task. + -- 3. We initialize the Cargos. + -- 4. We flag the Mission as Ongoing. + if not Client.ClientAlive then + Client.ClientAlive = true + Client.ClientBriefingShown = false + for TaskNumber, Task in pairs( Mission._Tasks ) do + -- Note that this a deepCopy. Each client must have their own Tasks with own Stages!!! + Client._Tasks[TaskNumber] = routines.utils.deepCopy( Mission._Tasks[TaskNumber] ) + -- Each MissionTask must point to the original Mission. + Client._Tasks[TaskNumber].MissionTask = Mission._Tasks[TaskNumber] + Client._Tasks[TaskNumber].Cargos = Mission._Tasks[TaskNumber].Cargos + Client._Tasks[TaskNumber].LandingZones = Mission._Tasks[TaskNumber].LandingZones + end + + Mission:Ongoing() + end + + + -- For each Client, check for each Task the state and evolve the mission. + -- This flag will indicate if the Task of the Client is Complete. + local TaskComplete = false + + for TaskNumber, Task in pairs( Client._Tasks ) do + + if not Task.Stage then + Task:SetStage( 1 ) + end + + + local TransportTime = timer.getTime() + + if not Task:IsDone() then + + if Task:Goal() then + Task:ShowGoalProgress( Mission, Client ) + end + + --env.info( 'Scheduler: Mission = ' .. Mission.Name .. ' / Client = ' .. Client.ClientName .. ' / Task = ' .. Task.Name .. ' / Stage = ' .. Task.ActiveStage .. ' - ' .. Task.Stage.Name .. ' - ' .. Task.Stage.StageType ) + + -- Action + if Task:StageExecute() then + Task.Stage:Execute( Mission, Client, Task ) + end + + -- Wait until execution is finished + if Task.ExecuteStage == _TransportExecuteStage.EXECUTING then + Task.Stage:Executing( Mission, Client, Task ) + end + + -- Validate completion or reverse to earlier stage + if Task.Time + Task.Stage.WaitTime <= TransportTime then + Task:SetStage( Task.Stage:Validate( Mission, Client, Task ) ) + end + + if Task:IsDone() then + --env.info( 'Scheduler: Mission '.. Mission.Name .. ' Task ' .. Task.Name .. ' Stage ' .. Task.Stage.Name .. ' done. TaskComplete = ' .. string.format ( "%s", TaskComplete and "true" or "false" ) ) + TaskComplete = true -- when a task is not yet completed, a mission cannot be completed + + else + -- break only if this task is not yet done, so that future task are not yet activated. + TaskComplete = false -- when a task is not yet completed, a mission cannot be completed + --env.info( 'Scheduler: Mission "'.. Mission.Name .. '" Task "' .. Task.Name .. '" Stage "' .. Task.Stage.Name .. '" break. TaskComplete = ' .. string.format ( "%s", TaskComplete and "true" or "false" ) ) + break + end + + if TaskComplete then + + if Mission.GoalFunction ~= nil then + Mission.GoalFunction( Mission, Client ) + end + if MISSIONSCHEDULER.Scoring then + MISSIONSCHEDULER.Scoring:_AddMissionTaskScore( Client:GetClientGroupDCSUnit(), Mission.Name, 25 ) + end + +-- if not Mission:IsCompleted() then +-- end + end + end + end + + local MissionComplete = true + for TaskNumber, Task in pairs( Mission._Tasks ) do + if Task:Goal() then +-- Task:ShowGoalProgress( Mission, Client ) + if Task:IsGoalReached() then + else + MissionComplete = false + end + else + MissionComplete = false -- If there is no goal, the mission should never be ended. The goal status will be set somewhere else. + end + end + + if MissionComplete then + Mission:Completed() + if MISSIONSCHEDULER.Scoring then + MISSIONSCHEDULER.Scoring:_AddMissionScore( Mission.Name, 100 ) + end + else + if TaskComplete then + -- Reset for new tasking of active client + Client.ClientAlive = false -- Reset the client tasks. + end + end + + + else + if Client.ClientAlive then + env.info( 'Scheduler: Client "' .. Client.ClientName .. '" is inactive.' ) + Client.ClientAlive = false + + -- This is tricky. If we sanitize Client._Tasks before sanitizing Client._Tasks[TaskNumber].MissionTask, then the original MissionTask will be sanitized, and will be lost within the garbage collector. + -- So first sanitize Client._Tasks[TaskNumber].MissionTask, after that, sanitize only the whole _Tasks structure... + --Client._Tasks[TaskNumber].MissionTask = nil + --Client._Tasks = nil + end + end + end + + -- If all Clients of this Mission are not activated, then the Mission status needs to be put back into Pending status. + -- But only if the Mission was Ongoing. In case the Mission is Completed or Failed, the Mission status may not be changed. In these cases, this will be the last run of this Mission in the Scheduler. + if ClientsAlive == false then + if Mission:IsOngoing() then + -- Mission status back to pending... + Mission:Pending() + end + end + end + + Mission:StatusToClients() + + if Mission:ReportTrigger() then + Mission:ReportToAll() + end + end + + return true +end + +--- Start the MISSIONSCHEDULER. +function MISSIONSCHEDULER.Start() + if MISSIONSCHEDULER ~= nil then + --MISSIONSCHEDULER.SchedulerId = routines.scheduleFunction( MISSIONSCHEDULER.Scheduler, { }, 0, 2 ) + MISSIONSCHEDULER.SchedulerId = SCHEDULER:New( nil, MISSIONSCHEDULER.Scheduler, { }, 0, 2 ) + end +end + +--- Stop the MISSIONSCHEDULER. +function MISSIONSCHEDULER.Stop() + if MISSIONSCHEDULER.SchedulerId then + routines.removeFunction(MISSIONSCHEDULER.SchedulerId) + MISSIONSCHEDULER.SchedulerId = nil + end +end + +--- This is the main MISSION declaration method. Each Mission is like the master or a Mission orchestration between, Clients, Tasks, Stages etc. +-- @param Mission is the MISSION object instantiated by @{MISSION:New}. +-- @return MISSION +-- @usage +-- -- Declare a mission. +-- Mission = MISSION:New( 'Russia Transport Troops SA-6', +-- 'Operational', +-- 'Transport troops from the control center to one of the SA-6 SAM sites to activate their operation.', +-- 'Russia' ) +-- MISSIONSCHEDULER:AddMission( Mission ) +function MISSIONSCHEDULER.AddMission( Mission ) + MISSIONSCHEDULER.Missions[Mission.Name] = Mission + MISSIONSCHEDULER.MissionCount = MISSIONSCHEDULER.MissionCount + 1 + -- Add an overall AI Client for the AI tasks... This AI Client will facilitate the Events in the background for each Task. + --MissionAdd:AddClient( CLIENT:Register( 'AI' ) ) + + return Mission +end + +--- Remove a MISSION from the MISSIONSCHEDULER. +-- @param MissionName is the name of the MISSION given at declaration using @{AddMission}. +-- @usage +-- -- Declare a mission. +-- Mission = MISSION:New( 'Russia Transport Troops SA-6', +-- 'Operational', +-- 'Transport troops from the control center to one of the SA-6 SAM sites to activate their operation.', +-- 'Russia' ) +-- MISSIONSCHEDULER:AddMission( Mission ) +-- +-- -- Now remove the Mission. +-- MISSIONSCHEDULER:RemoveMission( 'Russia Transport Troops SA-6' ) +function MISSIONSCHEDULER.RemoveMission( MissionName ) + MISSIONSCHEDULER.Missions[MissionName] = nil + MISSIONSCHEDULER.MissionCount = MISSIONSCHEDULER.MissionCount - 1 +end + +--- Find a MISSION within the MISSIONSCHEDULER. +-- @param MissionName is the name of the MISSION given at declaration using @{AddMission}. +-- @return MISSION +-- @usage +-- -- Declare a mission. +-- Mission = MISSION:New( 'Russia Transport Troops SA-6', +-- 'Operational', +-- 'Transport troops from the control center to one of the SA-6 SAM sites to activate their operation.', +-- 'Russia' ) +-- MISSIONSCHEDULER:AddMission( Mission ) +-- +-- -- Now find the Mission. +-- MissionFind = MISSIONSCHEDULER:FindMission( 'Russia Transport Troops SA-6' ) +function MISSIONSCHEDULER.FindMission( MissionName ) + return MISSIONSCHEDULER.Missions[MissionName] +end + +-- Internal function used by the MISSIONSCHEDULER menu. +function MISSIONSCHEDULER.ReportMissionsShow( ) + for MissionName, Mission in pairs( MISSIONSCHEDULER.Missions ) do + Mission.MissionReportShow = true + Mission.MissionReportFlash = false + end +end + +-- Internal function used by the MISSIONSCHEDULER menu. +function MISSIONSCHEDULER.ReportMissionsFlash( TimeInterval ) + local Count = 0 + for MissionName, Mission in pairs( MISSIONSCHEDULER.Missions ) do + Mission.MissionReportShow = false + Mission.MissionReportFlash = true + Mission.MissionReportTrigger = timer.getTime() + Count * TimeInterval + Mission.MissionTimeInterval = MISSIONSCHEDULER.MissionCount * TimeInterval + env.info( "TimeInterval = " .. Mission.MissionTimeInterval ) + Count = Count + 1 + end +end + +-- Internal function used by the MISSIONSCHEDULER menu. +function MISSIONSCHEDULER.ReportMissionsHide( Prm ) + for MissionName, Mission in pairs( MISSIONSCHEDULER.Missions ) do + Mission.MissionReportShow = false + Mission.MissionReportFlash = false + end +end + +--- Enables a MENU option in the communications menu under F10 to control the status of the active missions. +-- This function should be called only once when starting the MISSIONSCHEDULER. +function MISSIONSCHEDULER.ReportMenu() + local ReportMenu = SUBMENU:New( 'Status' ) + local ReportMenuShow = COMMANDMENU:New( 'Show Report Missions', ReportMenu, MISSIONSCHEDULER.ReportMissionsShow, 0 ) + local ReportMenuFlash = COMMANDMENU:New('Flash Report Missions', ReportMenu, MISSIONSCHEDULER.ReportMissionsFlash, 120 ) + local ReportMenuHide = COMMANDMENU:New( 'Hide Report Missions', ReportMenu, MISSIONSCHEDULER.ReportMissionsHide, 0 ) +end + +--- Show the remaining mission time. +function MISSIONSCHEDULER:TimeShow() + self.TimeIntervalCount = self.TimeIntervalCount + 1 + if self.TimeIntervalCount >= self.TimeTriggerShow then + local TimeMsg = string.format("%00d", ( self.TimeSeconds / 60 ) - ( timer.getTime() / 60 )) .. ' minutes left until mission reload.' + MESSAGE:New( TimeMsg, self.TimeShow, "Mission time" ):ToAll() + self.TimeIntervalCount = 0 + end +end + +function MISSIONSCHEDULER:Time( TimeSeconds, TimeIntervalShow, TimeShow ) + + self.TimeIntervalCount = 0 + self.TimeSeconds = TimeSeconds + self.TimeIntervalShow = TimeIntervalShow + self.TimeShow = TimeShow +end + +--- Adds a mission scoring to the game. +function MISSIONSCHEDULER:Scoring( Scoring ) + + self.Scoring = Scoring +end + +--- This module contains the TASK class. +-- +-- 1) @{#TASK} class, extends @{Core.Base#BASE} +-- ============================================ +-- 1.1) The @{#TASK} class implements the methods for task orchestration within MOOSE. +-- ---------------------------------------------------------------------------------------- +-- The class provides a couple of methods to: +-- +-- * @{#TASK.AssignToGroup}():Assign a task to a group (of players). +-- * @{#TASK.AddProcess}():Add a @{Process} to a task. +-- * @{#TASK.RemoveProcesses}():Remove a running @{Process} from a running task. +-- * @{#TASK.SetStateMachine}():Set a @{Fsm} to a task. +-- * @{#TASK.RemoveStateMachine}():Remove @{Fsm} from a task. +-- * @{#TASK.HasStateMachine}():Enquire if the task has a @{Fsm} +-- * @{#TASK.AssignToUnit}(): Assign a task to a unit. (Needs to be implemented in the derived classes from @{#TASK}. +-- * @{#TASK.UnAssignFromUnit}(): Unassign the task from a unit. +-- +-- 1.2) Set and enquire task status (beyond the task state machine processing). +-- ---------------------------------------------------------------------------- +-- A task needs to implement as a minimum the following task states: +-- +-- * **Success**: Expresses the successful execution and finalization of the task. +-- * **Failed**: Expresses the failure of a task. +-- * **Planned**: Expresses that the task is created, but not yet in execution and is not assigned yet. +-- * **Assigned**: Expresses that the task is assigned to a Group of players, and that the task is in execution mode. +-- +-- A task may also implement the following task states: +-- +-- * **Rejected**: Expresses that the task is rejected by a player, who was requested to accept the task. +-- * **Cancelled**: Expresses that the task is cancelled by HQ or through a logical situation where a cancellation of the task is required. +-- +-- A task can implement more statusses than the ones outlined above. Please consult the documentation of the specific tasks to understand the different status modelled. +-- +-- The status of tasks can be set by the methods **State** followed by the task status. An example is `StateAssigned()`. +-- The status of tasks can be enquired by the methods **IsState** followed by the task status name. An example is `if IsStateAssigned() then`. +-- +-- 1.3) Add scoring when reaching a certain task status: +-- ----------------------------------------------------- +-- Upon reaching a certain task status in a task, additional scoring can be given. If the Mission has a scoring system attached, the scores will be added to the mission scoring. +-- Use the method @{#TASK.AddScore}() to add scores when a status is reached. +-- +-- 1.4) Task briefing: +-- ------------------- +-- A task briefing can be given that is shown to the player when he is assigned to the task. +-- +-- === +-- +-- ### Authors: FlightControl - Design and Programming +-- +-- @module Task + +--- The TASK class +-- @type TASK +-- @field Core.Scheduler#SCHEDULER TaskScheduler +-- @field Tasking.Mission#MISSION Mission +-- @field Core.Set#SET_GROUP SetGroup The Set of Groups assigned to the Task +-- @field Core.Fsm#FSM_PROCESS FsmTemplate +-- @field Tasking.Mission#MISSION Mission +-- @field Tasking.CommandCenter#COMMANDCENTER CommandCenter +-- @extends Core.Fsm#FSM_TASK +TASK = { + ClassName = "TASK", + TaskScheduler = nil, + ProcessClasses = {}, -- The container of the Process classes that will be used to create and assign new processes for the task to ProcessUnits. + Processes = {}, -- The container of actual process objects instantiated and assigned to ProcessUnits. + Players = nil, + Scores = {}, + Menu = {}, + SetGroup = nil, + FsmTemplate = nil, + Mission = nil, + CommandCenter = nil, +} + +--- FSM PlayerAborted event handler prototype for TASK. +-- @function [parent=#TASK] OnAfterPlayerAborted +-- @param #TASK self +-- @param Wrapper.Unit#UNIT PlayerUnit The Unit of the Player when he went back to spectators or left the mission. +-- @param #string PlayerName The name of the Player. + +--- FSM PlayerCrashed event handler prototype for TASK. +-- @function [parent=#TASK] OnAfterPlayerCrashed +-- @param #TASK self +-- @param Wrapper.Unit#UNIT PlayerUnit The Unit of the Player when he crashed in the mission. +-- @param #string PlayerName The name of the Player. + +--- FSM PlayerDead event handler prototype for TASK. +-- @function [parent=#TASK] OnAfterPlayerDead +-- @param #TASK self +-- @param Wrapper.Unit#UNIT PlayerUnit The Unit of the Player when he died in the mission. +-- @param #string PlayerName The name of the Player. + +--- FSM Fail synchronous event function for TASK. +-- Use this event to Fail the Task. +-- @function [parent=#TASK] Fail +-- @param #TASK self + +--- FSM Fail asynchronous event function for TASK. +-- Use this event to Fail the Task. +-- @function [parent=#TASK] __Fail +-- @param #TASK self + +--- FSM Abort synchronous event function for TASK. +-- Use this event to Abort the Task. +-- @function [parent=#TASK] Abort +-- @param #TASK self + +--- FSM Abort asynchronous event function for TASK. +-- Use this event to Abort the Task. +-- @function [parent=#TASK] __Abort +-- @param #TASK self + +--- FSM Success synchronous event function for TASK. +-- Use this event to make the Task a Success. +-- @function [parent=#TASK] Success +-- @param #TASK self + +--- FSM Success asynchronous event function for TASK. +-- Use this event to make the Task a Success. +-- @function [parent=#TASK] __Success +-- @param #TASK self + +--- FSM Cancel synchronous event function for TASK. +-- Use this event to Cancel the Task. +-- @function [parent=#TASK] Cancel +-- @param #TASK self + +--- FSM Cancel asynchronous event function for TASK. +-- Use this event to Cancel the Task. +-- @function [parent=#TASK] __Cancel +-- @param #TASK self + +--- FSM Replan synchronous event function for TASK. +-- Use this event to Replan the Task. +-- @function [parent=#TASK] Replan +-- @param #TASK self + +--- FSM Replan asynchronous event function for TASK. +-- Use this event to Replan the Task. +-- @function [parent=#TASK] __Replan +-- @param #TASK self + + +--- Instantiates a new TASK. Should never be used. Interface Class. +-- @param #TASK self +-- @param Tasking.Mission#MISSION Mission The mission wherein the Task is registered. +-- @param Core.Set#SET_GROUP SetGroupAssign The set of groups for which the Task can be assigned. +-- @param #string TaskName The name of the Task +-- @param #string TaskType The type of the Task +-- @return #TASK self +function TASK:New( Mission, SetGroupAssign, TaskName, TaskType ) + + local self = BASE:Inherit( self, FSM_TASK:New() ) -- Core.Fsm#FSM_TASK + + self:SetStartState( "Planned" ) + self:AddTransition( "Planned", "Assign", "Assigned" ) + self:AddTransition( "Assigned", "AssignUnit", "Assigned" ) + self:AddTransition( "Assigned", "Success", "Success" ) + self:AddTransition( "Assigned", "Fail", "Failed" ) + self:AddTransition( "Assigned", "Abort", "Aborted" ) + self:AddTransition( "Assigned", "Cancel", "Cancelled" ) + self:AddTransition( "*", "PlayerCrashed", "*" ) + self:AddTransition( "*", "PlayerAborted", "*" ) + self:AddTransition( "*", "PlayerDead", "*" ) + self:AddTransition( { "Failed", "Aborted", "Cancelled" }, "Replan", "Planned" ) + + self:E( "New TASK " .. TaskName ) + + self.Processes = {} + self.Fsm = {} + + self.Mission = Mission + self.CommandCenter = Mission:GetCommandCenter() + + self.SetGroup = SetGroupAssign + + self:SetType( TaskType ) + self:SetName( TaskName ) + self:SetID( Mission:GetNextTaskID( self ) ) -- The Mission orchestrates the task sequences .. + + self.TaskBriefing = "You are invited for the task: " .. self.TaskName .. "." + + self.FsmTemplate = self.FsmTemplate or FSM_PROCESS:New() + + -- Handle the birth of new planes within the assigned set. + + + -- Handle when a player crashes ... + -- The Task is UnAssigned from the Unit. + -- When there is no Unit left running the Task, and all of the Players crashed, the Task goes into Failed ... +-- self:EventOnCrash( +-- --- @param #TASK self +-- -- @param Core.Event#EVENTDATA EventData +-- function( self, EventData ) +-- self:E( "In LeaveUnit" ) +-- self:E( { "State", self:GetState() } ) +-- if self:IsStateAssigned() then +-- local TaskUnit = EventData.IniUnit +-- local TaskGroup = EventData.IniUnit:GetGroup() +-- self:E( self.SetGroup:IsIncludeObject( TaskGroup ) ) +-- if self.SetGroup:IsIncludeObject( TaskGroup ) then +-- self:UnAssignFromUnit( TaskUnit ) +-- end +-- self:MessageToGroups( TaskUnit:GetPlayerName() .. " crashed!, and has aborted Task " .. self:GetName() ) +-- end +-- end +-- ) +-- + + Mission:AddTask( self ) + + return self +end + +--- Get the Task FSM Process Template +-- @param #TASK self +-- @return Core.Fsm#FSM_PROCESS +function TASK:GetUnitProcess() + + return self.FsmTemplate +end + +--- Sets the Task FSM Process Template +-- @param #TASK self +-- @param Core.Fsm#FSM_PROCESS +function TASK:SetUnitProcess( FsmTemplate ) + + self.FsmTemplate = FsmTemplate +end + +--- Add a PlayerUnit to join the Task. +-- For each Group within the Task, the Unit is check if it can join the Task. +-- If the Unit was not part of the Task, false is returned. +-- If the Unit is part of the Task, true is returned. +-- @param #TASK self +-- @param Wrapper.Unit#UNIT PlayerUnit The CLIENT or UNIT of the Player joining the Mission. +-- @return #boolean true if Unit is part of the Task. +function TASK:JoinUnit( PlayerUnit ) + self:F( { PlayerUnit = PlayerUnit } ) + + local PlayerUnitAdded = false + + local PlayerGroups = self:GetGroups() + local PlayerGroup = PlayerUnit:GetGroup() + + -- Is the PlayerGroup part of the PlayerGroups? + if PlayerGroups:IsIncludeObject( PlayerGroup ) then + + -- Check if the PlayerGroup is already assigned to the Task. If yes, the PlayerGroup is added to the Task. + -- If the PlayerGroup is not assigned to the Task, the menu needs to be set. In that case, the PlayerUnit will become the GroupPlayer leader. + if self:IsStatePlanned() or self:IsStateReplanned() then + self:SetMenuForGroup( PlayerGroup ) + self:MessageToGroups( PlayerUnit:GetPlayerName() .. " is planning to join Task " .. self:GetName() ) + end + if self:IsStateAssigned() then + local IsAssignedToGroup = self:IsAssignedToGroup( PlayerGroup ) + self:E( { IsAssignedToGroup = IsAssignedToGroup } ) + if IsAssignedToGroup then + self:AssignToUnit( PlayerUnit ) + self:MessageToGroups( PlayerUnit:GetPlayerName() .. " joined Task " .. self:GetName() ) + end + end + end + + return PlayerUnitAdded +end + +--- Abort a PlayerUnit from a Task. +-- If the Unit was not part of the Task, false is returned. +-- If the Unit is part of the Task, true is returned. +-- @param #TASK self +-- @param Wrapper.Unit#UNIT PlayerUnit The CLIENT or UNIT of the Player aborting the Task. +-- @return #boolean true if Unit is part of the Task. +function TASK:AbortUnit( PlayerUnit ) + self:F( { PlayerUnit = PlayerUnit } ) + + local PlayerUnitAborted = false + + local PlayerGroups = self:GetGroups() + local PlayerGroup = PlayerUnit:GetGroup() + + -- Is the PlayerGroup part of the PlayerGroups? + if PlayerGroups:IsIncludeObject( PlayerGroup ) then + + -- Check if the PlayerGroup is already assigned to the Task. If yes, the PlayerGroup is aborted from the Task. + -- If the PlayerUnit was the last unit of the PlayerGroup, the menu needs to be removed from the Group. + if self:IsStateAssigned() then + local IsAssignedToGroup = self:IsAssignedToGroup( PlayerGroup ) + self:E( { IsAssignedToGroup = IsAssignedToGroup } ) + if IsAssignedToGroup then + self:UnAssignFromUnit( PlayerUnit ) + self:MessageToGroups( PlayerUnit:GetPlayerName() .. " aborted Task " .. self:GetName() ) + self:E( { TaskGroup = PlayerGroup:GetName(), GetUnits = PlayerGroup:GetUnits() } ) + if #PlayerGroup:GetUnits() == 1 then + PlayerGroup:SetState( PlayerGroup, "Assigned", nil ) + self:RemoveMenuForGroup( PlayerGroup ) + end + self:PlayerAborted( PlayerUnit ) + end + end + end + + return PlayerUnitAborted +end + +--- A PlayerUnit crashed in a Task. Abort the Player. +-- If the Unit was not part of the Task, false is returned. +-- If the Unit is part of the Task, true is returned. +-- @param #TASK self +-- @param Wrapper.Unit#UNIT PlayerUnit The CLIENT or UNIT of the Player aborting the Task. +-- @return #boolean true if Unit is part of the Task. +function TASK:CrashUnit( PlayerUnit ) + self:F( { PlayerUnit = PlayerUnit } ) + + local PlayerUnitCrashed = false + + local PlayerGroups = self:GetGroups() + local PlayerGroup = PlayerUnit:GetGroup() + + -- Is the PlayerGroup part of the PlayerGroups? + if PlayerGroups:IsIncludeObject( PlayerGroup ) then + + -- Check if the PlayerGroup is already assigned to the Task. If yes, the PlayerGroup is aborted from the Task. + -- If the PlayerUnit was the last unit of the PlayerGroup, the menu needs to be removed from the Group. + if self:IsStateAssigned() then + local IsAssignedToGroup = self:IsAssignedToGroup( PlayerGroup ) + self:E( { IsAssignedToGroup = IsAssignedToGroup } ) + if IsAssignedToGroup then + self:UnAssignFromUnit( PlayerUnit ) + self:MessageToGroups( PlayerUnit:GetPlayerName() .. " crashed in Task " .. self:GetName() ) + self:E( { TaskGroup = PlayerGroup:GetName(), GetUnits = PlayerGroup:GetUnits() } ) + if #PlayerGroup:GetUnits() == 1 then + PlayerGroup:SetState( PlayerGroup, "Assigned", nil ) + self:RemoveMenuForGroup( PlayerGroup ) + end + self:PlayerCrashed( PlayerUnit ) + end + end + end + + return PlayerUnitCrashed +end + + + +--- Gets the Mission to where the TASK belongs. +-- @param #TASK self +-- @return Tasking.Mission#MISSION +function TASK:GetMission() + + return self.Mission +end + + +--- Gets the SET_GROUP assigned to the TASK. +-- @param #TASK self +-- @return Core.Set#SET_GROUP +function TASK:GetGroups() + return self.SetGroup +end + + + +--- Assign the @{Task}to a @{Group}. +-- @param #TASK self +-- @param Wrapper.Group#GROUP TaskGroup +-- @return #TASK +function TASK:AssignToGroup( TaskGroup ) + self:F2( TaskGroup:GetName() ) + + local TaskGroupName = TaskGroup:GetName() + + TaskGroup:SetState( TaskGroup, "Assigned", self ) + + self:RemoveMenuForGroup( TaskGroup ) + self:SetAssignedMenuForGroup( TaskGroup ) + + local TaskUnits = TaskGroup:GetUnits() + for UnitID, UnitData in pairs( TaskUnits ) do + local TaskUnit = UnitData -- Wrapper.Unit#UNIT + local PlayerName = TaskUnit:GetPlayerName() + self:E(PlayerName) + if PlayerName ~= nil or PlayerName ~= "" then + self:AssignToUnit( TaskUnit ) + end + end + + return self +end + +--- +-- @param #TASK self +-- @param Wrapper.Group#GROUP FindGroup +-- @return #boolean +function TASK:HasGroup( FindGroup ) + + self:GetGroups():FilterOnce() -- Ensure that the filter is updated. + return self:GetGroups():IsIncludeObject( FindGroup ) + +end + +--- Assign the @{Task} to an alive @{Unit}. +-- @param #TASK self +-- @param Wrapper.Unit#UNIT TaskUnit +-- @return #TASK self +function TASK:AssignToUnit( TaskUnit ) + self:F( TaskUnit:GetName() ) + + local FsmTemplate = self:GetUnitProcess() + + -- Assign a new FsmUnit to TaskUnit. + local FsmUnit = self:SetStateMachine( TaskUnit, FsmTemplate:Copy( TaskUnit, self ) ) -- Core.Fsm#FSM_PROCESS + self:E({"Address FsmUnit", tostring( FsmUnit ) } ) + + FsmUnit:SetStartState( "Planned" ) + FsmUnit:Accept() -- Each Task needs to start with an Accept event to start the flow. + + return self +end + +--- UnAssign the @{Task} from an alive @{Unit}. +-- @param #TASK self +-- @param Wrapper.Unit#UNIT TaskUnit +-- @return #TASK self +function TASK:UnAssignFromUnit( TaskUnit ) + self:F( TaskUnit ) + + self:RemoveStateMachine( TaskUnit ) + + return self +end + +--- Send a message of the @{Task} to the assigned @{Group}s. +-- @param #TASK self +function TASK:MessageToGroups( Message ) + self:F( { Message = Message } ) + + local Mission = self:GetMission() + local CC = Mission:GetCommandCenter() + + for TaskGroupName, TaskGroup in pairs( self.SetGroup:GetSet() ) do + CC:MessageToGroup( Message, TaskGroup ) + end +end + + +--- Send the briefng message of the @{Task} to the assigned @{Group}s. +-- @param #TASK self +function TASK:SendBriefingToAssignedGroups() + self:F2() + + for TaskGroupName, TaskGroup in pairs( self.SetGroup:GetSet() ) do + + if self:IsAssignedToGroup( TaskGroup ) then + TaskGroup:Message( self.TaskBriefing, 60 ) + end + end +end + + +--- Assign the @{Task} from the @{Group}s. +-- @param #TASK self +function TASK:UnAssignFromGroups() + self:F2() + + for TaskGroupName, TaskGroup in pairs( self.SetGroup:GetSet() ) do + + TaskGroup:SetState( TaskGroup, "Assigned", nil ) + + self:RemoveMenuForGroup( TaskGroup ) + + local TaskUnits = TaskGroup:GetUnits() + for UnitID, UnitData in pairs( TaskUnits ) do + local TaskUnit = UnitData -- Wrapper.Unit#UNIT + local PlayerName = TaskUnit:GetPlayerName() + if PlayerName ~= nil or PlayerName ~= "" then + self:UnAssignFromUnit( TaskUnit ) + end + end + end +end + +--- Returns if the @{Task} is assigned to the Group. +-- @param #TASK self +-- @param Wrapper.Group#GROUP TaskGroup +-- @return #boolean +function TASK:IsAssignedToGroup( TaskGroup ) + + local TaskGroupName = TaskGroup:GetName() + + if self:IsStateAssigned() then + if TaskGroup:GetState( TaskGroup, "Assigned" ) == self then + return true + end + end + + return false +end + +--- Returns if the @{Task} has still alive and assigned Units. +-- @param #TASK self +-- @return #boolean +function TASK:HasAliveUnits() + self:F() + + for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do + if self:IsStateAssigned() then + if self:IsAssignedToGroup( TaskGroup ) then + for TaskUnitID, TaskUnit in pairs( TaskGroup:GetUnits() ) do + if TaskUnit:IsAlive() then + self:T( { HasAliveUnits = true } ) + return true + end + end + end + end + end + + self:T( { HasAliveUnits = false } ) + return false +end + +--- Set the menu options of the @{Task} to all the groups in the SetGroup. +-- @param #TASK self +function TASK:SetMenu() + self:F() + + self.SetGroup:Flush() + for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do + self:RemoveMenuForGroup( TaskGroup ) + if self:IsStatePlanned() or self:IsStateReplanned() then + self:SetMenuForGroup( TaskGroup ) + end + end +end + + +--- Remove the menu options of the @{Task} to all the groups in the SetGroup. +-- @param #TASK self +-- @return #TASK self +function TASK:RemoveMenu() + + for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do + self:RemoveMenuForGroup( TaskGroup ) + end +end + + +--- Set the Menu for a Group +-- @param #TASK self +function TASK:SetMenuForGroup( TaskGroup ) + + if not self:IsAssignedToGroup( TaskGroup ) then + self:SetPlannedMenuForGroup( TaskGroup, self:GetTaskName() ) + else + self:SetAssignedMenuForGroup( TaskGroup ) + end +end + + +--- Set the planned menu option of the @{Task}. +-- @param #TASK self +-- @param Wrapper.Group#GROUP TaskGroup +-- @param #string MenuText The menu text. +-- @return #TASK self +function TASK:SetPlannedMenuForGroup( TaskGroup, MenuText ) + self:E( TaskGroup:GetName() ) + + local Mission = self:GetMission() + local MissionMenu = Mission:GetMissionMenu( TaskGroup ) + + local TaskType = self:GetType() + local TaskTypeMenu = MENU_GROUP:New( TaskGroup, TaskType, MissionMenu ) + local TaskMenu = MENU_GROUP_COMMAND:New( TaskGroup, MenuText, TaskTypeMenu, self.MenuAssignToGroup, { self = self, TaskGroup = TaskGroup } ) + + return self +end + +--- Set the assigned menu options of the @{Task}. +-- @param #TASK self +-- @param Wrapper.Group#GROUP TaskGroup +-- @return #TASK self +function TASK:SetAssignedMenuForGroup( TaskGroup ) + self:E( TaskGroup:GetName() ) + + local Mission = self:GetMission() + local MissionMenu = Mission:GetMissionMenu( TaskGroup ) + + self:E( { MissionMenu = MissionMenu } ) + + local TaskTypeMenu = MENU_GROUP_COMMAND:New( TaskGroup, "Task Status", MissionMenu, self.MenuTaskStatus, { self = self, TaskGroup = TaskGroup } ) + local TaskMenu = MENU_GROUP_COMMAND:New( TaskGroup, "Abort Task", MissionMenu, self.MenuTaskAbort, { self = self, TaskGroup = TaskGroup } ) + + return self +end + +--- Remove the menu option of the @{Task} for a @{Group}. +-- @param #TASK self +-- @param Wrapper.Group#GROUP TaskGroup +-- @return #TASK self +function TASK:RemoveMenuForGroup( TaskGroup ) + + local Mission = self:GetMission() + local MissionName = Mission:GetName() + + local MissionMenu = Mission:GetMissionMenu( TaskGroup ) + MissionMenu:Remove() +end + +function TASK.MenuAssignToGroup( MenuParam ) + + local self = MenuParam.self + local TaskGroup = MenuParam.TaskGroup + + self:E( "Assigned menu selected") + + self:AssignToGroup( TaskGroup ) +end + +function TASK.MenuTaskStatus( MenuParam ) + + local self = MenuParam.self + local TaskGroup = MenuParam.TaskGroup + + --self:AssignToGroup( TaskGroup ) +end + +function TASK.MenuTaskAbort( MenuParam ) + + local self = MenuParam.self + local TaskGroup = MenuParam.TaskGroup + + --self:AssignToGroup( TaskGroup ) +end + + + +--- Returns the @{Task} name. +-- @param #TASK self +-- @return #string TaskName +function TASK:GetTaskName() + return self.TaskName +end + + + + +--- Get the default or currently assigned @{Process} template with key ProcessName. +-- @param #TASK self +-- @param #string ProcessName +-- @return Core.Fsm#FSM_PROCESS +function TASK:GetProcessTemplate( ProcessName ) + + local ProcessTemplate = self.ProcessClasses[ProcessName] + + return ProcessTemplate +end + + + +-- TODO: Obscolete? +--- Fail processes from @{Task} with key @{Unit} +-- @param #TASK self +-- @param #string TaskUnitName +-- @return #TASK self +function TASK:FailProcesses( TaskUnitName ) + + for ProcessID, ProcessData in pairs( self.Processes[TaskUnitName] ) do + local Process = ProcessData + Process.Fsm:Fail() + end +end + +--- Add a FiniteStateMachine to @{Task} with key Task@{Unit} +-- @param #TASK self +-- @param Wrapper.Unit#UNIT TaskUnit +-- @return #TASK self +function TASK:SetStateMachine( TaskUnit, Fsm ) + self:F( { TaskUnit, self.Fsm[TaskUnit] ~= nil } ) + + self.Fsm[TaskUnit] = Fsm + + return Fsm +end + +--- Remove FiniteStateMachines from @{Task} with key Task@{Unit} +-- @param #TASK self +-- @param Wrapper.Unit#UNIT TaskUnit +-- @return #TASK self +function TASK:RemoveStateMachine( TaskUnit ) + self:F( { TaskUnit, self.Fsm[TaskUnit] ~= nil } ) + + self.Fsm[TaskUnit] = nil + collectgarbage() + self:T( "Garbage Collected, Processes should be finalized now ...") +end + +--- Checks if there is a FiniteStateMachine assigned to Task@{Unit} for @{Task} +-- @param #TASK self +-- @param Wrapper.Unit#UNIT TaskUnit +-- @return #TASK self +function TASK:HasStateMachine( TaskUnit ) + self:F( { TaskUnit, self.Fsm[TaskUnit] ~= nil } ) + + return ( self.Fsm[TaskUnit] ~= nil ) +end + + +--- Gets the Scoring of the task +-- @param #TASK self +-- @return Functional.Scoring#SCORING Scoring +function TASK:GetScoring() + return self.Mission:GetScoring() +end + + +--- Gets the Task Index, which is a combination of the Task type, the Task name. +-- @param #TASK self +-- @return #string The Task ID +function TASK:GetTaskIndex() + + local TaskType = self:GetType() + local TaskName = self:GetName() + + return TaskType .. "." .. TaskName +end + +--- Sets the Name of the Task +-- @param #TASK self +-- @param #string TaskName +function TASK:SetName( TaskName ) + self.TaskName = TaskName +end + +--- Gets the Name of the Task +-- @param #TASK self +-- @return #string The Task Name +function TASK:GetName() + return self.TaskName +end + +--- Sets the Type of the Task +-- @param #TASK self +-- @param #string TaskType +function TASK:SetType( TaskType ) + self.TaskType = TaskType +end + +--- Gets the Type of the Task +-- @param #TASK self +-- @return #string TaskType +function TASK:GetType() + return self.TaskType +end + +--- Sets the ID of the Task +-- @param #TASK self +-- @param #string TaskID +function TASK:SetID( TaskID ) + self.TaskID = TaskID +end + +--- Gets the ID of the Task +-- @param #TASK self +-- @return #string TaskID +function TASK:GetID() + return self.TaskID +end + + +--- Sets a @{Task} to status **Success**. +-- @param #TASK self +function TASK:StateSuccess() + self:SetState( self, "State", "Success" ) + return self +end + +--- Is the @{Task} status **Success**. +-- @param #TASK self +function TASK:IsStateSuccess() + return self:Is( "Success" ) +end + +--- Sets a @{Task} to status **Failed**. +-- @param #TASK self +function TASK:StateFailed() + self:SetState( self, "State", "Failed" ) + return self +end + +--- Is the @{Task} status **Failed**. +-- @param #TASK self +function TASK:IsStateFailed() + return self:Is( "Failed" ) +end + +--- Sets a @{Task} to status **Planned**. +-- @param #TASK self +function TASK:StatePlanned() + self:SetState( self, "State", "Planned" ) + return self +end + +--- Is the @{Task} status **Planned**. +-- @param #TASK self +function TASK:IsStatePlanned() + return self:Is( "Planned" ) +end + +--- Sets a @{Task} to status **Assigned**. +-- @param #TASK self +function TASK:StateAssigned() + self:SetState( self, "State", "Assigned" ) + return self +end + +--- Is the @{Task} status **Assigned**. +-- @param #TASK self +function TASK:IsStateAssigned() + return self:Is( "Assigned" ) +end + +--- Sets a @{Task} to status **Hold**. +-- @param #TASK self +function TASK:StateHold() + self:SetState( self, "State", "Hold" ) + return self +end + +--- Is the @{Task} status **Hold**. +-- @param #TASK self +function TASK:IsStateHold() + return self:Is( "Hold" ) +end + +--- Sets a @{Task} to status **Replanned**. +-- @param #TASK self +function TASK:StateReplanned() + self:SetState( self, "State", "Replanned" ) + return self +end + +--- Is the @{Task} status **Replanned**. +-- @param #TASK self +function TASK:IsStateReplanned() + return self:Is( "Replanned" ) +end + +--- Gets the @{Task} status. +-- @param #TASK self +function TASK:GetStateString() + return self:GetState( self, "State" ) +end + +--- Sets a @{Task} briefing. +-- @param #TASK self +-- @param #string TaskBriefing +-- @return #TASK self +function TASK:SetBriefing( TaskBriefing ) + self.TaskBriefing = TaskBriefing + return self +end + + + + +--- FSM function for a TASK +-- @param #TASK self +-- @param #string Event +-- @param #string From +-- @param #string To +function TASK:onenterAssigned( Event, From, To ) + + self:E("Task Assigned") + + self:MessageToGroups( "Task " .. self:GetName() .. " has been assigned!" ) + self:GetMission():__Start() +end + + +--- FSM function for a TASK +-- @param #TASK self +-- @param #string Event +-- @param #string From +-- @param #string To +function TASK:onenterSuccess( Event, From, To ) + + self:E( "Task Success" ) + + self:MessageToGroups( "Task " .. self:GetName() .. " is successful! Good job!" ) + self:UnAssignFromGroups() + + self:GetMission():__Complete() + +end + + +--- FSM function for a TASK +-- @param #TASK self +-- @param #string Event +-- @param #string From +-- @param #string To +function TASK:onenterAborted( Event, From, To ) + + self:E( "Task Aborted" ) + + self:GetMission():GetCommandCenter():MessageToCoalition( "Task " .. self:GetName() .. " has been aborted! Task may be replanned." ) + + self:UnAssignFromGroups() +end + +--- FSM function for a TASK +-- @param #TASK self +-- @param #string Event +-- @param #string From +-- @param #string To +function TASK:onenterFailed( Event, From, To ) + + self:E( "Task Failed" ) + + self:GetMission():GetCommandCenter():MessageToCoalition( "Task " .. self:GetName() .. " has failed!" ) + + self:UnAssignFromGroups() +end + +--- FSM function for a TASK +-- @param #TASK self +-- @param #string Event +-- @param #string From +-- @param #string To +function TASK:onstatechange( Event, From, To ) + + if self:IsTrace() then + MESSAGE:New( "@ Task " .. self.TaskName .. " : " .. Event .. " changed to state " .. To, 2 ):ToAll() + end + + if self.Scores[To] then + local Scoring = self:GetScoring() + if Scoring then + self:E( { self.Scores[To].ScoreText, self.Scores[To].Score } ) + Scoring:_AddMissionScore( self.Mission, self.Scores[To].ScoreText, self.Scores[To].Score ) + end + end + +end + +do -- Reporting + +--- Create a summary report of the Task. +-- List the Task Name and Status +-- @param #TASK self +-- @return #string +function TASK:ReportSummary() + + local Report = REPORT:New() + + -- List the name of the Task. + local Name = self:GetName() + + -- Determine the status of the Task. + local State = self:GetState() + + Report:Add( "Task " .. Name .. " - State '" .. State ) + + return Report:Text() +end + + +--- Create a detailed report of the Task. +-- List the Task Status, and the Players assigned to the Task. +-- @param #TASK self +-- @return #string +function TASK:ReportDetails() + + local Report = REPORT:New() + + -- List the name of the Task. + local Name = self:GetName() + + -- Determine the status of the Task. + local State = self:GetState() + + + -- Loop each Unit active in the Task, and find Player Names. + local PlayerNames = {} + for PlayerGroupID, PlayerGroup in pairs( self:GetGroups():GetSet() ) do + local Player = PlayerGroup -- Wrapper.Group#GROUP + for PlayerUnitID, PlayerUnit in pairs( PlayerGroup:GetUnits() ) do + local PlayerUnit = PlayerUnit -- Wrapper.Unit#UNIT + if PlayerUnit and PlayerUnit:IsAlive() then + local PlayerName = PlayerUnit:GetPlayerName() + PlayerNames[#PlayerNames+1] = PlayerName + end + end + PlayerNameText = table.concat( PlayerNames, ", " ) + Report:Add( "Task " .. Name .. " - State '" .. State .. "' - Players " .. PlayerNameText ) + end + + return Report:Text() +end + + +end -- Reporting +-- This module contains the DETECTION_MANAGER class and derived classes. +-- +-- === +-- +-- 1) @{Tasking.DetectionManager#DETECTION_MANAGER} class, extends @{Core.Base#BASE} -- ==================================================================== --- The @{DetectionManager#DETECTION_MANAGER} class defines the core functions to report detected objects to groups. +-- The @{Tasking.DetectionManager#DETECTION_MANAGER} class defines the core functions to report detected objects to groups. -- Reportings can be done in several manners, and it is up to the derived classes if DETECTION_MANAGER to model the reporting behaviour. -- -- 1.1) DETECTION_MANAGER constructor: -- ----------------------------------- --- * @{DetectionManager#DETECTION_MANAGER.New}(): Create a new DETECTION_MANAGER instance. +-- * @{Tasking.DetectionManager#DETECTION_MANAGER.New}(): Create a new DETECTION_MANAGER instance. -- -- 1.2) DETECTION_MANAGER reporting: -- --------------------------------- --- Derived DETECTION_MANAGER classes will reports detected units using the method @{DetectionManager#DETECTION_MANAGER.ReportDetected}(). This method implements polymorphic behaviour. +-- Derived DETECTION_MANAGER classes will reports detected units using the method @{Tasking.DetectionManager#DETECTION_MANAGER.ReportDetected}(). This method implements polymorphic behaviour. -- --- The time interval in seconds of the reporting can be changed using the methods @{DetectionManager#DETECTION_MANAGER.SetReportInterval}(). --- To control how long a reporting message is displayed, use @{DetectionManager#DETECTION_MANAGER.SetReportDisplayTime}(). --- Derived classes need to implement the method @{DetectionManager#DETECTION_MANAGER.GetReportDisplayTime}() to use the correct display time for displayed messages during a report. +-- The time interval in seconds of the reporting can be changed using the methods @{Tasking.DetectionManager#DETECTION_MANAGER.SetReportInterval}(). +-- To control how long a reporting message is displayed, use @{Tasking.DetectionManager#DETECTION_MANAGER.SetReportDisplayTime}(). +-- Derived classes need to implement the method @{Tasking.DetectionManager#DETECTION_MANAGER.GetReportDisplayTime}() to use the correct display time for displayed messages during a report. -- --- Reporting can be started and stopped using the methods @{DetectionManager#DETECTION_MANAGER.StartReporting}() and @{DetectionManager#DETECTION_MANAGER.StopReporting}() respectively. --- If an ad-hoc report is requested, use the method @{DetectionManager#DETECTION_MANAGER#ReportNow}(). +-- Reporting can be started and stopped using the methods @{Tasking.DetectionManager#DETECTION_MANAGER.StartReporting}() and @{Tasking.DetectionManager#DETECTION_MANAGER.StopReporting}() respectively. +-- If an ad-hoc report is requested, use the method @{Tasking.DetectionManager#DETECTION_MANAGER#ReportNow}(). -- -- The default reporting interval is every 60 seconds. The reporting messages are displayed 15 seconds. -- -- === -- --- 2) @{DetectionManager#DETECTION_REPORTING} class, extends @{DetectionManager#DETECTION_MANAGER} +-- 2) @{Tasking.DetectionManager#DETECTION_REPORTING} class, extends @{Tasking.DetectionManager#DETECTION_MANAGER} -- ========================================================================================= --- The @{DetectionManager#DETECTION_REPORTING} class implements detected units reporting. Reporting can be controlled using the reporting methods available in the @{DetectionManager#DETECTION_MANAGER} class. +-- The @{Tasking.DetectionManager#DETECTION_REPORTING} class implements detected units reporting. Reporting can be controlled using the reporting methods available in the @{Tasking.DetectionManager#DETECTION_MANAGER} class. -- -- 2.1) DETECTION_REPORTING constructor: -- ------------------------------- --- The @{DetectionManager#DETECTION_REPORTING.New}() method creates a new DETECTION_REPORTING instance. +-- The @{Tasking.DetectionManager#DETECTION_REPORTING.New}() method creates a new DETECTION_REPORTING instance. -- -- === -- @@ -27192,7 +28676,7 @@ do -- DETECTION MANAGER --- DETECTION_MANAGER class. -- @type DETECTION_MANAGER -- @field Set#SET_GROUP SetGroup The groups to which the FAC will report to. - -- @field Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects. + -- @field Functional.Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects. -- @extends Base#BASE DETECTION_MANAGER = { ClassName = "DETECTION_MANAGER", @@ -27203,12 +28687,12 @@ do -- DETECTION MANAGER --- FAC constructor. -- @param #DETECTION_MANAGER self -- @param Set#SET_GROUP SetGroup - -- @param Detection#DETECTION_BASE Detection + -- @param Functional.Detection#DETECTION_BASE Detection -- @return #DETECTION_MANAGER self function DETECTION_MANAGER:New( SetGroup, Detection ) -- Inherits from BASE - local self = BASE:Inherit( self, BASE:New() ) -- Detection#DETECTION_MANAGER + local self = BASE:Inherit( self, BASE:New() ) -- Functional.Detection#DETECTION_MANAGER self.SetGroup = SetGroup self.Detection = Detection @@ -27253,7 +28737,7 @@ do -- DETECTION MANAGER --- Reports the detected items to the @{Set#SET_GROUP}. -- @param #DETECTION_MANAGER self - -- @param Detection#DETECTION_BASE Detection + -- @param Functional.Detection#DETECTION_BASE Detection -- @return #DETECTION_MANAGER self function DETECTION_MANAGER:ReportDetected( Detection ) self:F2() @@ -27276,7 +28760,7 @@ do -- DETECTION MANAGER return self end - --- Report the detected @{Unit#UNIT}s detected within the @{Detection#DETECTION_BASE} object to the @{Set#SET_GROUP}s. + --- Report the detected @{Wrapper.Unit#UNIT}s detected within the @{Functional.Detection#DETECTION_BASE} object to the @{Set#SET_GROUP}s. -- @param #DETECTION_MANAGER self function DETECTION_MANAGER:_FacScheduler( SchedulerName ) self:F2( { SchedulerName } ) @@ -27284,7 +28768,7 @@ do -- DETECTION MANAGER return self:ProcessDetected( self.Detection ) -- self.SetGroup:ForEachGroup( --- --- @param Group#GROUP Group +-- --- @param Wrapper.Group#GROUP Group -- function( Group ) -- if Group:IsAlive() then -- return self:ProcessDetected( self.Detection ) @@ -27303,7 +28787,7 @@ do -- DETECTION_REPORTING --- DETECTION_REPORTING class. -- @type DETECTION_REPORTING -- @field Set#SET_GROUP SetGroup The groups to which the FAC will report to. - -- @field Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects. + -- @field Functional.Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects. -- @extends #DETECTION_MANAGER DETECTION_REPORTING = { ClassName = "DETECTION_REPORTING", @@ -27313,7 +28797,7 @@ do -- DETECTION_REPORTING --- DETECTION_REPORTING constructor. -- @param #DETECTION_REPORTING self -- @param Set#SET_GROUP SetGroup - -- @param Detection#DETECTION_AREAS Detection + -- @param Functional.Detection#DETECTION_AREAS Detection -- @return #DETECTION_REPORTING self function DETECTION_REPORTING:New( SetGroup, Detection ) @@ -27326,7 +28810,7 @@ do -- DETECTION_REPORTING --- Creates a string of the detected items in a @{Detection}. -- @param #DETECTION_MANAGER self - -- @param Set#SET_UNIT DetectedSet The detected Set created by the @{Detection#DETECTION_BASE} object. + -- @param Set#SET_UNIT DetectedSet The detected Set created by the @{Functional.Detection#DETECTION_BASE} object. -- @return #DETECTION_MANAGER self function DETECTION_REPORTING:GetDetectedItemsText( DetectedSet ) self:F2() @@ -27335,7 +28819,7 @@ do -- DETECTION_REPORTING local UnitTypes = {} for DetectedUnitID, DetectedUnitData in pairs( DetectedSet:GetSet() ) do - local DetectedUnit = DetectedUnitData -- Unit#UNIT + local DetectedUnit = DetectedUnitData -- Wrapper.Unit#UNIT if DetectedUnit:IsAlive() then local UnitType = DetectedUnit:GetTypeName() @@ -27358,8 +28842,8 @@ do -- DETECTION_REPORTING --- Reports the detected items to the @{Set#SET_GROUP}. -- @param #DETECTION_REPORTING self - -- @param Group#GROUP Group The @{Group} object to where the report needs to go. - -- @param Detection#DETECTION_AREAS Detection The detection created by the @{Detection#DETECTION_BASE} object. + -- @param Wrapper.Group#GROUP Group The @{Group} object to where the report needs to go. + -- @param Functional.Detection#DETECTION_AREAS Detection The detection created by the @{Functional.Detection#DETECTION_BASE} object. -- @return #boolean Return true if you want the reporting to continue... false will cancel the reporting loop. function DETECTION_REPORTING:ProcessDetected( Group, Detection ) self:F2( Group ) @@ -27367,7 +28851,7 @@ do -- DETECTION_REPORTING self:E( Group ) local DetectedMsg = {} for DetectedAreaID, DetectedAreaData in pairs( Detection:GetDetectedAreas() ) do - local DetectedArea = DetectedAreaData -- Detection#DETECTION_AREAS.DetectedArea + local DetectedArea = DetectedAreaData -- Functional.Detection#DETECTION_AREAS.DetectedArea DetectedMsg[#DetectedMsg+1] = " - Group #" .. DetectedAreaID .. ": " .. self:GetDetectedItemsText( DetectedArea.Set ) end local FACGroup = Detection:GetDetectionGroups() @@ -27383,10 +28867,10 @@ do -- DETECTION_DISPATCHER --- DETECTION_DISPATCHER class. -- @type DETECTION_DISPATCHER -- @field Set#SET_GROUP SetGroup The groups to which the FAC will report to. - -- @field Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects. - -- @field Mission#MISSION Mission - -- @field Group#GROUP CommandCenter - -- @extends DetectionManager#DETECTION_MANAGER + -- @field Functional.Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects. + -- @field Tasking.Mission#MISSION Mission + -- @field Wrapper.Group#GROUP CommandCenter + -- @extends Tasking.DetectionManager#DETECTION_MANAGER DETECTION_DISPATCHER = { ClassName = "DETECTION_DISPATCHER", Mission = nil, @@ -27398,7 +28882,7 @@ do -- DETECTION_DISPATCHER --- DETECTION_DISPATCHER constructor. -- @param #DETECTION_DISPATCHER self -- @param Set#SET_GROUP SetGroup - -- @param Detection#DETECTION_BASE Detection + -- @param Functional.Detection#DETECTION_BASE Detection -- @return #DETECTION_DISPATCHER self function DETECTION_DISPATCHER:New( Mission, CommandCenter, SetGroup, Detection ) @@ -27416,7 +28900,7 @@ do -- DETECTION_DISPATCHER --- Creates a SEAD task when there are targets for it. -- @param #DETECTION_DISPATCHER self - -- @param Detection#DETECTION_AREAS.DetectedArea DetectedArea + -- @param Functional.Detection#DETECTION_AREAS.DetectedArea DetectedArea -- @return Set#SET_UNIT TargetSetUnit: The target set of units. -- @return #nil If there are no targets to be set. function DETECTION_DISPATCHER:EvaluateSEAD( DetectedArea ) @@ -27444,8 +28928,8 @@ do -- DETECTION_DISPATCHER --- Creates a CAS task when there are targets for it. -- @param #DETECTION_DISPATCHER self - -- @param Detection#DETECTION_AREAS.DetectedArea DetectedArea - -- @return Task#TASK_BASE + -- @param Functional.Detection#DETECTION_AREAS.DetectedArea DetectedArea + -- @return Tasking.Task#TASK function DETECTION_DISPATCHER:EvaluateCAS( DetectedArea ) self:F( { DetectedArea.AreaID } ) @@ -27472,8 +28956,8 @@ do -- DETECTION_DISPATCHER --- Creates a BAI task when there are targets for it. -- @param #DETECTION_DISPATCHER self - -- @param Detection#DETECTION_AREAS.DetectedArea DetectedArea - -- @return Task#TASK_BASE + -- @param Functional.Detection#DETECTION_AREAS.DetectedArea DetectedArea + -- @return Tasking.Task#TASK function DETECTION_DISPATCHER:EvaluateBAI( DetectedArea, FriendlyCoalition ) self:F( { DetectedArea.AreaID } ) @@ -27501,14 +28985,15 @@ do -- DETECTION_DISPATCHER --- Evaluates the removal of the Task from the Mission. -- Can only occur when the DetectedArea is Changed AND the state of the Task is "Planned". -- @param #DETECTION_DISPATCHER self - -- @param Mission#MISSION Mission - -- @param Task#TASK_BASE Task - -- @param Detection#DETECTION_AREAS.DetectedArea DetectedArea - -- @return Task#TASK_BASE + -- @param Tasking.Mission#MISSION Mission + -- @param Tasking.Task#TASK Task + -- @param Functional.Detection#DETECTION_AREAS.DetectedArea DetectedArea + -- @return Tasking.Task#TASK function DETECTION_DISPATCHER:EvaluateRemoveTask( Mission, Task, DetectedArea ) if Task then if Task:IsStatePlanned() and DetectedArea.Changed == true then + self:E( "Removing Tasking: " .. Task:GetTaskName() ) Mission:RemoveTaskMenu( Task ) Task = Mission:RemoveTask( Task ) end @@ -27520,7 +29005,7 @@ do -- DETECTION_DISPATCHER --- Assigns tasks in relation to the detected items to the @{Set#SET_GROUP}. -- @param #DETECTION_DISPATCHER self - -- @param Detection#DETECTION_AREAS Detection The detection created by the @{Detection#DETECTION_AREAS} object. + -- @param Functional.Detection#DETECTION_AREAS Detection The detection created by the @{Functional.Detection#DETECTION_AREAS} object. -- @return #boolean Return true if you want the task assigning to continue... false will cancel the loop. function DETECTION_DISPATCHER:ProcessDetected( Detection ) self:F2() @@ -27534,7 +29019,7 @@ do -- DETECTION_DISPATCHER --- First we need to the detected targets. for DetectedAreaID, DetectedAreaData in ipairs( Detection:GetDetectedAreas() ) do - local DetectedArea = DetectedAreaData -- Detection#DETECTION_AREAS.DetectedArea + local DetectedArea = DetectedAreaData -- Functional.Detection#DETECTION_AREAS.DetectedArea local DetectedSet = DetectedArea.Set local DetectedZone = DetectedArea.Zone self:E( { "Targets in DetectedArea", DetectedArea.AreaID, DetectedSet:Count(), tostring( DetectedArea ) } ) @@ -27548,11 +29033,12 @@ do -- DETECTION_DISPATCHER if not SEADTask then local TargetSetUnit = self:EvaluateSEAD( DetectedArea ) -- Returns a SetUnit if there are targets to be SEADed... if TargetSetUnit then - SEADTask = Mission:AddTask( TASK_SEAD:New( Mission, self.SetGroup, "SEAD." .. AreaID, TargetSetUnit , DetectedZone ) ):StatePlanned() + SEADTask = Mission:AddTask( TASK_SEAD:New( Mission, self.SetGroup, "SEAD." .. AreaID, TargetSetUnit , DetectedZone ) ) end end if SEADTask and SEADTask:IsStatePlanned() then - SEADTask:SetPlannedMenu() + self:E( "Planned" ) + --SEADTask:SetPlannedMenu() TaskMsg[#TaskMsg+1] = " - " .. SEADTask:GetStateString() .. " SEAD " .. AreaID .. " - " .. SEADTask.TargetSetUnit:GetUnitTypesText() end @@ -27562,11 +29048,11 @@ do -- DETECTION_DISPATCHER if not CASTask then local TargetSetUnit = self:EvaluateCAS( DetectedArea ) -- Returns a SetUnit if there are targets to be SEADed... if TargetSetUnit then - CASTask = Mission:AddTask( TASK_A2G:New( Mission, self.SetGroup, "CAS." .. AreaID, "CAS", TargetSetUnit , DetectedZone, DetectedArea.NearestFAC ) ):StatePlanned() + CASTask = Mission:AddTask( TASK_A2G:New( Mission, self.SetGroup, "CAS." .. AreaID, "CAS", TargetSetUnit , DetectedZone, DetectedArea.NearestFAC ) ) end end if CASTask and CASTask:IsStatePlanned() then - CASTask:SetPlannedMenu() + --CASTask:SetPlannedMenu() TaskMsg[#TaskMsg+1] = " - " .. CASTask:GetStateString() .. " CAS " .. AreaID .. " - " .. CASTask.TargetSetUnit:GetUnitTypesText() end @@ -27576,11 +29062,11 @@ do -- DETECTION_DISPATCHER if not BAITask then local TargetSetUnit = self:EvaluateBAI( DetectedArea, self.CommandCenter:GetCoalition() ) -- Returns a SetUnit if there are targets to be SEADed... if TargetSetUnit then - BAITask = Mission:AddTask( TASK_A2G:New( Mission, self.SetGroup, "BAI." .. AreaID, "BAI", TargetSetUnit , DetectedZone, DetectedArea.NearestFAC ) ):StatePlanned() + BAITask = Mission:AddTask( TASK_A2G:New( Mission, self.SetGroup, "BAI." .. AreaID, "BAI", TargetSetUnit , DetectedZone, DetectedArea.NearestFAC ) ) end end if BAITask and BAITask:IsStatePlanned() then - BAITask:SetPlannedMenu() + --BAITask:SetPlannedMenu() TaskMsg[#TaskMsg+1] = " - " .. BAITask:GetStateString() .. " BAI " .. AreaID .. " - " .. BAITask.TargetSetUnit:GetUnitTypesText() end @@ -27611,6 +29097,9 @@ do -- DETECTION_DISPATCHER end + -- TODO set menus using the HQ coordinator + Mission:SetMenu() + if #AreaMsg > 0 then for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do if not TaskGroup:GetState( TaskGroup, "Assigned" ) then @@ -27629,2018 +29118,17 @@ do -- DETECTION_DISPATCHER return true end -end--- This module contains the STATEMACHINE class. --- This development is based on a state machine implementation made by Conroy Kyle. --- The state machine can be found here: https://github.com/kyleconroy/lua-state-machine +end--- This module contains the TASK_SEAD classes. -- --- I've taken the development and enhanced it to make the state machine hierarchical... --- It is a fantastic development, this module. --- --- === --- --- 1) @{Workflow#STATEMACHINE} class, extends @{Base#BASE} --- ============================================== --- --- 1.1) Add or remove objects from the STATEMACHINE --- -------------------------------------------- --- @module StateMachine --- @author FlightControl - - ---- STATEMACHINE class --- @type STATEMACHINE -STATEMACHINE = { - ClassName = "STATEMACHINE", -} - ---- Creates a new STATEMACHINE object. --- @param #STATEMACHINE self --- @return #STATEMACHINE -function STATEMACHINE:New( options ) - - -- Inherits from BASE - local self = BASE:Inherit( self, BASE:New() ) - - - --local self = routines.utils.deepCopy( self ) -- Create a new self instance - - assert(options.events) - - --local MT = {} - --setmetatable( self, MT ) - --self.__index = self - - self.options = options - self.current = options.initial or 'none' - self.events = {} - self.subs = {} - self.endstates = {} - - for _, event in ipairs(options.events or {}) do - local name = event.name - self[name] = self[name] or self:_create_transition(name) - self.events[name] = self.events[name] or { map = {} } - self:_add_to_map(self.events[name].map, event) - end - - for name, callback in pairs(options.callbacks or {}) do - self[name] = callback - end - - for name, sub in pairs( options.subs or {} ) do - self:_submap( self.subs, sub, name ) - end - - for name, endstate in pairs( options.endstates or {} ) do - self.endstates[endstate] = endstate - end - - return self -end - -function STATEMACHINE:LoadCallBacks( CallBackTable ) - - for name, callback in pairs( CallBackTable or {} ) do - self[name] = callback - end - -end - -function STATEMACHINE:_submap( subs, sub, name ) - self:E( { sub = sub, name = name } ) - subs[sub.onstateparent] = subs[sub.onstateparent] or {} - subs[sub.onstateparent][sub.oneventparent] = subs[sub.onstateparent][sub.oneventparent] or {} - local Index = #subs[sub.onstateparent][sub.oneventparent] + 1 - subs[sub.onstateparent][sub.oneventparent][Index] = {} - subs[sub.onstateparent][sub.oneventparent][Index].fsm = sub.fsm - subs[sub.onstateparent][sub.oneventparent][Index].event = sub.event - subs[sub.onstateparent][sub.oneventparent][Index].returnevents = sub.returnevents -- these events need to be given to find the correct continue event ... if none given, the processing will stop. - subs[sub.onstateparent][sub.oneventparent][Index].name = name - subs[sub.onstateparent][sub.oneventparent][Index].fsmparent = self -end - - -function STATEMACHINE:_call_handler(handler, params) - if handler then - return handler(unpack(params)) - end -end - -function STATEMACHINE:_create_transition(name) - self:E( { name = name } ) - return function(self, ...) - local can, to = self:can(name) - self:T( { name, can, to } ) - - if can then - local from = self.current - local params = { self, name, from, to, ... } - - if self:_call_handler(self["onbefore" .. name], params) == false - or self:_call_handler(self["onleave" .. from], params) == false then - return false - end - - self.current = to - - local execute = true - - local subtable = self:_gosub( to, name ) - for _, sub in pairs( subtable ) do - self:F( "calling sub: " .. sub.event ) - sub.fsm.fsmparent = self - sub.fsm.returnevents = sub.returnevents - sub.fsm[sub.event]( sub.fsm ) - execute = true - end - - local fsmparent, event = self:_isendstate( to ) - if fsmparent and event then - self:F( { "end state: ", fsmparent, event } ) - self:_call_handler(self["onenter" .. to] or self["on" .. to], params) - self:_call_handler(self["onafter" .. name] or self["on" .. name], params) - self:_call_handler(self["onstatechange"], params) - fsmparent[event]( fsmparent ) - execute = false - end - - if execute then - self:F( { "execute: " .. to, name } ) - self:_call_handler(self["onenter" .. to] or self["on" .. to], params) - self:_call_handler(self["onafter" .. name] or self["on" .. name], params) - self:_call_handler(self["onstatechange"], params) - end - - return true - end - - return false - end -end - -function STATEMACHINE:_gosub( parentstate, parentevent ) - local fsmtable = {} - if self.subs[parentstate] and self.subs[parentstate][parentevent] then - return self.subs[parentstate][parentevent] - else - return {} - end -end - -function STATEMACHINE:_isendstate( state ) - local fsmparent = self.fsmparent - if fsmparent and self.endstates[state] then - self:E( { state = state, endstates = self.endstates, endstate = self.endstates[state] } ) - local returnevent = nil - local fromstate = fsmparent.current - self:E( fromstate ) - self:E( self.returnevents ) - for _, eventname in pairs( self.returnevents ) do - local event = fsmparent.events[eventname] - self:E( event ) - local to = event and event.map[fromstate] or event.map['*'] - if to and to == state then - return fsmparent, eventname - else - self:E( { "could not find parent event name for state", fromstate, to } ) - end - end - end - - return nil -end - -function STATEMACHINE:_add_to_map(map, event) - if type(event.from) == 'string' then - map[event.from] = event.to - else - for _, from in ipairs(event.from) do - map[from] = event.to - end - end -end - -function STATEMACHINE:is(state) - return self.current == state -end - -function STATEMACHINE:can(e) - local event = self.events[e] - local to = event and event.map[self.current] or event.map['*'] - return to ~= nil, to -end - -function STATEMACHINE:cannot(e) - return not self:can(e) -end - -function STATEMACHINE:todot(filename) - local dotfile = io.open(filename,'w') - dotfile:write('digraph {\n') - local transition = function(event,from,to) - dotfile:write(string.format('%s -> %s [label=%s];\n',from,to,event)) - end - for _, event in pairs(self.options.events) do - if type(event.from) == 'table' then - for _, from in ipairs(event.from) do - transition(event.name,from,event.to) - end - else - transition(event.name,event.from,event.to) - end - end - dotfile:write('}\n') - dotfile:close() -end - ---- STATEMACHINE_PROCESS class --- @type STATEMACHINE_PROCESS --- @field Process#PROCESS Process --- @extends StateMachine#STATEMACHINE -STATEMACHINE_PROCESS = { - ClassName = "STATEMACHINE_PROCESS", -} - ---- Creates a new STATEMACHINE_PROCESS object. --- @param #STATEMACHINE_PROCESS self --- @return #STATEMACHINE_PROCESS -function STATEMACHINE_PROCESS:New( Process, options ) - - local FsmProcess = routines.utils.deepCopy( self ) -- Create a new self instance - local Parent = STATEMACHINE:New(options) - - setmetatable( FsmProcess, Parent ) - FsmProcess.__index = FsmProcess - - FsmProcess["onstatechange"] = Process.OnStateChange - FsmProcess.Process = Process - - return FsmProcess -end - -function STATEMACHINE_PROCESS:_call_handler( handler, params ) - if handler then - return handler( self.Process, unpack( params ) ) - end -end - ---- STATEMACHINE_TASK class --- @type STATEMACHINE_TASK --- @field Task#TASK_BASE Task --- @extends StateMachine#STATEMACHINE -STATEMACHINE_TASK = { - ClassName = "STATEMACHINE_TASK", -} - ---- Creates a new STATEMACHINE_TASK object. --- @param #STATEMACHINE_TASK self --- @return #STATEMACHINE_TASK -function STATEMACHINE_TASK:New( Task, TaskUnit, options ) - - local FsmTask = routines.utils.deepCopy( self ) -- Create a new self instance - local Parent = STATEMACHINE:New(options) - - setmetatable( FsmTask, Parent ) - FsmTask.__index = FsmTask - - FsmTask["onstatechange"] = Task.OnStateChange - FsmTask["onAssigned"] = Task.OnAssigned - FsmTask["onSuccess"] = Task.OnSuccess - FsmTask["onFailed"] = Task.OnFailed - - FsmTask.Task = Task - FsmTask.TaskUnit = TaskUnit - - return FsmTask -end - -function STATEMACHINE_TASK:_call_handler( handler, params ) - if handler then - return handler( self.Task, self.TaskUnit, unpack( params ) ) - end -end ---- @module Process - ---- The PROCESS class --- @type PROCESS --- @field Scheduler#SCHEDULER ProcessScheduler --- @field Unit#UNIT ProcessUnit --- @field Group#GROUP ProcessGroup --- @field Menu#MENU_GROUP MissionMenu --- @field Task#TASK_BASE Task --- @field StateMachine#STATEMACHINE_TASK Fsm --- @field #string ProcessName --- @extends Base#BASE -PROCESS = { - ClassName = "TASK", - ProcessScheduler = nil, - NextEvent = nil, - Scores = {}, -} - ---- Instantiates a new TASK Base. Should never be used. Interface Class. --- @param #PROCESS self --- @param #string ProcessName --- @param Task#TASK_BASE Task --- @param Unit#UNIT ProcessUnit --- @return #PROCESS self -function PROCESS:New( ProcessName, Task, ProcessUnit ) - local self = BASE:Inherit( self, BASE:New() ) - self:F() - - self.ProcessUnit = ProcessUnit - self.ProcessGroup = ProcessUnit:GetGroup() - self.MissionMenu = Task.Mission:GetMissionMenu( self.ProcessGroup ) - self.Task = Task - self.ProcessName = ProcessName - - self.ProcessScheduler = SCHEDULER:New() - - return self -end - ---- @param #PROCESS self -function PROCESS:NextEvent( NextEvent, ... ) - self:F(self.ProcessName) - self.ProcessScheduler:Schedule( self.Fsm, NextEvent, arg, 1 ) -- This schedules the next event, but only if scheduling is activated. -end - ---- @param #PROCESS self -function PROCESS:StopEvents() - self:F( { "Stop Process ", self.ProcessName } ) - self.ProcessScheduler:Stop() -end - ---- Adds a score for the PROCESS to be achieved. --- @param #PROCESS self --- @param #string ProcessStatus is the status of the PROCESS when the score needs to be given. --- @param #string ScoreText is a text describing the score that is given according the status. --- @param #number Score is a number providing the score of the status. --- @return #PROCESS self -function PROCESS:AddScore( ProcessStatus, ScoreText, Score ) - self:F2( { ProcessStatus, ScoreText, Score } ) - - self.Scores[ProcessStatus] = self.Scores[ProcessStatus] or {} - self.Scores[ProcessStatus].ScoreText = ScoreText - self.Scores[ProcessStatus].Score = Score - return self -end - ---- StateMachine callback function for a PROCESS --- @param #PROCESS self --- @param StateMachine#STATEMACHINE_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To -function PROCESS:OnStateChange( Fsm, Event, From, To ) - self:E( { self.ProcessName, Event, From, To, self.ProcessUnit.UnitName } ) - - if self:IsTrace() then - MESSAGE:New( "Process " .. self.ProcessName .. " : " .. Event .. " changed to state " .. To, 15 ):ToAll() - end - - if self.Scores[To] then - - local Scoring = self.Task:GetScoring() - if Scoring then - Scoring:_AddMissionTaskScore( self.Task.Mission, self.ProcessUnit, self.Scores[To].ScoreText, self.Scores[To].Score ) - end - end -end - - ---- This module contains the PROCESS_ASSIGN classes. --- --- === --- --- 1) @{Task_Assign#TASK_ASSIGN_ACCEPT} class, extends @{Task#TASK_BASE} --- ===================================================================== --- The @{Task_Assign#TASK_ASSIGN_ACCEPT} class accepts by default a task for a player. No player intervention is allowed to reject the task. --- --- 2) @{Task_Assign#TASK_ASSIGN_MENU_ACCEPT} class, extends @{Task#TASK_BASE} --- ========================================================================== --- The @{Task_Assign#TASK_ASSIGN_MENU_ACCEPT} class accepts a task when the player accepts the task through an added menu option. --- This assignment type is useful to conditionally allow the player to choose whether or not he would accept the task. --- The assignment type also allows to reject the task. --- --- --- --- --- --- --- @module Task_Assign --- - - -do -- PROCESS_ASSIGN_ACCEPT - - --- PROCESS_ASSIGN_ACCEPT class - -- @type PROCESS_ASSIGN_ACCEPT - -- @field Task#TASK_BASE Task - -- @field Unit#UNIT ProcessUnit - -- @field Zone#ZONE_BASE TargetZone - -- @extends Task2#TASK2 - PROCESS_ASSIGN_ACCEPT = { - ClassName = "PROCESS_ASSIGN_ACCEPT", - } - - - --- Creates a new task assignment state machine. The process will accept the task by default, no player intervention accepted. - -- @param #PROCESS_ASSIGN_ACCEPT self - -- @param Task#TASK Task - -- @param Unit#UNIT Unit - -- @return #PROCESS_ASSIGN_ACCEPT self - function PROCESS_ASSIGN_ACCEPT:New( Task, ProcessUnit, TaskBriefing ) - - -- Inherits from BASE - local self = BASE:Inherit( self, PROCESS:New( "ASSIGN_ACCEPT", Task, ProcessUnit ) ) -- #PROCESS_ASSIGN_ACCEPT - - self.TaskBriefing = TaskBriefing - - self.Fsm = STATEMACHINE_PROCESS:New( self, { - initial = 'UnAssigned', - events = { - { name = 'Start', from = 'UnAssigned', to = 'Assigned' }, - { name = 'Fail', from = 'UnAssigned', to = 'Failed' }, - }, - callbacks = { - onAssign = self.OnAssign, - }, - endstates = { - 'Assigned', 'Failed' - }, - } ) - - return self - end - - --- StateMachine callback function for a TASK2 - -- @param #PROCESS_ASSIGN_ACCEPT self - -- @param StateMachine#STATEMACHINE_PROCESS Fsm - -- @param #string Event - -- @param #string From - -- @param #string To - function PROCESS_ASSIGN_ACCEPT:OnAssigned( Fsm, Event, From, To ) - self:E( { Event, From, To, self.ProcessUnit.UnitName} ) - - end - -end - - -do -- PROCESS_ASSIGN_MENU_ACCEPT - - --- PROCESS_ASSIGN_MENU_ACCEPT class - -- @type PROCESS_ASSIGN_MENU_ACCEPT - -- @field Task#TASK_BASE Task - -- @field Unit#UNIT ProcessUnit - -- @field Zone#ZONE_BASE TargetZone - -- @extends Task2#TASK2 - PROCESS_ASSIGN_MENU_ACCEPT = { - ClassName = "PROCESS_ASSIGN_MENU_ACCEPT", - } - - - --- Creates a new task assignment state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator. - -- @param #PROCESS_ASSIGN_MENU_ACCEPT self - -- @param Task#TASK Task - -- @param Unit#UNIT Unit - -- @return #PROCESS_ASSIGN_MENU_ACCEPT self - function PROCESS_ASSIGN_MENU_ACCEPT:New( Task, ProcessUnit, TaskBriefing ) - - -- Inherits from BASE - local self = BASE:Inherit( self, PROCESS:New( "ASSIGN_MENU_ACCEPT", Task, ProcessUnit ) ) -- #PROCESS_ASSIGN_MENU_ACCEPT - - self.TaskBriefing = TaskBriefing - - self.Fsm = STATEMACHINE_PROCESS:New( self, { - initial = 'UnAssigned', - events = { - { name = 'Start', from = 'UnAssigned', to = 'AwaitAccept' }, - { name = 'Assign', from = 'AwaitAccept', to = 'Assigned' }, - { name = 'Reject', from = 'AwaitAccept', to = 'Rejected' }, - { name = 'Fail', from = 'AwaitAccept', to = 'Rejected' }, - }, - callbacks = { - onStart = self.OnStart, - onAssign = self.OnAssign, - onReject = self.OnReject, - }, - endstates = { - 'Assigned', 'Rejected' - }, - } ) - - return self - end - - --- StateMachine callback function for a TASK2 - -- @param #PROCESS_ASSIGN_MENU_ACCEPT self - -- @param StateMachine#STATEMACHINE_TASK Fsm - -- @param #string Event - -- @param #string From - -- @param #string To - function PROCESS_ASSIGN_MENU_ACCEPT:OnStart( Fsm, Event, From, To ) - self:E( { Event, From, To, self.ProcessUnit.UnitName} ) - - MESSAGE:New( self.TaskBriefing .. "\nAccess the radio menu to accept the task. You have 30 seconds or the assignment will be cancelled.", 30, "Assignment" ):ToGroup( self.ProcessUnit:GetGroup() ) - self.MenuText = self.Task.TaskName - - local ProcessGroup = self.ProcessUnit:GetGroup() - self.Menu = MENU_GROUP:New( ProcessGroup, "Task " .. self.MenuText .. " acceptance" ) - self.MenuAcceptTask = MENU_GROUP_COMMAND:New( ProcessGroup, "Accept task " .. self.MenuText, self.Menu, self.MenuAssign, self ) - self.MenuRejectTask = MENU_GROUP_COMMAND:New( ProcessGroup, "Reject task " .. self.MenuText, self.Menu, self.MenuReject, self ) - end - - --- Menu function. - -- @param #PROCESS_ASSIGN_MENU_ACCEPT self - function PROCESS_ASSIGN_MENU_ACCEPT:MenuAssign() - self:E( ) - - self:NextEvent( self.Fsm.Assign ) - end - - --- Menu function. - -- @param #PROCESS_ASSIGN_MENU_ACCEPT self - function PROCESS_ASSIGN_MENU_ACCEPT:MenuReject() - self:E( ) - - self:NextEvent( self.Fsm.Reject ) - end - - --- StateMachine callback function for a TASK2 - -- @param #PROCESS_ASSIGN_MENU_ACCEPT self - -- @param StateMachine#STATEMACHINE_PROCESS Fsm - -- @param #string Event - -- @param #string From - -- @param #string To - function PROCESS_ASSIGN_MENU_ACCEPT:OnAssign( Fsm, Event, From, To ) - self:E( { Event, From, To, self.ProcessUnit.UnitName} ) - - self.Menu:Remove() - end - - --- StateMachine callback function for a TASK2 - -- @param #PROCESS_ASSIGN_MENU_ACCEPT self - -- @param StateMachine#STATEMACHINE_PROCESS Fsm - -- @param #string Event - -- @param #string From - -- @param #string To - function PROCESS_ASSIGN_MENU_ACCEPT:OnReject( Fsm, Event, From, To ) - self:E( { Event, From, To, self.ProcessUnit.UnitName} ) - - self.Menu:Remove() - self.Task:UnAssignFromUnit( self.ProcessUnit ) - self.ProcessUnit:Destroy() - end -end ---- @module Task_Route - ---- PROCESS_ROUTE class --- @type PROCESS_ROUTE --- @field Task#TASK TASK --- @field Unit#UNIT ProcessUnit --- @field Zone#ZONE_BASE TargetZone --- @extends Task2#TASK2 -PROCESS_ROUTE = { - ClassName = "PROCESS_ROUTE", -} - - ---- Creates a new routing state machine. The task will route a CLIENT to a ZONE until the CLIENT is within that ZONE. --- @param #PROCESS_ROUTE self --- @param Task#TASK Task --- @param Unit#UNIT Unit --- @return #PROCESS_ROUTE self -function PROCESS_ROUTE:New( Task, ProcessUnit, TargetZone ) - - -- Inherits from BASE - local self = BASE:Inherit( self, PROCESS:New( "ROUTE", Task, ProcessUnit ) ) -- #PROCESS_ROUTE - - self.TargetZone = TargetZone - self.DisplayInterval = 30 - self.DisplayCount = 30 - self.DisplayMessage = true - self.DisplayTime = 10 -- 10 seconds is the default - self.DisplayCategory = "HQ" -- Route is the default display category - - self.Fsm = STATEMACHINE_PROCESS:New( self, { - initial = 'UnArrived', - events = { - { name = 'Start', from = 'UnArrived', to = 'UnArrived' }, - { name = 'Fail', from = 'UnArrived', to = 'Failed' }, - }, - callbacks = { - onleaveUnArrived = self.OnLeaveUnArrived, - onFail = self.OnFail, - }, - endstates = { - 'Arrived', 'Failed' - }, - } ) - - return self -end - ---- Task Events - ---- StateMachine callback function for a TASK2 --- @param #PROCESS_ROUTE self --- @param StateMachine#STATEMACHINE_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To -function PROCESS_ROUTE:OnLeaveUnArrived( Fsm, Event, From, To ) - - if self.ProcessUnit:IsAlive() then - local IsInZone = self.ProcessUnit:IsInZone( self.TargetZone ) - - if self.DisplayCount >= self.DisplayInterval then - if not IsInZone then - local ZoneVec2 = self.TargetZone:GetVec2() - local ZonePointVec2 = POINT_VEC2:New( ZoneVec2.x, ZoneVec2.y ) - local TaskUnitVec2 = self.ProcessUnit:GetVec2() - local TaskUnitPointVec2 = POINT_VEC2:New( TaskUnitVec2.x, TaskUnitVec2.y ) - local RouteText = self.ProcessUnit:GetCallsign() .. ": Route to " .. TaskUnitPointVec2:GetBRText( ZonePointVec2 ) .. " km to target." - MESSAGE:New( RouteText, self.DisplayTime, self.DisplayCategory ):ToGroup( self.ProcessUnit:GetGroup() ) - end - self.DisplayCount = 1 - else - self.DisplayCount = self.DisplayCount + 1 - end - - --if not IsInZone then - self:NextEvent( Fsm.Start ) - --end - - return IsInZone -- if false, then the event will not be executed... - end - - return false - -end - ---- @module Process_Smoke - -do -- PROCESS_SMOKE_TARGETS - - --- PROCESS_SMOKE_TARGETS class - -- @type PROCESS_SMOKE_TARGETS - -- @field Task#TASK_BASE Task - -- @field Unit#UNIT ProcessUnit - -- @field Set#SET_UNIT TargetSetUnit - -- @field Zone#ZONE_BASE TargetZone - -- @extends Task2#TASK2 - PROCESS_SMOKE_TARGETS = { - ClassName = "PROCESS_SMOKE_TARGETS", - } - - - --- Creates a new task assignment state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator. - -- @param #PROCESS_SMOKE_TARGETS self - -- @param Task#TASK Task - -- @param Unit#UNIT Unit - -- @return #PROCESS_SMOKE_TARGETS self - function PROCESS_SMOKE_TARGETS:New( Task, ProcessUnit, TargetSetUnit, TargetZone ) - - -- Inherits from BASE - local self = BASE:Inherit( self, PROCESS:New( "ASSIGN_MENU_ACCEPT", Task, ProcessUnit ) ) -- #PROCESS_SMOKE_TARGETS - - self.TargetSetUnit = TargetSetUnit - self.TargetZone = TargetZone - - self.Fsm = STATEMACHINE_PROCESS:New( self, { - initial = 'None', - events = { - { name = 'Start', from = 'None', to = 'AwaitSmoke' }, - { name = 'Next', from = 'AwaitSmoke', to = 'Smoking' }, - { name = 'Next', from = 'Smoking', to = 'AwaitSmoke' }, - { name = 'Fail', from = 'Smoking', to = 'Failed' }, - { name = 'Fail', from = 'AwaitSmoke', to = 'Failed' }, - { name = 'Fail', from = 'None', to = 'Failed' }, - }, - callbacks = { - onStart = self.OnStart, - onNext = self.OnNext, - onSmoking = self.OnSmoking, - }, - endstates = { - }, - } ) - - return self - end - - --- StateMachine callback function for a TASK2 - -- @param #PROCESS_SMOKE_TARGETS self - -- @param StateMachine#STATEMACHINE_TASK Fsm - -- @param #string Event - -- @param #string From - -- @param #string To - function PROCESS_SMOKE_TARGETS:OnStart( Fsm, Event, From, To ) - self:E( { Event, From, To, self.ProcessUnit.UnitName} ) - - self:E("Set smoke menu") - - local ProcessGroup = self.ProcessUnit:GetGroup() - local MissionMenu = self.Task.Mission:GetMissionMenu( ProcessGroup ) - - local function MenuSmoke( MenuParam ) - self:E( MenuParam ) - local self = MenuParam.self - local SmokeColor = MenuParam.SmokeColor - self.SmokeColor = SmokeColor - self:NextEvent( self.Fsm.Next ) - end - - self.Menu = MENU_GROUP:New( ProcessGroup, "Target acquisition", MissionMenu ) - self.MenuSmokeBlue = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop blue smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Blue } ) - self.MenuSmokeGreen = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop green smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Green } ) - self.MenuSmokeOrange = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop Orange smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Orange } ) - self.MenuSmokeRed = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop Red smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Red } ) - self.MenuSmokeWhite = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop White smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.White } ) - end - - --- StateMachine callback function for a TASK2 - -- @param #PROCESS_SMOKE_TARGETS self - -- @param StateMachine#STATEMACHINE_PROCESS Fsm - -- @param #string Event - -- @param #string From - -- @param #string To - function PROCESS_SMOKE_TARGETS:OnSmoking( Fsm, Event, From, To ) - self:E( { Event, From, To, self.ProcessUnit.UnitName} ) - - self.TargetSetUnit:ForEachUnit( - --- @param Unit#UNIT SmokeUnit - function( SmokeUnit ) - if math.random( 1, ( 100 * self.TargetSetUnit:Count() ) / 4 ) <= 100 then - SCHEDULER:New( self, - function() - if SmokeUnit:IsAlive() then - SmokeUnit:Smoke( self.SmokeColor, 150 ) - end - end, {}, math.random( 10, 60 ) - ) - end - end - ) - - end - -end--- @module Process_Destroy - ---- PROCESS_DESTROY class --- @type PROCESS_DESTROY --- @field Unit#UNIT ProcessUnit --- @field Set#SET_UNIT TargetSetUnit --- @extends Process#PROCESS -PROCESS_DESTROY = { - ClassName = "PROCESS_DESTROY", - Fsm = {}, - TargetSetUnit = nil, -} - - ---- Creates a new DESTROY process. --- @param #PROCESS_DESTROY self --- @param Task#TASK Task --- @param Unit#UNIT ProcessUnit --- @param Set#SET_UNIT TargetSetUnit --- @return #PROCESS_DESTROY self -function PROCESS_DESTROY:New( Task, ProcessName, ProcessUnit, TargetSetUnit ) - - -- Inherits from BASE - local self = BASE:Inherit( self, PROCESS:New( ProcessName, Task, ProcessUnit ) ) -- #PROCESS_DESTROY - - self.TargetSetUnit = TargetSetUnit - - self.DisplayInterval = 30 - self.DisplayCount = 30 - self.DisplayMessage = true - self.DisplayTime = 10 -- 10 seconds is the default - self.DisplayCategory = "HQ" -- Targets is the default display category - - self.Fsm = STATEMACHINE_PROCESS:New( self, { - initial = 'Assigned', - events = { - { name = 'Start', from = 'Assigned', to = 'Waiting' }, - { name = 'Start', from = 'Waiting', to = 'Waiting' }, - { name = 'HitTarget', from = 'Waiting', to = 'Destroy' }, - { name = 'MoreTargets', from = 'Destroy', to = 'Waiting' }, - { name = 'Destroyed', from = 'Destroy', to = 'Success' }, - { name = 'Fail', from = 'Assigned', to = 'Failed' }, - { name = 'Fail', from = 'Waiting', to = 'Failed' }, - { name = 'Fail', from = 'Destroy', to = 'Failed' }, - }, - callbacks = { - onStart = self.OnStart, - onWaiting = self.OnWaiting, - onHitTarget = self.OnHitTarget, - onMoreTargets = self.OnMoreTargets, - onDestroyed = self.OnDestroyed, - onKilled = self.OnKilled, - }, - endstates = { 'Success', 'Failed' } - } ) - - - _EVENTDISPATCHER:OnDead( self.EventDead, self ) - - return self -end - ---- Process Events - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_DESTROY self --- @param StateMachine#STATEMACHINE_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To -function PROCESS_DESTROY:OnStart( Fsm, Event, From, To ) - - self:NextEvent( Fsm.Start ) -end - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_DESTROY self --- @param StateMachine#STATEMACHINE_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To -function PROCESS_DESTROY:OnWaiting( Fsm, Event, From, To ) - - local TaskGroup = self.ProcessUnit:GetGroup() - if self.DisplayCount >= self.DisplayInterval then - MESSAGE:New( "Your group with assigned " .. self.Task:GetName() .. " task has " .. self.TargetSetUnit:GetUnitTypesText() .. " targets left to be destroyed.", 5, "HQ" ):ToGroup( TaskGroup ) - self.DisplayCount = 1 - else - self.DisplayCount = self.DisplayCount + 1 - end - - return true -- Process always the event. - -end - - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_DESTROY self --- @param StateMachine#STATEMACHINE_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To --- @param Event#EVENTDATA Event -function PROCESS_DESTROY:OnHitTarget( Fsm, Event, From, To, Event ) - - - self.TargetSetUnit:Flush() - - if self.TargetSetUnit:FindUnit( Event.IniUnitName ) then - self.TargetSetUnit:RemoveUnitsByName( Event.IniUnitName ) - local TaskGroup = self.ProcessUnit:GetGroup() - MESSAGE:New( "You hit a target. Your group with assigned " .. self.Task:GetName() .. " task has " .. self.TargetSetUnit:Count() .. " targets ( " .. self.TargetSetUnit:GetUnitTypesText() .. " ) left to be destroyed.", 15, "HQ" ):ToGroup( TaskGroup ) - end - - - if self.TargetSetUnit:Count() > 0 then - self:NextEvent( Fsm.MoreTargets ) - else - self:NextEvent( Fsm.Destroyed ) - end -end - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_DESTROY self --- @param StateMachine#STATEMACHINE_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To -function PROCESS_DESTROY:OnMoreTargets( Fsm, Event, From, To ) - - -end - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_DESTROY self --- @param StateMachine#STATEMACHINE_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To --- @param Event#EVENTDATA DCSEvent -function PROCESS_DESTROY:OnKilled( Fsm, Event, From, To ) - - self:NextEvent( Fsm.Restart ) - -end - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_DESTROY self --- @param StateMachine#STATEMACHINE_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To -function PROCESS_DESTROY:OnRestart( Fsm, Event, From, To ) - - self:NextEvent( Fsm.Menu ) - -end - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_DESTROY self --- @param StateMachine#STATEMACHINE_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To -function PROCESS_DESTROY:OnDestroyed( Fsm, Event, From, To ) - -end - ---- DCS Events - ---- @param #PROCESS_DESTROY self --- @param Event#EVENTDATA Event -function PROCESS_DESTROY:EventDead( Event ) - - if Event.IniDCSUnit then - self:NextEvent( self.Fsm.HitTarget, Event ) - end -end - - ---- @module Process_JTAC - ---- PROCESS_JTAC class --- @type PROCESS_JTAC --- @field Unit#UNIT ProcessUnit --- @field Set#SET_UNIT TargetSetUnit --- @extends Process#PROCESS -PROCESS_JTAC = { - ClassName = "PROCESS_JTAC", - Fsm = {}, - TargetSetUnit = nil, -} - - ---- Creates a new DESTROY process. --- @param #PROCESS_JTAC self --- @param Task#TASK Task --- @param Unit#UNIT ProcessUnit --- @param Set#SET_UNIT TargetSetUnit --- @param Unit#UNIT FACUnit --- @return #PROCESS_JTAC self -function PROCESS_JTAC:New( Task, ProcessUnit, TargetSetUnit, FACUnit ) - - -- Inherits from BASE - local self = BASE:Inherit( self, PROCESS:New( "JTAC", Task, ProcessUnit ) ) -- #PROCESS_JTAC - - self.TargetSetUnit = TargetSetUnit - self.FACUnit = FACUnit - - self.DisplayInterval = 60 - self.DisplayCount = 30 - self.DisplayMessage = true - self.DisplayTime = 10 -- 10 seconds is the default - self.DisplayCategory = "HQ" -- Targets is the default display category - - - self.Fsm = STATEMACHINE_PROCESS:New( self, { - initial = 'Assigned', - events = { - { name = 'Start', from = 'Assigned', to = 'CreatedMenu' }, - { name = 'JTACMenuUpdate', from = 'CreatedMenu', to = 'AwaitingMenu' }, - { name = 'JTACMenuAwait', from = 'AwaitingMenu', to = 'AwaitingMenu' }, - { name = 'JTACMenuSpot', from = 'AwaitingMenu', to = 'AwaitingMenu' }, - { name = 'JTACMenuCancel', from = 'AwaitingMenu', to = 'AwaitingMenu' }, - { name = 'JTACStatus', from = 'AwaitingMenu', to = 'AwaitingMenu' }, - { name = 'Fail', from = 'AwaitingMenu', to = 'Failed' }, - { name = 'Fail', from = 'CreatedMenu', to = 'Failed' }, - }, - callbacks = { - onStart = self.OnStart, - onJTACMenuUpdate = self.OnJTACMenuUpdate, - onJTACMenuAwait = self.OnJTACMenuAwait, - onJTACMenuSpot = self.OnJTACMenuSpot, - onJTACMenuCancel = self.OnJTACMenuCancel, - }, - endstates = { 'Failed' } - } ) - - - _EVENTDISPATCHER:OnDead( self.EventDead, self ) - - return self -end - ---- Process Events - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_JTAC self --- @param StateMachine#STATEMACHINE_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To -function PROCESS_JTAC:OnStart( Fsm, Event, From, To ) - - self:NextEvent( Fsm.JTACMenuUpdate ) -end - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_JTAC self --- @param StateMachine#STATEMACHINE_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To -function PROCESS_JTAC:OnJTACMenuUpdate( Fsm, Event, From, To ) - - local function JTACMenuSpot( MenuParam ) - self:E( MenuParam.TargetUnit.UnitName ) - local self = MenuParam.self - local TargetUnit = MenuParam.TargetUnit - - self:NextEvent( self.Fsm.JTACMenuSpot, TargetUnit ) - end - - local function JTACMenuCancel( MenuParam ) - self:E( MenuParam ) - local self = MenuParam.self - local TargetUnit = MenuParam.TargetUnit - - self:NextEvent( self.Fsm.JTACMenuCancel, TargetUnit ) - end - - - -- Loop each unit in the target set, and determine the threat levels map table. - local UnitThreatLevels = self.TargetSetUnit:GetUnitThreatLevels() - - self:E( {"UnitThreadLevels", UnitThreatLevels } ) - - local JTACMenu = self.ProcessGroup:GetState( self.ProcessGroup, "JTACMenu" ) - - if not JTACMenu then - JTACMenu = MENU_GROUP:New( self.ProcessGroup, "JTAC", self.MissionMenu ) - for ThreatLevel, ThreatLevelTable in pairs( UnitThreatLevels ) do - local JTACMenuThreatLevel = MENU_GROUP:New( self.ProcessGroup, ThreatLevelTable.UnitThreatLevelText, JTACMenu ) - for ThreatUnitName, ThreatUnit in pairs( ThreatLevelTable.Units ) do - local JTACMenuUnit = MENU_GROUP:New( self.ProcessGroup, ThreatUnit:GetTypeName(), JTACMenuThreatLevel ) - MENU_GROUP_COMMAND:New( self.ProcessGroup, "Lase Target", JTACMenuUnit, JTACMenuSpot, { self = self, TargetUnit = ThreatUnit } ) - MENU_GROUP_COMMAND:New( self.ProcessGroup, "Cancel Target", JTACMenuUnit, JTACMenuCancel, { self = self, TargetUnit = ThreatUnit } ) - end - end - end - -end - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_JTAC self --- @param StateMachine#STATEMACHINE_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To -function PROCESS_JTAC:OnJTACMenuAwait( Fsm, Event, From, To ) - - if self.DisplayCount >= self.DisplayInterval then - - local TaskJTAC = self.Task -- Task#TASK_JTAC - TaskJTAC.Spots = TaskJTAC.Spots or {} - for TargetUnitName, SpotData in pairs( TaskJTAC.Spots) do - local TargetUnit = UNIT:FindByName( TargetUnitName ) - self.FACUnit:MessageToGroup( "Lasing " .. TargetUnit:GetTypeName() .. " with laser code " .. SpotData:getCode(), 15, self.ProcessGroup ) - end - self.DisplayCount = 1 - else - self.DisplayCount = self.DisplayCount + 1 - end - - self:NextEvent( Fsm.JTACMenuAwait ) -end - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_JTAC self --- @param StateMachine#STATEMACHINE_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To --- @param Unit#UNIT TargetUnit -function PROCESS_JTAC:OnJTACMenuSpot( Fsm, Event, From, To, TargetUnit ) - - local TargetUnitName = TargetUnit:GetName() - - local TaskJTAC = self.Task -- Task#TASK_JTAC - - TaskJTAC.Spots = TaskJTAC.Spots or {} - TaskJTAC.Spots[TargetUnitName] = TaskJTAC.Spots[TargetUnitName] or {} - - local DCSFACObject = self.FACUnit:GetDCSObject() - local TargetVec3 = TargetUnit:GetVec3() - - TaskJTAC.Spots[TargetUnitName] = Spot.createInfraRed( self.FACUnit:GetDCSObject(), { x = 0, y = 1, z = 0 }, TargetUnit:GetVec3(), math.random( 1000, 9999 ) ) - - local SpotData = TaskJTAC.Spots[TargetUnitName] - self.FACUnit:MessageToGroup( "Lasing " .. TargetUnit:GetTypeName() .. " with laser code " .. SpotData:getCode(), 15, self.ProcessGroup ) - - self:NextEvent( Fsm.JTACMenuAwait ) -end - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_JTAC self --- @param StateMachine#STATEMACHINE_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To --- @param Unit#UNIT TargetUnit -function PROCESS_JTAC:OnJTACMenuCancel( Fsm, Event, From, To, TargetUnit ) - - local TargetUnitName = TargetUnit:GetName() - - local TaskJTAC = self.Task -- Task#TASK_JTAC - - TaskJTAC.Spots = TaskJTAC.Spots or {} - if TaskJTAC.Spots[TargetUnitName] then - TaskJTAC.Spots[TargetUnitName]:destroy() -- destroys the spot - TaskJTAC.Spots[TargetUnitName] = nil - end - - self.FACUnit:MessageToGroup( "Stopped lasing " .. TargetUnit:GetTypeName(), 15, self.ProcessGroup ) - - self:NextEvent( Fsm.JTACMenuAwait ) -end - - ---- This module contains the TASK_BASE class. --- --- 1) @{#TASK_BASE} class, extends @{Base#BASE} --- ============================================ --- 1.1) The @{#TASK_BASE} class implements the methods for task orchestration within MOOSE. --- ---------------------------------------------------------------------------------------- --- The class provides a couple of methods to: --- --- * @{#TASK_BASE.AssignToGroup}():Assign a task to a group (of players). --- * @{#TASK_BASE.AddProcess}():Add a @{Process} to a task. --- * @{#TASK_BASE.RemoveProcesses}():Remove a running @{Process} from a running task. --- * @{#TASK_BASE.AddStateMachine}():Add a @{StateMachine} to a task. --- * @{#TASK_BASE.RemoveStateMachines}():Remove @{StateMachine}s from a task. --- * @{#TASK_BASE.HasStateMachine}():Enquire if the task has a @{StateMachine} --- * @{#TASK_BASE.AssignToUnit}(): Assign a task to a unit. (Needs to be implemented in the derived classes from @{#TASK_BASE}. --- * @{#TASK_BASE.UnAssignFromUnit}(): Unassign the task from a unit. --- --- 1.2) Set and enquire task status (beyond the task state machine processing). --- ---------------------------------------------------------------------------- --- A task needs to implement as a minimum the following task states: --- --- * **Success**: Expresses the successful execution and finalization of the task. --- * **Failed**: Expresses the failure of a task. --- * **Planned**: Expresses that the task is created, but not yet in execution and is not assigned yet. --- * **Assigned**: Expresses that the task is assigned to a Group of players, and that the task is in execution mode. --- --- A task may also implement the following task states: --- --- * **Rejected**: Expresses that the task is rejected by a player, who was requested to accept the task. --- * **Cancelled**: Expresses that the task is cancelled by HQ or through a logical situation where a cancellation of the task is required. --- --- A task can implement more statusses than the ones outlined above. Please consult the documentation of the specific tasks to understand the different status modelled. --- --- The status of tasks can be set by the methods **State** followed by the task status. An example is `StateAssigned()`. --- The status of tasks can be enquired by the methods **IsState** followed by the task status name. An example is `if IsStateAssigned() then`. --- --- 1.3) Add scoring when reaching a certain task status: --- ----------------------------------------------------- --- Upon reaching a certain task status in a task, additional scoring can be given. If the Mission has a scoring system attached, the scores will be added to the mission scoring. --- Use the method @{#TASK_BASE.AddScore}() to add scores when a status is reached. --- --- 1.4) Task briefing: --- ------------------- --- A task briefing can be given that is shown to the player when he is assigned to the task. --- --- === --- --- ### Authors: FlightControl - Design and Programming --- --- @module Task - ---- The TASK_BASE class --- @type TASK_BASE --- @field Scheduler#SCHEDULER TaskScheduler --- @field Mission#MISSION Mission --- @field StateMachine#STATEMACHINE Fsm --- @field Set#SET_GROUP SetGroup The Set of Groups assigned to the Task --- @extends Base#BASE -TASK_BASE = { - ClassName = "TASK_BASE", - TaskScheduler = nil, - Processes = {}, - Players = nil, - Scores = {}, - Menu = {}, - SetGroup = nil, -} - - ---- Instantiates a new TASK_BASE. Should never be used. Interface Class. --- @param #TASK_BASE self --- @param Mission#MISSION The mission wherein the Task is registered. --- @param Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. --- @param #string TaskName The name of the Task --- @param #string TaskType The type of the Task --- @param #string TaskCategory The category of the Task (A2G, A2A, Transport, ... ) --- @return #TASK_BASE self -function TASK_BASE:New( Mission, SetGroup, TaskName, TaskType, TaskCategory ) - - local self = BASE:Inherit( self, BASE:New() ) - self:E( "New TASK " .. TaskName ) - - self.Processes = {} - self.Fsm = {} - - self.Mission = Mission - self.SetGroup = SetGroup - - self:SetCategory( TaskCategory ) - self:SetType( TaskType ) - self:SetName( TaskName ) - self:SetID( Mission:GetNextTaskID( self ) ) -- The Mission orchestrates the task sequences .. - - self.TaskBriefing = "You are assigned to the task: " .. self.TaskName .. "." - - return self -end - ---- Cleans all references of a TASK_BASE. --- @param #TASK_BASE self --- @return #nil -function TASK_BASE:CleanUp() - - _EVENTDISPATCHER:OnPlayerLeaveRemove( self ) - _EVENTDISPATCHER:OnDeadRemove( self ) - _EVENTDISPATCHER:OnCrashRemove( self ) - _EVENTDISPATCHER:OnPilotDeadRemove( self ) - - return nil -end - - ---- Assign the @{Task}to a @{Group}. --- @param #TASK_BASE self --- @param Group#GROUP TaskGroup -function TASK_BASE:AssignToGroup( TaskGroup ) - self:F2( TaskGroup:GetName() ) - - local TaskGroupName = TaskGroup:GetName() - - TaskGroup:SetState( TaskGroup, "Assigned", self ) - - self:RemoveMenuForGroup( TaskGroup ) - self:SetAssignedMenuForGroup( TaskGroup ) - - local TaskUnits = TaskGroup:GetUnits() - for UnitID, UnitData in pairs( TaskUnits ) do - local TaskUnit = UnitData -- Unit#UNIT - local PlayerName = TaskUnit:GetPlayerName() - if PlayerName ~= nil or PlayerName ~= "" then - self:AssignToUnit( TaskUnit ) - end - end -end - ---- Send the briefng message of the @{Task} to the assigned @{Group}s. --- @param #TASK_BASE self -function TASK_BASE:SendBriefingToAssignedGroups() - self:F2() - - for TaskGroupName, TaskGroup in pairs( self.SetGroup:GetSet() ) do - - if self:IsAssignedToGroup( TaskGroup ) then - TaskGroup:Message( self.TaskBriefing, 60 ) - end - end -end - - ---- Assign the @{Task} from the @{Group}s. --- @param #TASK_BASE self -function TASK_BASE:UnAssignFromGroups() - self:F2() - - for TaskGroupName, TaskGroup in pairs( self.SetGroup:GetSet() ) do - - TaskGroup:SetState( TaskGroup, "Assigned", nil ) - local TaskUnits = TaskGroup:GetUnits() - for UnitID, UnitData in pairs( TaskUnits ) do - local TaskUnit = UnitData -- Unit#UNIT - local PlayerName = TaskUnit:GetPlayerName() - if PlayerName ~= nil or PlayerName ~= "" then - self:UnAssignFromUnit( TaskUnit ) - end - end - end -end - ---- Returns if the @{Task} is assigned to the Group. --- @param #TASK_BASE self --- @param Group#GROUP TaskGroup --- @return #boolean -function TASK_BASE:IsAssignedToGroup( TaskGroup ) - - local TaskGroupName = TaskGroup:GetName() - - if self:IsStateAssigned() then - if TaskGroup:GetState( TaskGroup, "Assigned" ) == self then - return true - end - end - - return false -end - ---- Assign the @{Task}to an alive @{Unit}. --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @return #TASK_BASE self -function TASK_BASE:AssignToUnit( TaskUnit ) - self:F( TaskUnit:GetName() ) - - return nil -end - ---- UnAssign the @{Task} from an alive @{Unit}. --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @return #TASK_BASE self -function TASK_BASE:UnAssignFromUnit( TaskUnitName ) - self:F( TaskUnitName ) - - if self:HasStateMachine( TaskUnitName ) == true then - self:RemoveStateMachines( TaskUnitName ) - self:RemoveProcesses( TaskUnitName ) - end - - return self -end - ---- Set the menu options of the @{Task} to all the groups in the SetGroup. --- @param #TASK_BASE self --- @return #TASK_BASE self -function TASK_BASE:SetPlannedMenu() - - local MenuText = self:GetPlannedMenuText() - for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - if not self:IsAssignedToGroup( TaskGroup ) then - self:SetPlannedMenuForGroup( TaskGroup, MenuText ) - end - end -end - ---- Set the menu options of the @{Task} to all the groups in the SetGroup. --- @param #TASK_BASE self --- @return #TASK_BASE self -function TASK_BASE:SetAssignedMenu() - - for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - if self:IsAssignedToGroup( TaskGroup ) then - self:SetAssignedMenuForGroup( TaskGroup ) - end - end -end - ---- Remove the menu options of the @{Task} to all the groups in the SetGroup. --- @param #TASK_BASE self --- @return #TASK_BASE self -function TASK_BASE:RemoveMenu() - - for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - self:RemoveMenuForGroup( TaskGroup ) - end -end - ---- Set the planned menu option of the @{Task}. --- @param #TASK_BASE self --- @param Group#GROUP TaskGroup --- @param #string MenuText The menu text. --- @return #TASK_BASE self -function TASK_BASE:SetPlannedMenuForGroup( TaskGroup, MenuText ) - self:E( TaskGroup:GetName() ) - - local TaskMission = self.Mission:GetName() - local TaskCategory = self:GetCategory() - local TaskType = self:GetType() - - local Mission = self.Mission - - Mission.MenuMission = Mission.MenuMission or {} - local MenuMission = Mission.MenuMission - - Mission.MenuCategory = Mission.MenuCategory or {} - local MenuCategory = Mission.MenuCategory - - Mission.MenuType = Mission.MenuType or {} - local MenuType = Mission.MenuType - - self.Menu = self.Menu or {} - local Menu = self.Menu - - local TaskGroupName = TaskGroup:GetName() - MenuMission[TaskGroupName] = MenuMission[TaskGroupName] or MENU_GROUP:New( TaskGroup, TaskMission, nil ) - - MenuCategory[TaskGroupName] = MenuCategory[TaskGroupName] or {} - MenuCategory[TaskGroupName][TaskCategory] = MenuCategory[TaskGroupName][TaskCategory] or MENU_GROUP:New( TaskGroup, TaskCategory, MenuMission[TaskGroupName] ) - - MenuType[TaskGroupName] = MenuType[TaskGroupName] or {} - MenuType[TaskGroupName][TaskType] = MenuType[TaskGroupName][TaskType] or MENU_GROUP:New( TaskGroup, TaskType, MenuCategory[TaskGroupName][TaskCategory] ) - - if Menu[TaskGroupName] then - Menu[TaskGroupName]:Remove() - end - Menu[TaskGroupName] = MENU_GROUP_COMMAND:New( TaskGroup, MenuText, MenuType[TaskGroupName][TaskType], self.MenuAssignToGroup, { self = self, TaskGroup = TaskGroup } ) - - return self -end - ---- Set the assigned menu options of the @{Task}. --- @param #TASK_BASE self --- @param Group#GROUP TaskGroup --- @return #TASK_BASE self -function TASK_BASE:SetAssignedMenuForGroup( TaskGroup ) - self:E( TaskGroup:GetName() ) - - local TaskMission = self.Mission:GetName() - - local Mission = self.Mission - - Mission.MenuMission = Mission.MenuMission or {} - local MenuMission = Mission.MenuMission - - self.MenuStatus = self.MenuStatus or {} - local MenuStatus = self.MenuStatus - - - self.MenuAbort = self.MenuAbort or {} - local MenuAbort = self.MenuAbort - - local TaskGroupName = TaskGroup:GetName() - MenuMission[TaskGroupName] = MenuMission[TaskGroupName] or MENU_GROUP:New( TaskGroup, TaskMission, nil ) - MenuStatus[TaskGroupName] = MENU_GROUP_COMMAND:New( TaskGroup, "Task Status", MenuMission[TaskGroupName], self.MenuTaskStatus, { self = self, TaskGroup = TaskGroup } ) - MenuAbort[TaskGroupName] = MENU_GROUP_COMMAND:New( TaskGroup, "Abort Task", MenuMission[TaskGroupName], self.MenuTaskAbort, { self = self, TaskGroup = TaskGroup } ) - - return self -end - ---- Remove the menu option of the @{Task} for a @{Group}. --- @param #TASK_BASE self --- @param Group#GROUP TaskGroup --- @return #TASK_BASE self -function TASK_BASE:RemoveMenuForGroup( TaskGroup ) - - local TaskGroupName = TaskGroup:GetName() - - local Mission = self.Mission - local MenuMission = Mission.MenuMission - local MenuCategory = Mission.MenuCategory - local MenuType = Mission.MenuType - local MenuStatus = self.MenuStatus - local MenuAbort = self.MenuAbort - local Menu = self.Menu - - Menu = Menu or {} - if Menu[TaskGroupName] then - Menu[TaskGroupName]:Remove() - Menu[TaskGroupName] = nil - end - - MenuType = MenuType or {} - if MenuType[TaskGroupName] then - for _, Menu in pairs( MenuType[TaskGroupName] ) do - Menu:Remove() - end - MenuType[TaskGroupName] = nil - end - - MenuCategory = MenuCategory or {} - if MenuCategory[TaskGroupName] then - for _, Menu in pairs( MenuCategory[TaskGroupName] ) do - Menu:Remove() - end - MenuCategory[TaskGroupName] = nil - end - - MenuStatus = MenuStatus or {} - if MenuStatus[TaskGroupName] then - MenuStatus[TaskGroupName]:Remove() - MenuStatus[TaskGroupName] = nil - end - - MenuAbort = MenuAbort or {} - if MenuAbort[TaskGroupName] then - MenuAbort[TaskGroupName]:Remove() - MenuAbort[TaskGroupName] = nil - end - -end - -function TASK_BASE.MenuAssignToGroup( MenuParam ) - - local self = MenuParam.self - local TaskGroup = MenuParam.TaskGroup - - self:AssignToGroup( TaskGroup ) -end - -function TASK_BASE.MenuTaskStatus( MenuParam ) - - local self = MenuParam.self - local TaskGroup = MenuParam.TaskGroup - - --self:AssignToGroup( TaskGroup ) -end - -function TASK_BASE.MenuTaskAbort( MenuParam ) - - local self = MenuParam.self - local TaskGroup = MenuParam.TaskGroup - - --self:AssignToGroup( TaskGroup ) -end - - - ---- Returns the @{Task} name. --- @param #TASK_BASE self --- @return #string TaskName -function TASK_BASE:GetTaskName() - return self.TaskName -end - - ---- Add Process to @{Task} with key @{Unit}. --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @return #TASK_BASE self -function TASK_BASE:AddProcess( TaskUnit, Process ) - local TaskUnitName = TaskUnit:GetName() - self.Processes = self.Processes or {} - self.Processes[TaskUnitName] = self.Processes[TaskUnitName] or {} - self.Processes[TaskUnitName][#self.Processes[TaskUnitName]+1] = Process - return Process -end - - ---- Remove Processes from @{Task} with key @{Unit} --- @param #TASK_BASE self --- @param #string TaskUnitName --- @return #TASK_BASE self -function TASK_BASE:RemoveProcesses( TaskUnitName ) - - for ProcessID, ProcessData in pairs( self.Processes[TaskUnitName] ) do - local Process = ProcessData -- Process#PROCESS - Process:StopEvents() - Process = nil - self.Processes[TaskUnitName][ProcessID] = nil - self:E( self.Processes[TaskUnitName][ProcessID] ) - end - self.Processes[TaskUnitName] = nil -end - ---- Fail processes from @{Task} with key @{Unit} --- @param #TASK_BASE self --- @param #string TaskUnitName --- @return #TASK_BASE self -function TASK_BASE:FailProcesses( TaskUnitName ) - - for ProcessID, ProcessData in pairs( self.Processes[TaskUnitName] ) do - local Process = ProcessData -- Process#PROCESS - Process.Fsm:Fail() - end -end - ---- Add a FiniteStateMachine to @{Task} with key @{Unit} --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @return #TASK_BASE self -function TASK_BASE:AddStateMachine( TaskUnit, Fsm ) - local TaskUnitName = TaskUnit:GetName() - self.Fsm[TaskUnitName] = self.Fsm[TaskUnitName] or {} - self.Fsm[TaskUnitName][#self.Fsm[TaskUnitName]+1] = Fsm - return Fsm -end - ---- Remove FiniteStateMachines from @{Task} with key @{Unit} --- @param #TASK_BASE self --- @param #string TaskUnitName --- @return #TASK_BASE self -function TASK_BASE:RemoveStateMachines( TaskUnitName ) - - for _, Fsm in pairs( self.Fsm[TaskUnitName] ) do - Fsm = nil - self.Fsm[TaskUnitName][_] = nil - self:E( self.Fsm[TaskUnitName][_] ) - end - self.Fsm[TaskUnitName] = nil -end - ---- Checks if there is a FiniteStateMachine assigned to @{Unit} for @{Task} --- @param #TASK_BASE self --- @param #string TaskUnitName --- @return #TASK_BASE self -function TASK_BASE:HasStateMachine( TaskUnitName ) - - self:F( { TaskUnitName, self.Fsm[TaskUnitName] ~= nil } ) - return ( self.Fsm[TaskUnitName] ~= nil ) -end - - - - - ---- Register a potential new assignment for a new spawned @{Unit}. --- Tasks only get assigned if there are players in it. --- @param #TASK_BASE self --- @param Event#EVENTDATA Event --- @return #TASK_BASE self -function TASK_BASE:_EventAssignUnit( Event ) - if Event.IniUnit then - self:F( Event ) - local TaskUnit = Event.IniUnit - if TaskUnit:IsAlive() then - local TaskPlayerName = TaskUnit:GetPlayerName() - if TaskPlayerName ~= nil then - if not self:HasStateMachine( TaskUnit ) then - -- Check if the task was assigned to the group, if it was assigned to the group, assign to the unit just spawned and initiate the processes. - local TaskGroup = TaskUnit:GetGroup() - if self:IsAssignedToGroup( TaskGroup ) then - self:AssignToUnit( TaskUnit ) - end - end - end - end - end - return nil -end - ---- Catches the "player leave unit" event for a @{Unit} .... --- When a player is an air unit, and leaves the unit: --- --- * and he is not at an airbase runway on the ground, he will fail its task. --- * and he is on an airbase and on the ground, the process for him will just continue to work, he can switch airplanes, and take-off again. --- This is important to model the change from plane types for a player during mission assignment. --- @param #TASK_BASE self --- @param Event#EVENTDATA Event --- @return #TASK_BASE self -function TASK_BASE:_EventPlayerLeaveUnit( Event ) - self:F( Event ) - if Event.IniUnit then - local TaskUnit = Event.IniUnit - local TaskUnitName = Event.IniUnitName - - -- Check if for this unit in the task there is a process ongoing. - if self:HasStateMachine( TaskUnitName ) then - if TaskUnit:IsAir() then - if TaskUnit:IsAboveRunway() then - -- do nothing - else - self:E( "IsNotAboveRunway" ) - -- Player left airplane during an assigned task and was not at an airbase. - self:FailProcesses( TaskUnitName ) - self:UnAssignFromUnit( TaskUnitName ) - end - end - end - - end - return nil -end - ---- UnAssigns a @{Unit} that is left by a player, crashed, dead, .... --- There are only assignments if there are players in it. --- @param #TASK_BASE self --- @param Event#EVENTDATA Event --- @return #TASK_BASE self -function TASK_BASE:_EventDead( Event ) - self:F( Event ) - if Event.IniUnit then - local TaskUnit = Event.IniUnit - local TaskUnitName = Event.IniUnitName - - -- Check if for this unit in the task there is a process ongoing. - if self:HasStateMachine( TaskUnitName ) then - self:FailProcesses( TaskUnitName ) - self:UnAssignFromUnit( TaskUnitName ) - end - - local TaskGroup = Event.IniUnit:GetGroup() - TaskGroup:SetState( TaskGroup, "Assigned", nil ) - end - return nil -end - ---- Gets the Scoring of the task --- @param #TASK_BASE self --- @return Scoring#SCORING Scoring -function TASK_BASE:GetScoring() - return self.Mission:GetScoring() -end - - ---- Gets the Task Index, which is a combination of the Task category, the Task type, the Task name. --- @param #TASK_BASE self --- @return #string The Task ID -function TASK_BASE:GetTaskIndex() - - local TaskCategory = self:GetCategory() - local TaskType = self:GetType() - local TaskName = self:GetName() - - return TaskCategory .. "." ..TaskType .. "." .. TaskName -end - ---- Sets the Name of the Task --- @param #TASK_BASE self --- @param #string TaskName -function TASK_BASE:SetName( TaskName ) - self.TaskName = TaskName -end - ---- Gets the Name of the Task --- @param #TASK_BASE self --- @return #string The Task Name -function TASK_BASE:GetName() - return self.TaskName -end - ---- Sets the Type of the Task --- @param #TASK_BASE self --- @param #string TaskType -function TASK_BASE:SetType( TaskType ) - self.TaskType = TaskType -end - ---- Gets the Type of the Task --- @param #TASK_BASE self --- @return #string TaskType -function TASK_BASE:GetType() - return self.TaskType -end - ---- Sets the Category of the Task --- @param #TASK_BASE self --- @param #string TaskCategory -function TASK_BASE:SetCategory( TaskCategory ) - self.TaskCategory = TaskCategory -end - ---- Gets the Category of the Task --- @param #TASK_BASE self --- @return #string TaskCategory -function TASK_BASE:GetCategory() - return self.TaskCategory -end - ---- Sets the ID of the Task --- @param #TASK_BASE self --- @param #string TaskID -function TASK_BASE:SetID( TaskID ) - self.TaskID = TaskID -end - ---- Gets the ID of the Task --- @param #TASK_BASE self --- @return #string TaskID -function TASK_BASE:GetID() - return self.TaskID -end - - ---- Sets a @{Task} to status **Success**. --- @param #TASK_BASE self -function TASK_BASE:StateSuccess() - self:SetState( self, "State", "Success" ) - return self -end - ---- Is the @{Task} status **Success**. --- @param #TASK_BASE self -function TASK_BASE:IsStateSuccess() - return self:GetStateString() == "Success" -end - ---- Sets a @{Task} to status **Failed**. --- @param #TASK_BASE self -function TASK_BASE:StateFailed() - self:SetState( self, "State", "Failed" ) - return self -end - ---- Is the @{Task} status **Failed**. --- @param #TASK_BASE self -function TASK_BASE:IsStateFailed() - return self:GetStateString() == "Failed" -end - ---- Sets a @{Task} to status **Planned**. --- @param #TASK_BASE self -function TASK_BASE:StatePlanned() - self:SetState( self, "State", "Planned" ) - return self -end - ---- Is the @{Task} status **Planned**. --- @param #TASK_BASE self -function TASK_BASE:IsStatePlanned() - return self:GetStateString() == "Planned" -end - ---- Sets a @{Task} to status **Assigned**. --- @param #TASK_BASE self -function TASK_BASE:StateAssigned() - self:SetState( self, "State", "Assigned" ) - return self -end - ---- Is the @{Task} status **Assigned**. --- @param #TASK_BASE self -function TASK_BASE:IsStateAssigned() - return self:GetStateString() == "Assigned" -end - ---- Sets a @{Task} to status **Hold**. --- @param #TASK_BASE self -function TASK_BASE:StateHold() - self:SetState( self, "State", "Hold" ) - return self -end - ---- Is the @{Task} status **Hold**. --- @param #TASK_BASE self -function TASK_BASE:IsStateHold() - return self:GetStateString() == "Hold" -end - ---- Sets a @{Task} to status **Replanned**. --- @param #TASK_BASE self -function TASK_BASE:StateReplanned() - self:SetState( self, "State", "Replanned" ) - return self -end - ---- Is the @{Task} status **Replanned**. --- @param #TASK_BASE self -function TASK_BASE:IsStateReplanned() - return self:GetStateString() == "Replanned" -end - ---- Gets the @{Task} status. --- @param #TASK_BASE self -function TASK_BASE:GetStateString() - return self:GetState( self, "State" ) -end - ---- Sets a @{Task} briefing. --- @param #TASK_BASE self --- @param #string TaskBriefing --- @return #TASK_BASE self -function TASK_BASE:SetBriefing( TaskBriefing ) - self.TaskBriefing = TaskBriefing - return self -end - - - ---- Adds a score for the TASK to be achieved. --- @param #TASK_BASE self --- @param #string TaskStatus is the status of the TASK when the score needs to be given. --- @param #string ScoreText is a text describing the score that is given according the status. --- @param #number Score is a number providing the score of the status. --- @return #TASK_BASE self -function TASK_BASE:AddScore( TaskStatus, ScoreText, Score ) - self:F2( { TaskStatus, ScoreText, Score } ) - - self.Scores[TaskStatus] = self.Scores[TaskStatus] or {} - self.Scores[TaskStatus].ScoreText = ScoreText - self.Scores[TaskStatus].Score = Score - return self -end - ---- StateMachine callback function for a TASK --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @param StateMachine#STATEMACHINE_TASK Fsm --- @param #string Event --- @param #string From --- @param #string To --- @param Event#EVENTDATA Event -function TASK_BASE:OnAssigned( TaskUnit, Fsm, Event, From, To ) - - self:E("Assigned") - - local TaskGroup = TaskUnit:GetGroup() - - TaskGroup:Message( self.TaskBriefing, 20 ) - - self:RemoveMenuForGroup( TaskGroup ) - self:SetAssignedMenuForGroup( TaskGroup ) - -end - - ---- StateMachine callback function for a TASK --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @param StateMachine#STATEMACHINE_TASK Fsm --- @param #string Event --- @param #string From --- @param #string To --- @param Event#EVENTDATA Event -function TASK_BASE:OnSuccess( TaskUnit, Fsm, Event, From, To ) - - self:E("Success") - - self:UnAssignFromGroups() - - local TaskGroup = TaskUnit:GetGroup() - self.Mission:SetPlannedMenu() - - self:StateSuccess() - - -- The task has become successful, the event catchers can be cleaned. - self:CleanUp() - -end - ---- StateMachine callback function for a TASK --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @param StateMachine#STATEMACHINE_TASK Fsm --- @param #string Event --- @param #string From --- @param #string To --- @param Event#EVENTDATA Event -function TASK_BASE:OnFailed( TaskUnit, Fsm, Event, From, To ) - - self:E( { "Failed for unit ", TaskUnit:GetName(), TaskUnit:GetPlayerName() } ) - - -- A task cannot be "failed", so a task will always be there waiting for players to join. - -- When the player leaves its unit, we will need to check whether he was on the ground or not at an airbase. - -- When the player crashes, we will need to check whether in the group there are other players still active. It not, we reset the task from Assigned to Planned, otherwise, we just leave as Assigned. - - self:UnAssignFromGroups() - self:StatePlanned() - -end - ---- StateMachine callback function for a TASK --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @param StateMachine#STATEMACHINE_TASK Fsm --- @param #string Event --- @param #string From --- @param #string To --- @param Event#EVENTDATA Event -function TASK_BASE:OnStateChange( TaskUnit, Fsm, Event, From, To ) - - if self:IsTrace() then - MESSAGE:New( "Task " .. self.TaskName .. " : " .. Event .. " changed to state " .. To, 15 ):ToAll() - end - - self:E( { Event, From, To } ) - self:SetState( self, "State", To ) - - if self.Scores[To] then - local Scoring = self:GetScoring() - if Scoring then - Scoring:_AddMissionScore( self.Mission, self.Scores[To].ScoreText, self.Scores[To].Score ) - end - end - -end - - ---- @param #TASK_BASE self -function TASK_BASE:_Schedule() - self:F2() - - self.TaskScheduler = SCHEDULER:New( self, _Scheduler, {}, 15, 15 ) - return self -end - - ---- @param #TASK_BASE self -function TASK_BASE._Scheduler() - self:F2() - - return true -end - - - - ---- This module contains the TASK_SEAD classes. --- --- 1) @{#TASK_SEAD} class, extends @{Task#TASK_BASE} +-- 1) @{#TASK_SEAD} class, extends @{Tasking.Task#TASK} -- ================================================= -- The @{#TASK_SEAD} class defines a SEAD task for a @{Set} of Target Units, located at a Target Zone, --- based on the tasking capabilities defined in @{Task#TASK_BASE}. --- The TASK_SEAD is implemented using a @{Statemachine#STATEMACHINE_TASK}, and has the following statuses: +-- based on the tasking capabilities defined in @{Tasking.Task#TASK}. +-- The TASK_SEAD is implemented using a @{Statemachine#FSM_TASK}, and has the following statuses: -- -- * **None**: Start of the process --- * **Planned**: The SEAD task is planned. Upon Planned, the sub-process @{Process_Assign#PROCESS_ASSIGN_ACCEPT} is started to accept the task. --- * **Assigned**: The SEAD task is assigned to a @{Group#GROUP}. Upon Assigned, the sub-process @{Process_Route#PROCESS_ROUTE} is started to route the active Units in the Group to the attack zone. +-- * **Planned**: The SEAD task is planned. Upon Planned, the sub-process @{Process_Fsm.Assign#ACT_ASSIGN_ACCEPT} is started to accept the task. +-- * **Assigned**: The SEAD task is assigned to a @{Wrapper.Group#GROUP}. Upon Assigned, the sub-process @{Process_Fsm.Route#ACT_ROUTE} is started to route the active Units in the Group to the attack zone. -- * **Success**: The SEAD task is successfully completed. Upon Success, the sub-process @{Process_SEAD#PROCESS_SEAD} is started to follow-up successful SEADing of the targets assigned in the task. -- * **Failed**: The SEAD task has failed. This will happen if the player exists the task early, without communicating a possible cancellation to HQ. -- @@ -29651,142 +29139,74 @@ end -- @module Task_SEAD + do -- TASK_SEAD --- The TASK_SEAD class -- @type TASK_SEAD -- @field Set#SET_UNIT TargetSetUnit - -- @extends Task#TASK_BASE + -- @extends Tasking.Task#TASK TASK_SEAD = { ClassName = "TASK_SEAD", } --- Instantiates a new TASK_SEAD. -- @param #TASK_SEAD self - -- @param Mission#MISSION Mission + -- @param Tasking.Mission#MISSION Mission -- @param Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. -- @param #string TaskName The name of the Task. -- @param Set#SET_UNIT UnitSetTargets - -- @param Zone#ZONE_BASE TargetZone + -- @param Core.Zone#ZONE_BASE TargetZone -- @return #TASK_SEAD self function TASK_SEAD:New( Mission, SetGroup, TaskName, TargetSetUnit, TargetZone ) - local self = BASE:Inherit( self, TASK_BASE:New( Mission, SetGroup, TaskName, "SEAD", "A2G" ) ) + local self = BASE:Inherit( self, TASK:New( Mission, SetGroup, TaskName, "SEAD" ) ) -- Tasking.Task_SEAD#TASK_SEAD self:F() self.TargetSetUnit = TargetSetUnit self.TargetZone = TargetZone + + local Fsm = self:GetUnitProcess() - _EVENTDISPATCHER:OnPlayerLeaveUnit( self._EventPlayerLeaveUnit, self ) - _EVENTDISPATCHER:OnDead( self._EventDead, self ) - _EVENTDISPATCHER:OnCrash( self._EventDead, self ) - _EVENTDISPATCHER:OnPilotDead( self._EventDead, self ) + Fsm:AddProcess( "Planned", "Accept", ACT_ASSIGN_ACCEPT:New( self.TaskBriefing ), { Assigned = "Route", Rejected = "Eject" } ) + Fsm:AddProcess( "Assigned", "Route", ACT_ROUTE_ZONE:New( self.TargetZone ), { Arrived = "Update" } ) + Fsm:AddAction ( "Rejected", "Eject", "Planned" ) + Fsm:AddAction ( "Arrived", "Update", "Updated" ) + Fsm:AddProcess( "Updated", "Account", ACT_ACCOUNT_DEADS:New( self.TargetSetUnit, "SEAD" ), { Accounted = "Success" } ) + Fsm:AddProcess( "Updated", "Smoke", ACT_ASSIST_SMOKE_TARGETS_ZONE:New( self.TargetSetUnit, self.TargetZone ) ) + Fsm:AddAction ( "Accounted", "Success", "Success" ) + Fsm:AddAction ( "Failed", "Fail", "Failed" ) + + function Fsm:onenterUpdated( TaskUnit ) + self:E( { self } ) + self:Account() + self:Smoke() + end + +-- _EVENTDISPATCHER:OnPlayerLeaveUnit( self._EventPlayerLeaveUnit, self ) +-- _EVENTDISPATCHER:OnDead( self._EventDead, self ) +-- _EVENTDISPATCHER:OnCrash( self._EventDead, self ) +-- _EVENTDISPATCHER:OnPilotDead( self._EventDead, self ) return self end - - --- Removes a TASK_SEAD. - -- @param #TASK_SEAD self - -- @return #nil - function TASK_SEAD:CleanUp() - - self:GetParent(self):CleanUp() - - return nil - end - - - - --- Assign the @{Task} to a @{Unit}. - -- @param #TASK_SEAD self - -- @param Unit#UNIT TaskUnit - -- @return #TASK_SEAD self - function TASK_SEAD:AssignToUnit( TaskUnit ) - self:F( TaskUnit:GetName() ) - - local ProcessAssign = self:AddProcess( TaskUnit, PROCESS_ASSIGN_ACCEPT:New( self, TaskUnit, self.TaskBriefing ) ) - local ProcessRoute = self:AddProcess( TaskUnit, PROCESS_ROUTE:New( self, TaskUnit, self.TargetZone ) ) - local ProcessSEAD = self:AddProcess( TaskUnit, PROCESS_DESTROY:New( self, "SEAD", TaskUnit, self.TargetSetUnit ) ) - local ProcessSmoke = self:AddProcess( TaskUnit, PROCESS_SMOKE_TARGETS:New( self, TaskUnit, self.TargetSetUnit, self.TargetZone ) ) - - local Process = self:AddStateMachine( TaskUnit, STATEMACHINE_TASK:New( self, TaskUnit, { - initial = 'None', - events = { - { name = 'Next', from = 'None', to = 'Planned' }, - { name = 'Next', from = 'Planned', to = 'Assigned' }, - { name = 'Reject', from = 'Planned', to = 'Rejected' }, - { name = 'Next', from = 'Assigned', to = 'Success' }, - { name = 'Fail', from = 'Assigned', to = 'Failed' }, - { name = 'Fail', from = 'Arrived', to = 'Failed' } - }, - callbacks = { - onNext = self.OnNext, - onRemove = self.OnRemove, - }, - subs = { - Assign = { onstateparent = 'Planned', oneventparent = 'Next', fsm = ProcessAssign.Fsm, event = 'Start', returnevents = { 'Next', 'Reject' } }, - Route = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessRoute.Fsm, event = 'Start' }, - Sead = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessSEAD.Fsm, event = 'Start', returnevents = { 'Next' } }, - Smoke = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessSmoke.Fsm, event = 'Start', } - } - } ) ) - - ProcessRoute:AddScore( "Failed", "failed to destroy a radar", -100 ) - ProcessSEAD:AddScore( "Destroy", "destroyed a radar", 25 ) - ProcessSEAD:AddScore( "Failed", "failed to destroy a radar", -100 ) - self:AddScore( "Success", "Destroyed all target radars", 250 ) - - Process:Next() - - return self - end - - --- StateMachine callback function for a TASK - -- @param #TASK_SEAD self - -- @param StateMachine#STATEMACHINE_TASK Fsm - -- @param #string Event - -- @param #string From - -- @param #string To - -- @param Event#EVENTDATA Event - function TASK_SEAD:OnNext( Fsm, Event, From, To ) - - self:SetState( self, "State", To ) - - end - - + --- @param #TASK_SEAD self function TASK_SEAD:GetPlannedMenuText() return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.TargetSetUnit:GetUnitTypesText() .. " )" end - --- @param #TASK_SEAD self - function TASK_SEAD:_Schedule() - self:F2() - - self.TaskScheduler = SCHEDULER:New( self, _Scheduler, {}, 15, 15 ) - return self - end - - - --- @param #TASK_SEAD self - function TASK_SEAD._Scheduler() - self:F2() - - return true - end - end ---- This module contains the TASK_A2G classes. +--- (AI) (SP) (MP) Tasking for Air to Ground Processes. -- --- 1) @{#TASK_A2G} class, extends @{Task#TASK_BASE} +-- 1) @{#TASK_A2G} class, extends @{Tasking.Task#TASK} -- ================================================= -- The @{#TASK_A2G} class defines a CAS or BAI task of a @{Set} of Target Units, --- located at a Target Zone, based on the tasking capabilities defined in @{Task#TASK_BASE}. --- The TASK_A2G is implemented using a @{Statemachine#STATEMACHINE_TASK}, and has the following statuses: +-- located at a Target Zone, based on the tasking capabilities defined in @{Tasking.Task#TASK}. +-- The TASK_A2G is implemented using a @{Statemachine#FSM_TASK}, and has the following statuses: -- -- * **None**: Start of the process --- * **Planned**: The SEAD task is planned. Upon Planned, the sub-process @{Process_Assign#PROCESS_ASSIGN_ACCEPT} is started to accept the task. --- * **Assigned**: The SEAD task is assigned to a @{Group#GROUP}. Upon Assigned, the sub-process @{Process_Route#PROCESS_ROUTE} is started to route the active Units in the Group to the attack zone. +-- * **Planned**: The SEAD task is planned. Upon Planned, the sub-process @{Process_Fsm.Assign#ACT_ASSIGN_ACCEPT} is started to accept the task. +-- * **Assigned**: The SEAD task is assigned to a @{Wrapper.Group#GROUP}. Upon Assigned, the sub-process @{Process_Fsm.Route#ACT_ROUTE} is started to route the active Units in the Group to the attack zone. -- * **Success**: The SEAD task is successfully completed. Upon Success, the sub-process @{Process_SEAD#PROCESS_SEAD} is started to follow-up successful SEADing of the targets assigned in the task. -- * **Failed**: The SEAD task has failed. This will happen if the player exists the task early, without communicating a possible cancellation to HQ. -- @@ -29801,128 +29221,136 @@ do -- TASK_A2G --- The TASK_A2G class -- @type TASK_A2G - -- @extends Task#TASK_BASE + -- @extends Tasking.Task#TASK TASK_A2G = { ClassName = "TASK_A2G", } --- Instantiates a new TASK_A2G. -- @param #TASK_A2G self - -- @param Mission#MISSION Mission + -- @param Tasking.Mission#MISSION Mission -- @param Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. -- @param #string TaskName The name of the Task. -- @param #string TaskType BAI or CAS -- @param Set#SET_UNIT UnitSetTargets - -- @param Zone#ZONE_BASE TargetZone + -- @param Core.Zone#ZONE_BASE TargetZone -- @return #TASK_A2G self function TASK_A2G:New( Mission, SetGroup, TaskName, TaskType, TargetSetUnit, TargetZone, FACUnit ) - local self = BASE:Inherit( self, TASK_BASE:New( Mission, SetGroup, TaskName, TaskType, "A2G" ) ) + local self = BASE:Inherit( self, TASK:New( Mission, SetGroup, TaskName, TaskType ) ) self:F() self.TargetSetUnit = TargetSetUnit self.TargetZone = TargetZone self.FACUnit = FACUnit + + local Fsm = self:GetUnitProcess() - _EVENTDISPATCHER:OnPlayerLeaveUnit( self._EventPlayerLeaveUnit, self ) - _EVENTDISPATCHER:OnDead( self._EventDead, self ) - _EVENTDISPATCHER:OnCrash( self._EventDead, self ) - _EVENTDISPATCHER:OnPilotDead( self._EventDead, self ) + Fsm:AddProcess( "Planned", "Accept", ACT_ASSIGN_ACCEPT:New( "Attack the Area" ), { Assigned = "Route", Rejected = "Eject" } ) + Fsm:AddProcess( "Assigned", "Route", ACT_ROUTE_ZONE:New( self.TargetZone ), { Arrived = "Update" } ) + Fsm:AddAction ( "Rejected", "Eject", "Planned" ) + Fsm:AddAction ( "Arrived", "Update", "Updated" ) + Fsm:AddProcess( "Updated", "Account", ACT_ACCOUNT_DEADS:New( self.TargetSetUnit, "Attack" ), { Accounted = "Success" } ) + Fsm:AddProcess( "Updated", "Smoke", ACT_ASSIST_SMOKE_TARGETS_ZONE:New( self.TargetSetUnit, self.TargetZone ) ) + --Fsm:AddProcess( "Updated", "JTAC", PROCESS_JTAC:New( self, TaskUnit, self.TargetSetUnit, self.FACUnit ) ) + Fsm:AddAction ( "Accounted", "Success", "Success" ) + Fsm:AddAction ( "Failed", "Fail", "Failed" ) + + function Fsm:onenterUpdated( TaskUnit ) + self:E( { self } ) + self:Account() + self:Smoke() + end + + + + --_EVENTDISPATCHER:OnPlayerLeaveUnit( self._EventPlayerLeaveUnit, self ) + --_EVENTDISPATCHER:OnDead( self._EventDead, self ) + --_EVENTDISPATCHER:OnCrash( self._EventDead, self ) + --_EVENTDISPATCHER:OnPilotDead( self._EventDead, self ) return self end - --- Removes a TASK_A2G. - -- @param #TASK_A2G self - -- @return #nil - function TASK_A2G:CleanUp() - - self:GetParent( self ):CleanUp() - - return nil - end - - - --- Assign the @{Task} to a @{Unit}. - -- @param #TASK_A2G self - -- @param Unit#UNIT TaskUnit - -- @return #TASK_A2G self - function TASK_A2G:AssignToUnit( TaskUnit ) - self:F( TaskUnit:GetName() ) - - local ProcessAssign = self:AddProcess( TaskUnit, PROCESS_ASSIGN_ACCEPT:New( self, TaskUnit, self.TaskBriefing ) ) - local ProcessRoute = self:AddProcess( TaskUnit, PROCESS_ROUTE:New( self, TaskUnit, self.TargetZone ) ) - local ProcessDestroy = self:AddProcess( TaskUnit, PROCESS_DESTROY:New( self, self.TaskType, TaskUnit, self.TargetSetUnit ) ) - local ProcessSmoke = self:AddProcess( TaskUnit, PROCESS_SMOKE_TARGETS:New( self, TaskUnit, self.TargetSetUnit, self.TargetZone ) ) - local ProcessJTAC = self:AddProcess( TaskUnit, PROCESS_JTAC:New( self, TaskUnit, self.TargetSetUnit, self.FACUnit ) ) - - local Process = self:AddStateMachine( TaskUnit, STATEMACHINE_TASK:New( self, TaskUnit, { - initial = 'None', - events = { - { name = 'Next', from = 'None', to = 'Planned' }, - { name = 'Next', from = 'Planned', to = 'Assigned' }, - { name = 'Reject', from = 'Planned', to = 'Rejected' }, - { name = 'Next', from = 'Assigned', to = 'Success' }, - { name = 'Fail', from = 'Assigned', to = 'Failed' }, - { name = 'Fail', from = 'Arrived', to = 'Failed' } - }, - callbacks = { - onNext = self.OnNext, - onRemove = self.OnRemove, - }, - subs = { - Assign = { onstateparent = 'Planned', oneventparent = 'Next', fsm = ProcessAssign.Fsm, event = 'Start', returnevents = { 'Next', 'Reject' } }, - Route = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessRoute.Fsm, event = 'Start' }, - Destroy = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessDestroy.Fsm, event = 'Start', returnevents = { 'Next' } }, - Smoke = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessSmoke.Fsm, event = 'Start', }, - JTAC = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessJTAC.Fsm, event = 'Start', }, - } - } ) ) - - ProcessRoute:AddScore( "Failed", "failed to destroy a ground unit", -100 ) - ProcessDestroy:AddScore( "Destroy", "destroyed a ground unit", 25 ) - ProcessDestroy:AddScore( "Failed", "failed to destroy a ground unit", -100 ) - - Process:Next() - - return self - end - - --- StateMachine callback function for a TASK - -- @param #TASK_A2G self - -- @param StateMachine#STATEMACHINE_TASK Fsm - -- @param #string Event - -- @param #string From - -- @param #string To - -- @param Event#EVENTDATA Event - function TASK_A2G:OnNext( Fsm, Event, From, To, Event ) - - self:SetState( self, "State", To ) - - end - --- @param #TASK_A2G self function TASK_A2G:GetPlannedMenuText() return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.TargetSetUnit:GetUnitTypesText() .. " )" end - - --- @param #TASK_A2G self - function TASK_A2G:_Schedule() - self:F2() - - self.TaskScheduler = SCHEDULER:New( self, _Scheduler, {}, 15, 15 ) - return self end - - - --- @param #TASK_A2G self - function TASK_A2G._Scheduler() - self:F2() - - return true - end - -end + + + +--- The main include file for the MOOSE system. + +--- Core Routines +Include.File( "Utilities/Routines" ) +Include.File( "Utilities/Utils" ) + +--- Core Classes +Include.File( "Core/Base" ) +Include.File( "Core/Scheduler" ) +Include.File( "Core/ScheduleDispatcher") +Include.File( "Core/Event" ) +Include.File( "Core/Menu" ) +Include.File( "Core/Zone" ) +Include.File( "Core/Database" ) +Include.File( "Core/Set" ) +Include.File( "Core/Point" ) +Include.File( "Core/Message" ) +Include.File( "Core/Fsm" ) + +--- Wrapper Classes +Include.File( "Wrapper/Object" ) +Include.File( "Wrapper/Identifiable" ) +Include.File( "Wrapper/Positionable" ) +Include.File( "Wrapper/Controllable" ) +Include.File( "Wrapper/Group" ) +Include.File( "Wrapper/Unit" ) +Include.File( "Wrapper/Client" ) +Include.File( "Wrapper/Static" ) +Include.File( "Wrapper/Airbase" ) + +--- Functional Classes +Include.File( "Functional/Scoring" ) +Include.File( "Functional/CleanUp" ) +Include.File( "Functional/Spawn" ) +Include.File( "Functional/Movement" ) +Include.File( "Functional/Sead" ) +Include.File( "Functional/Escort" ) +Include.File( "Functional/MissileTrainer" ) +Include.File( "Functional/AirbasePolice" ) +Include.File( "Functional/Detection" ) + +--- AI Classes +Include.File( "AI/AI_Balancer" ) +Include.File( "AI/AI_Patrol" ) +Include.File( "AI/AI_Cargo" ) + +--- Actions +Include.File( "Actions/Act_Assign" ) +Include.File( "Actions/Act_Route" ) +Include.File( "Actions/Act_Account" ) +Include.File( "Actions/Act_Assist" ) + +--- Task Handling Classes +Include.File( "Tasking/CommandCenter" ) +Include.File( "Tasking/Mission" ) +Include.File( "Tasking/Task" ) +Include.File( "Tasking/DetectionManager" ) +Include.File( "Tasking/Task_SEAD" ) +Include.File( "Tasking/Task_A2G" ) + + +-- The order of the declarations is important here. Don't touch it. + +--- Declare the event dispatcher based on the EVENT class +_EVENTDISPATCHER = EVENT:New() -- Core.Event#EVENT + +--- Declare the timer dispatcher based on the SCHEDULEDISPATCHER class +_SCHEDULEDISPATCHER = SCHEDULEDISPATCHER:New() -- Core.Timer#SCHEDULEDISPATCHER + +--- Declare the main database object, which is used internally by the MOOSE classes. +_DATABASE = DATABASE:New() -- Database#DATABASE diff --git a/Moose Mission Setup/Moose_Create.bat b/Moose Mission Setup/Moose_Create.bat index 331781f0f..151558dbe 100644 --- a/Moose Mission Setup/Moose_Create.bat +++ b/Moose Mission Setup/Moose_Create.bat @@ -38,66 +38,66 @@ ECHO env.info( 'Moose Generation Timestamp: %2' ) >> Moose.lua COPY /b Moose.lua + "Moose Create Static\Moose_Static_Loader.lua" Moose.lua -COPY /b Moose.lua + %1\Routines.lua Moose.lua -COPY /b Moose.lua + %1\Utils.lua Moose.lua -COPY /b Moose.lua + %1\Base.lua Moose.lua -COPY /b Moose.lua + %1\Object.lua Moose.lua -COPY /b Moose.lua + %1\Identifiable.lua Moose.lua -COPY /b Moose.lua + %1\Positionable.lua Moose.lua -COPY /b Moose.lua + %1\Controllable.lua Moose.lua -COPY /b Moose.lua + %1\Scheduler.lua Moose.lua -COPY /b Moose.lua + %1\Event.lua Moose.lua -COPY /b Moose.lua + %1\Menu.lua Moose.lua -COPY /b Moose.lua + %1\Group.lua Moose.lua -COPY /b Moose.lua + %1\Unit.lua Moose.lua -COPY /b Moose.lua + %1\Zone.lua Moose.lua -COPY /b Moose.lua + %1\Client.lua Moose.lua -COPY /b Moose.lua + %1\Static.lua Moose.lua -COPY /b Moose.lua + %1\Airbase.lua Moose.lua -COPY /b Moose.lua + %1\Database.lua Moose.lua -COPY /b Moose.lua + %1\Set.lua Moose.lua -COPY /b Moose.lua + %1\Point.lua Moose.lua -COPY /b Moose.lua + %1\Moose.lua Moose.lua -COPY /b Moose.lua + %1\Scoring.lua Moose.lua -COPY /b Moose.lua + %1\Cargo.lua Moose.lua -COPY /b Moose.lua + %1\Message.lua Moose.lua -COPY /b Moose.lua + %1\Stage.lua Moose.lua -COPY /b Moose.lua + %1\Task.lua Moose.lua -COPY /b Moose.lua + %1\GoHomeTask.lua Moose.lua -COPY /b Moose.lua + %1\DestroyBaseTask.lua Moose.lua -COPY /b Moose.lua + %1\DestroyGroupsTask.lua Moose.lua -COPY /b Moose.lua + %1\DestroyRadarsTask.lua Moose.lua -COPY /b Moose.lua + %1\DestroyUnitTypesTask.lua Moose.lua -COPY /b Moose.lua + %1\PickupTask.lua Moose.lua -COPY /b Moose.lua + %1\DeployTask.lua Moose.lua -COPY /b Moose.lua + %1\NoTask.lua Moose.lua -COPY /b Moose.lua + %1\RouteTask.lua Moose.lua -COPY /b Moose.lua + %1\Mission.lua Moose.lua -COPY /b Moose.lua + %1\CleanUp.lua Moose.lua -COPY /b Moose.lua + %1\Spawn.lua Moose.lua -COPY /b Moose.lua + %1\Movement.lua Moose.lua -COPY /b Moose.lua + %1\Sead.lua Moose.lua -COPY /b Moose.lua + %1\Escort.lua Moose.lua -COPY /b Moose.lua + %1\MissileTrainer.lua Moose.lua -COPY /b Moose.lua + %1\PatrolZone.lua Moose.lua -COPY /b Moose.lua + %1\AIBalancer.lua Moose.lua -COPY /b Moose.lua + %1\AirbasePolice.lua Moose.lua +rem Core Routines +COPY /b Moose.lua + %1\Utilities\Routines.lua Moose.lua +COPY /b Moose.lua + %1\Utilities\Utils.lua Moose.lua -COPY /b Moose.lua + %1\Detection.lua Moose.lua -COPY /b Moose.lua + %1\DetectionManager.lua Moose.lua +rem Core Classes +COPY /b Moose.lua + %1\Core\Base.lua Moose.lua +COPY /b Moose.lua + %1\Core\Scheduler.lua Moose.lua +COPY /b Moose.lua + %1\Core\ScheduleDispatcher.lua Moose.lua +COPY /b Moose.lua + %1\Core\Event.lua Moose.lua +COPY /b Moose.lua + %1\Core\Menu.lua Moose.lua +COPY /b Moose.lua + %1\Core\Zone.lua Moose.lua +COPY /b Moose.lua + %1\Core\Database.lua Moose.lua +COPY /b Moose.lua + %1\Core\Set.lua Moose.lua +COPY /b Moose.lua + %1\Core\Point.lua Moose.lua +COPY /b Moose.lua + %1\Core\Message.lua Moose.lua +COPY /b Moose.lua + %1\Core\Fsm.lua Moose.lua -COPY /b Moose.lua + %1\StateMachine.lua Moose.lua +rem Wrapper Classes +COPY /b Moose.lua + %1\Wrapper\Object.lua Moose.lua +COPY /b Moose.lua + %1\Wrapper\Identifiable.lua Moose.lua +COPY /b Moose.lua + %1\Wrapper\Positionable.lua Moose.lua +COPY /b Moose.lua + %1\Wrapper\Controllable.lua Moose.lua +COPY /b Moose.lua + %1\Wrapper\Group.lua Moose.lua +COPY /b Moose.lua + %1\Wrapper\Unit.lua Moose.lua +COPY /b Moose.lua + %1\Wrapper\Client.lua Moose.lua +COPY /b Moose.lua + %1\Wrapper\Static.lua Moose.lua +COPY /b Moose.lua + %1\Wrapper\Airbase.lua Moose.lua -COPY /b Moose.lua + %1\Process.lua Moose.lua -COPY /b Moose.lua + %1\Process_Assign.lua Moose.lua -COPY /b Moose.lua + %1\Process_Route.lua Moose.lua -COPY /b Moose.lua + %1\Process_Smoke.lua Moose.lua -COPY /b Moose.lua + %1\Process_Destroy.lua Moose.lua -COPY /b Moose.lua + %1\Process_JTAC.lua Moose.lua +rem Functional Classes +COPY /b Moose.lua + %1\Functional\Scoring.lua Moose.lua +COPY /b Moose.lua + %1\Functional\CleanUp.lua Moose.lua +COPY /b Moose.lua + %1\Functional\Spawn.lua Moose.lua +COPY /b Moose.lua + %1\Functional\Movement.lua Moose.lua +COPY /b Moose.lua + %1\Functional\Sead.lua Moose.lua +COPY /b Moose.lua + %1\Functional\Escort.lua Moose.lua +COPY /b Moose.lua + %1\Functional\MissileTrainer.lua Moose.lua +COPY /b Moose.lua + %1\Functional\AirbasePolice.lua Moose.lua +COPY /b Moose.lua + %1\Functional\Detection.lua Moose.lua -COPY /b Moose.lua + %1\Task.lua Moose.lua -COPY /b Moose.lua + %1\Task_SEAD.lua Moose.lua -COPY /b Moose.lua + %1\Task_A2G.lua Moose.lua +rem AI Classes +COPY /b Moose.lua + %1\AI\AI_Balancer.lua Moose.lua +COPY /b Moose.lua + %1\AI\AI_Patrol.lua Moose.lua +COPY /b Moose.lua + %1\AI\AI_Cargo.lua Moose.lua + + +rem Actions +COPY /b Moose.lua + %1\Actions\Act_Assign.lua Moose.lua +COPY /b Moose.lua + %1\Actions\Act_Route.lua Moose.lua +COPY /b Moose.lua + %1\Actions\Act_Account.lua Moose.lua +COPY /b Moose.lua + %1\Actions\Act_Assist.lua Moose.lua + +rem Task Handling Classes +COPY /b Moose.lua + %1\Tasking\CommandCenter.lua Moose.lua +COPY /b Moose.lua + %1\Tasking\Mission.lua Moose.lua +COPY /b Moose.lua + %1\Tasking\Task.lua Moose.lua +COPY /b Moose.lua + %1\Tasking\DetectionManager.lua Moose.lua +COPY /b Moose.lua + %1\Tasking\Task_SEAD.lua Moose.lua +COPY /b Moose.lua + %1\Tasking\Task_A2G.lua Moose.lua + +COPY /b Moose.lua + %1\Moose.lua Moose.lua COPY /b Moose.lua + "Moose Create Static\Moose_Trace_Off.lua" Moose.lua diff --git a/Moose Test Missions/Moose_Test_AIRBASEPOLICE/Moose_Test_AIRBASEPOLICE_CAUCASUS.lua b/Moose Test Missions/ABP - Airbase Police/APL-001 - Caucasus/APL-001 - Caucasus.lua similarity index 100% rename from Moose Test Missions/Moose_Test_AIRBASEPOLICE/Moose_Test_AIRBASEPOLICE_CAUCASUS.lua rename to Moose Test Missions/ABP - Airbase Police/APL-001 - Caucasus/APL-001 - Caucasus.lua diff --git a/Moose Test Missions/ABP - Airbase Police/APL-001 - Caucasus/APL-001 - Caucasus.miz b/Moose Test Missions/ABP - Airbase Police/APL-001 - Caucasus/APL-001 - Caucasus.miz new file mode 100644 index 000000000..a8b1d54d1 Binary files /dev/null and b/Moose Test Missions/ABP - Airbase Police/APL-001 - Caucasus/APL-001 - Caucasus.miz differ diff --git a/Moose Test Missions/Moose_Test_AIRBASEPOLICE/Moose_Test_AIRBASEPOLICE_NEVADA.lua b/Moose Test Missions/ABP - Airbase Police/APL-002 - Nevada/APL-002 - Nevada.lua similarity index 100% rename from Moose Test Missions/Moose_Test_AIRBASEPOLICE/Moose_Test_AIRBASEPOLICE_NEVADA.lua rename to Moose Test Missions/ABP - Airbase Police/APL-002 - Nevada/APL-002 - Nevada.lua diff --git a/Moose Test Missions/ABP - Airbase Police/APL-002 - Nevada/APL-002 - Nevada.miz b/Moose Test Missions/ABP - Airbase Police/APL-002 - Nevada/APL-002 - Nevada.miz new file mode 100644 index 000000000..9268dd90d Binary files /dev/null and b/Moose Test Missions/ABP - Airbase Police/APL-002 - Nevada/APL-002 - Nevada.miz differ diff --git a/Moose Test Missions/ACL - Airbase Cleaner/ACL-001 - Airbase CleanUp/ACL-001 - Airbase CleanUp.lua b/Moose Test Missions/ACL - Airbase Cleaner/ACL-001 - Airbase CleanUp/ACL-001 - Airbase CleanUp.lua new file mode 100644 index 000000000..da3f23412 --- /dev/null +++ b/Moose Test Missions/ACL - Airbase Cleaner/ACL-001 - Airbase CleanUp/ACL-001 - Airbase CleanUp.lua @@ -0,0 +1,10 @@ + + + + +Clean = CLEANUP:New( 'CLEAN_BATUMI', 180 ) + +SpawnRU = SPAWN:New( 'RU Attack Heli Batumi'):InitLimit( 2, 20 ):SpawnScheduled( 2, 0.2 ) + +SpawnUS = SPAWN:New( 'US Attack Heli Batumi'):InitLimit( 2, 20 ):SpawnScheduled( 2, 0.2 ) + diff --git a/Moose Test Missions/ACL - Airbase Cleaner/ACL-001 - Airbase CleanUp/ACL-001 - Airbase CleanUp.miz b/Moose Test Missions/ACL - Airbase Cleaner/ACL-001 - Airbase CleanUp/ACL-001 - Airbase CleanUp.miz new file mode 100644 index 000000000..a3aa76e86 Binary files /dev/null and b/Moose Test Missions/ACL - Airbase Cleaner/ACL-001 - Airbase CleanUp/ACL-001 - Airbase CleanUp.miz differ diff --git a/Moose Test Missions/AIB - AI Balancing/AIB-001 - Spawned AI/AIB-001 - Spawned AI.lua b/Moose Test Missions/AIB - AI Balancing/AIB-001 - Spawned AI/AIB-001 - Spawned AI.lua new file mode 100644 index 000000000..bf9376140 --- /dev/null +++ b/Moose Test Missions/AIB - AI Balancing/AIB-001 - Spawned AI/AIB-001 - Spawned AI.lua @@ -0,0 +1,35 @@ +--- AI Spawning and Decomissioning +-- +-- === +-- +-- Name: Spawned AI +-- Author: FlightControl +-- Date Created: 07 Dec 2016 +-- +-- # Situation: +-- +-- For the red coalition, 2 client slots are foreseen. +-- We test the AI spawning frequency, validating the number of spawned AI, +-- matching the amount of players that not have joined the mission. +-- When players join, AI should fly to the nearest home base. +-- +-- # Test cases: +-- +-- 1. If no player is logging into the red slots, 2 red AI planes should be alive. +-- 2. If a player joins one red slot, one red AI plane should return to the nearest home base. +-- 3. If two players join the red slots, no AI plane should be spawned, and all airborne AI planes should return to the nearest home base. +-- +-- # Status: TESTED 07 Dec 2016 +-- +-- @module TEST.AI_BALANCER.T001 + +-- Define the SET of CLIENTs from the red coalition. This SET is filled during startup. +local RU_PlanesClientSet = SET_CLIENT:New():FilterCountries( "RUSSIA" ):FilterCategories( "plane" ):FilterStart() + +-- Define the SPAWN object for the red AI plane template. +-- We use InitCleanUp to check every 20 seconds, if there are no planes blocked at the airbase, waithing for take-off. +-- If a blocked plane exists, this red plane will be ReSpawned. +local RU_PlanesSpawn = SPAWN:New( "AI RU" ):InitCleanUp( 20 ) + +-- Start the AI_BALANCER, using the SET of red CLIENTs, and the SPAWN object as a parameter. +local RU_AI_Balancer = AI_BALANCER:New( RU_PlanesClientSet, RU_PlanesSpawn ) diff --git a/Moose Test Missions/AIB - AI Balancing/AIB-001 - Spawned AI/AIB-001 -Spawned AI.miz b/Moose Test Missions/AIB - AI Balancing/AIB-001 - Spawned AI/AIB-001 -Spawned AI.miz new file mode 100644 index 000000000..a7d029212 Binary files /dev/null and b/Moose Test Missions/AIB - AI Balancing/AIB-001 - Spawned AI/AIB-001 -Spawned AI.miz differ diff --git a/Moose Test Missions/AIB - AI Balancing/AIB-002 - Patrol AI/AIB-002 - Patrol AI.lua b/Moose Test Missions/AIB - AI Balancing/AIB-002 - Patrol AI/AIB-002 - Patrol AI.lua new file mode 100644 index 000000000..fa68376cb --- /dev/null +++ b/Moose Test Missions/AIB - AI Balancing/AIB-002 - Patrol AI/AIB-002 - Patrol AI.lua @@ -0,0 +1,49 @@ +--- AI Patrolling +-- +-- === +-- +-- Name: Patrol AI +-- Author: FlightControl +-- Date Created: 7 December 2016 +-- +-- # Situation: +-- +-- For the red coalition, 2 client slots are foreseen. +-- For those players that have not joined the mission, red AI is spawned. +-- The red AI should start patrolling an area until fuel is empty and return to the home base. +-- +-- # Test cases: +-- +-- 1. If no player is logging into the red slots, 2 red AI planes should be alive. +-- 2. If a player joins one red slot, one red AI plane should return to the nearest home base. +-- 3. If two players join the red slots, no AI plane should be spawned, and all airborne AI planes should return to the nearest home base. +-- 4. Spawned AI should take-off from the airbase, and start patrolling the area around Anapa. +-- 5. When the AI is out-of-fuel, it should report it is returning to the home base, and land at Anapa. +-- +-- # Status: DEVELOP 07 Dec 2016 +-- +-- @module TEST.AI_BALANCER.T002 + +-- Define the SET of CLIENTs from the red coalition. This SET is filled during startup. +local RU_PlanesClientSet = SET_CLIENT:New():FilterCountries( "RUSSIA" ):FilterCategories( "plane" ):FilterStart() + +-- Define the SPAWN object for the red AI plane template. +-- We use InitCleanUp to check every 20 seconds, if there are no planes blocked at the airbase, waithing for take-off. +-- If a blocked plane exists, this red plane will be ReSpawned. +local RU_PlanesSpawn = SPAWN:New( "AI RU" ):InitCleanUp( 20 ) + +-- Start the AI_BALANCER, using the SET of red CLIENTs, and the SPAWN object as a parameter. +local RU_AI_Balancer = AI_BALANCER:New( RU_PlanesClientSet, RU_PlanesSpawn ) + +function RU_AI_Balancer:OnAfterSpawned( SetGroup, Event, From, To, AIGroup ) + + local PatrolZoneGroup = GROUP:FindByName( "PatrolZone" ) + local PatrolZone = ZONE_POLYGON:New( "PatrolZone", PatrolZoneGroup ) + + + local Patrol = AI_PATROLZONE:New( PatrolZone, 3000, 6000, 400, 600 ) + Patrol:ManageFuel( 0.2, 60 ) + Patrol:SetControllable( AIGroup ) + Patrol:__Start( 5 ) + +end \ No newline at end of file diff --git a/Moose Test Missions/AIB - AI Balancing/AIB-002 - Patrol AI/AIB-002 - Patrol AI.miz b/Moose Test Missions/AIB - AI Balancing/AIB-002 - Patrol AI/AIB-002 - Patrol AI.miz new file mode 100644 index 000000000..f579a7971 Binary files /dev/null and b/Moose Test Missions/AIB - AI Balancing/AIB-002 - Patrol AI/AIB-002 - Patrol AI.miz differ diff --git a/Moose Test Missions/AIB - AI Balancing/AIB-003 - Two coalitions InitCleanUp test/AIB-003 - Two coalitions InitCleanUp test.lua b/Moose Test Missions/AIB - AI Balancing/AIB-003 - Two coalitions InitCleanUp test/AIB-003 - Two coalitions InitCleanUp test.lua new file mode 100644 index 000000000..ff37d8cdd --- /dev/null +++ b/Moose Test Missions/AIB - AI Balancing/AIB-003 - Two coalitions InitCleanUp test/AIB-003 - Two coalitions InitCleanUp test.lua @@ -0,0 +1,24 @@ + +local US_PlanesClientSet = SET_CLIENT:New():FilterCountries( "USA" ):FilterCategories( "plane" ):FilterStart() + +local US_PlanesSpawn = SPAWN:New( "AI US" ):InitCleanUp( 20 ) +local US_AI_Balancer = AI_BALANCER:New( US_PlanesClientSet, US_PlanesSpawn ) + +local RU_PlanesClientSet = SET_CLIENT:New():FilterCountries( "RUSSIA" ):FilterCategories( "plane" ):FilterStart() +local RU_PlanesSpawn = SPAWN:New( "AI RU" ):InitCleanUp( 20 ) +local RU_AI_Balancer = AI_BALANCER:New( RU_PlanesClientSet, RU_PlanesSpawn ) + +local RU_AirbasesSet = SET_AIRBASE:New():FilterCoalitions("red"):FilterStart() +RU_AirbasesSet:Flush() +RU_AI_Balancer:ReturnToNearestAirbases( 10000, RU_AirbasesSet ) +--RU_AI_Balancer:ReturnToHomeAirbase( 10000 ) + +--local PatrolZoneGroup = GROUP:FindByName( "Patrol Zone Blue" ) +--local PatrolZoneBlue = ZONE_POLYGON:New( "PatrolZone", PatrolZoneGroup ) +--local PatrolZoneB = AI_PATROLZONE:New( PatrolZoneBlue, 3000, 6000, 900, 1100 ):ManageFuel( 0.2, 180 ) +--US_AI_Balancer:SetPatrolZone( PatrolZoneB ) +-- +--local PatrolZoneGroup = GROUP:FindByName( "Patrol Zone Red" ) +--local PatrolZoneRed = ZONE_POLYGON:New( "PatrolZone", PatrolZoneGroup ) +--local PatrolZoneR = AI_PATROLZONE:New( PatrolZoneRed, 3000, 6000, 900, 1100 ):ManageFuel( 0.2, 180 ) +--RU_AI_Balancer:SetPatrolZone( PatrolZoneR ) diff --git a/Moose Test Missions/AIB - AI Balancing/AIB-003 - Two coalitions InitCleanUp test/AIB-003 - Two coalitions InitCleanUp test.miz b/Moose Test Missions/AIB - AI Balancing/AIB-003 - Two coalitions InitCleanUp test/AIB-003 - Two coalitions InitCleanUp test.miz new file mode 100644 index 000000000..3a641c641 Binary files /dev/null and b/Moose Test Missions/AIB - AI Balancing/AIB-003 - Two coalitions InitCleanUp test/AIB-003 - Two coalitions InitCleanUp test.miz differ diff --git a/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_UNIT_Board/Moose_Test_CARGO_UNIT_Board.lua b/Moose Test Missions/CGO - Cargo/CGO-001 - Unit Boarding/CGO-001 - Unit Boarding.lua similarity index 61% rename from Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_UNIT_Board/Moose_Test_CARGO_UNIT_Board.lua rename to Moose Test Missions/CGO - Cargo/CGO-001 - Unit Boarding/CGO-001 - Unit Boarding.lua index b45425e7c..b4a1e62f4 100644 --- a/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_UNIT_Board/Moose_Test_CARGO_UNIT_Board.lua +++ b/Moose Test Missions/CGO - Cargo/CGO-001 - Unit Boarding/CGO-001 - Unit Boarding.lua @@ -1,8 +1,6 @@ -local Mission = MISSION:New( "Transfer Cargo", "High", "Test for Cargo", coalition.side.RED ) - local CargoEngineer = UNIT:FindByName( "Engineer" ) -local InfantryCargo = CARGO_UNIT:New( Mission, CargoEngineer, "Engineer", "Engineer Sven", "81", 2000, 25 ) +local InfantryCargo = AI_CARGO_UNIT:New( CargoEngineer, "Engineer", "Engineer Sven", "81", 2000, 25 ) local CargoCarrier = UNIT:FindByName( "Carrier" ) diff --git a/Moose Test Missions/CGO - Cargo/CGO-001 - Unit Boarding/CGO-001 - Unit Boarding.miz b/Moose Test Missions/CGO - Cargo/CGO-001 - Unit Boarding/CGO-001 - Unit Boarding.miz new file mode 100644 index 000000000..928af9d98 Binary files /dev/null and b/Moose Test Missions/CGO - Cargo/CGO-001 - Unit Boarding/CGO-001 - Unit Boarding.miz differ diff --git a/Moose Test Missions/CGO - Cargo/CGO-002 - Unit Unboarding/CGO-002 - Unit Unboarding.lua b/Moose Test Missions/CGO - Cargo/CGO-002 - Unit Unboarding/CGO-002 - Unit Unboarding.lua new file mode 100644 index 000000000..ff8716a23 --- /dev/null +++ b/Moose Test Missions/CGO - Cargo/CGO-002 - Unit Unboarding/CGO-002 - Unit Unboarding.lua @@ -0,0 +1,11 @@ + +local CargoEngineer = UNIT:FindByName( "Engineer" ) +local InfantryCargo = AI_CARGO_UNIT:New( CargoEngineer, "Engineer", "Engineer Sven", "81", 2000, 25 ) + +local CargoCarrier = UNIT:FindByName( "Carrier" ) + +-- This will Load immediately the Cargo into the Carrier, regardless where the Cargo is. +InfantryCargo:Load( CargoCarrier ) + +-- This will Unboard the Cargo from the Carrier. +InfantryCargo:UnBoard() \ No newline at end of file diff --git a/Moose Test Missions/CGO - Cargo/CGO-002 - Unit Unboarding/CGO-002 - Unit Unboarding.miz b/Moose Test Missions/CGO - Cargo/CGO-002 - Unit Unboarding/CGO-002 - Unit Unboarding.miz new file mode 100644 index 000000000..5e1a54bf1 Binary files /dev/null and b/Moose Test Missions/CGO - Cargo/CGO-002 - Unit Unboarding/CGO-002 - Unit Unboarding.miz differ diff --git a/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_UNIT_Transfer/Moose_Test_CARGO_UNIT_Transfer.lua b/Moose Test Missions/CGO - Cargo/CGO-003 - Unit Transferring/CGO-003 - Unit Transferring.lua similarity index 62% rename from Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_UNIT_Transfer/Moose_Test_CARGO_UNIT_Transfer.lua rename to Moose Test Missions/CGO - Cargo/CGO-003 - Unit Transferring/CGO-003 - Unit Transferring.lua index 3a5d8e273..b5eb57036 100644 --- a/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_UNIT_Transfer/Moose_Test_CARGO_UNIT_Transfer.lua +++ b/Moose Test Missions/CGO - Cargo/CGO-003 - Unit Transferring/CGO-003 - Unit Transferring.lua @@ -1,8 +1,6 @@ -local Mission = MISSION:New( "Transfer Cargo", "High", "Test for Cargo", coalition.side.RED ) - local CargoEngineer = UNIT:FindByName( "Engineer" ) -local InfantryCargo = CARGO_UNIT:New( Mission, CargoEngineer, "Engineer", "Engineer Sven", "81", 2000, 25 ) +local InfantryCargo = AI_CARGO_UNIT:New( CargoEngineer, "Engineer", "Engineer Sven", "81", 2000, 25 ) local CargoCarrierFrom = UNIT:FindByName( "CarrierFrom" ) @@ -14,15 +12,13 @@ local CargoCarrierTo = UNIT:FindByName( "CarrierTo" ) InfantryCargo:Board( CargoCarrierFrom ) -- Once the Cargo has been loaded into the Carrier, drive to a point and unload the Cargo. -InfantryCargo:OnLoaded( - function( Cargo ) - Cargo:UnLoad() - end -) +function InfantryCargo:OnAfterLoaded() + self:__UnBoard( 1 ) + self.OnAfterLoaded = nil +end -- Once the Cargo has been unloaded from the Carrier (the Cargo has arrived to the unload gathering point), OnBoard the Cargo in the other Carrier. -InfantryCargo:OnUnLoaded( - function( Cargo ) - Cargo:Board( CargoCarrierTo ) - end -) +function InfantryCargo:OnAfterUnLoaded() + self:__Board( 1, CargoCarrierTo ) + self.OnAfterUnLoaded = nil +end diff --git a/Moose Test Missions/CGO - Cargo/CGO-003 - Unit Transferring/CGO-003 - Unit Transferring.miz b/Moose Test Missions/CGO - Cargo/CGO-003 - Unit Transferring/CGO-003 - Unit Transferring.miz new file mode 100644 index 000000000..77df5dd47 Binary files /dev/null and b/Moose Test Missions/CGO - Cargo/CGO-003 - Unit Transferring/CGO-003 - Unit Transferring.miz differ diff --git a/Moose Test Missions/CGO - Cargo/CGO-101 - Group Boarding/CGO-101 - Group Boarding.lua b/Moose Test Missions/CGO - Cargo/CGO-101 - Group Boarding/CGO-101 - Group Boarding.lua new file mode 100644 index 000000000..355680ed9 --- /dev/null +++ b/Moose Test Missions/CGO - Cargo/CGO-101 - Group Boarding/CGO-101 - Group Boarding.lua @@ -0,0 +1,16 @@ + +local CargoSet = SET_BASE:New() +CargoSet:Add( "Engineer1", AI_CARGO_UNIT:New( UNIT:FindByName( "Engineer1" ), "Engineers", "Engineer", 81, 2000, 25 ) ) +CargoSet:Add( "Engineer2", AI_CARGO_UNIT:New( UNIT:FindByName( "Engineer2" ), "Engineers", "Engineer", 64, 2000, 25 ) ) +CargoSet:Add( "Engineer3", AI_CARGO_UNIT:New( UNIT:FindByName( "Engineer3" ), "Engineers", "Engineer", 72, 2000, 25 ) ) +CargoSet:Add( "Engineer4", AI_CARGO_UNIT:New( UNIT:FindByName( "Engineer4" ), "Engineers", "Engineer", 69, 2000, 25 ) ) + +local InfantryCargo = AI_CARGO_GROUPED:New( CargoSet, "Engineers", "Engineers", 2000, 25 ) + +local CargoCarrier = UNIT:FindByName( "Carrier" ) + +-- This call will make the Cargo run to the CargoCarrier. +-- Upon arrival at the CargoCarrier, the Cargo will be Loaded into the Carrier. +-- This process is now fully automated. +InfantryCargo:Board( CargoCarrier ) + diff --git a/Moose Test Missions/CGO - Cargo/CGO-101 - Group Boarding/CGO-101 - Group Boarding.miz b/Moose Test Missions/CGO - Cargo/CGO-101 - Group Boarding/CGO-101 - Group Boarding.miz new file mode 100644 index 000000000..47444a5bf Binary files /dev/null and b/Moose Test Missions/CGO - Cargo/CGO-101 - Group Boarding/CGO-101 - Group Boarding.miz differ diff --git a/Moose Test Missions/CGO - Cargo/CGO-102 - Group Unboarding/CGO-102 - Group Unboarding.lua b/Moose Test Missions/CGO - Cargo/CGO-102 - Group Unboarding/CGO-102 - Group Unboarding.lua new file mode 100644 index 000000000..26693a557 --- /dev/null +++ b/Moose Test Missions/CGO - Cargo/CGO-102 - Group Unboarding/CGO-102 - Group Unboarding.lua @@ -0,0 +1,16 @@ + +local CargoSet = SET_BASE:New() +CargoSet:Add( "Engineer1", AI_CARGO_UNIT:New( UNIT:FindByName( "Engineer1" ), "Engineers", "Engineer", 81, 2000, 25 ) ) +CargoSet:Add( "Engineer2", AI_CARGO_UNIT:New( UNIT:FindByName( "Engineer2" ), "Engineers", "Engineer", 64, 2000, 25 ) ) +CargoSet:Add( "Engineer3", AI_CARGO_UNIT:New( UNIT:FindByName( "Engineer3" ), "Engineers", "Engineer", 72, 2000, 25 ) ) +CargoSet:Add( "Engineer4", AI_CARGO_UNIT:New( UNIT:FindByName( "Engineer4" ), "Engineers", "Engineer", 69, 2000, 25 ) ) + +local InfantryCargo = AI_CARGO_GROUPED:New( CargoSet, "Engineers", "Engineers", 2000, 25 ) + +local CargoCarrier = UNIT:FindByName( "Carrier" ) + +-- This will Load immediately the Cargo into the Carrier, regardless where the Cargo is. +InfantryCargo:Load( CargoCarrier ) + +-- This will Unboard the Cargo from the Carrier. +InfantryCargo:UnBoard() \ No newline at end of file diff --git a/Moose Test Missions/CGO - Cargo/CGO-102 - Group Unboarding/CGO-102 - Group Unboarding.miz b/Moose Test Missions/CGO - Cargo/CGO-102 - Group Unboarding/CGO-102 - Group Unboarding.miz new file mode 100644 index 000000000..d3eafecd5 Binary files /dev/null and b/Moose Test Missions/CGO - Cargo/CGO-102 - Group Unboarding/CGO-102 - Group Unboarding.miz differ diff --git a/Moose Test Missions/CGO - Cargo/CGO-103 - Group Transferring/CGO-103 - Group Transferring.lua b/Moose Test Missions/CGO - Cargo/CGO-103 - Group Transferring/CGO-103 - Group Transferring.lua new file mode 100644 index 000000000..06968a6da --- /dev/null +++ b/Moose Test Missions/CGO - Cargo/CGO-103 - Group Transferring/CGO-103 - Group Transferring.lua @@ -0,0 +1,29 @@ + +local CargoSet = SET_BASE:New() +CargoSet:Add( "Engineer1", AI_CARGO_UNIT:New( UNIT:FindByName( "Engineer1" ), "Engineers", "Engineer", 81, 2000, 25 ) ) +CargoSet:Add( "Engineer2", AI_CARGO_UNIT:New( UNIT:FindByName( "Engineer2" ), "Engineers", "Engineer", 64, 2000, 25 ) ) +CargoSet:Add( "Engineer3", AI_CARGO_UNIT:New( UNIT:FindByName( "Engineer3" ), "Engineers", "Engineer", 72, 2000, 25 ) ) +CargoSet:Add( "Engineer4", AI_CARGO_UNIT:New( UNIT:FindByName( "Engineer4" ), "Engineers", "Engineer", 69, 2000, 25 ) ) + +local InfantryCargo = AI_CARGO_GROUPED:New( CargoSet, "Engineers", "Engineers", 2000, 25 ) + +local CargoCarrierFrom = UNIT:FindByName( "CarrierFrom" ) + +local CargoCarrierTo = UNIT:FindByName( "CarrierTo" ) + +-- This call will make the Cargo run to the CargoCarrier. +-- Upon arrival at the CargoCarrier, the Cargo will be Loaded into the Carrier. +-- This process is now fully automated. +InfantryCargo:Board( CargoCarrierFrom ) + +-- Once the Cargo has been loaded into the Carrier, drive to a point and unload the Cargo. +function InfantryCargo:OnAfterLoaded() + self:__UnBoard( 1 ) + self.OnAfterLoaded = nil +end + +-- Once the Cargo has been unloaded from the Carrier (the Cargo has arrived to the unload gathering point), OnBoard the Cargo in the other Carrier. +function InfantryCargo:OnAfterUnLoaded() + self:__Board( 1, CargoCarrierTo ) + self.OnAfterUnLoaded = nil +end diff --git a/Moose Test Missions/CGO - Cargo/CGO-103 - Group Transferring/CGO-103 - Group Transferring.miz b/Moose Test Missions/CGO - Cargo/CGO-103 - Group Transferring/CGO-103 - Group Transferring.miz new file mode 100644 index 000000000..6a38c9367 Binary files /dev/null and b/Moose Test Missions/CGO - Cargo/CGO-103 - Group Transferring/CGO-103 - Group Transferring.miz differ diff --git a/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_PACKAGE_Board/Moose_Test_CARGO_PACKAGE_Board.lua b/Moose Test Missions/CGO - Cargo/CGO-201 - Package Boarding/CGO-201 - Package Boarding.lua similarity index 61% rename from Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_PACKAGE_Board/Moose_Test_CARGO_PACKAGE_Board.lua rename to Moose Test Missions/CGO - Cargo/CGO-201 - Package Boarding/CGO-201 - Package Boarding.lua index 773a96c7a..91f6b537f 100644 --- a/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_PACKAGE_Board/Moose_Test_CARGO_PACKAGE_Board.lua +++ b/Moose Test Missions/CGO - Cargo/CGO-201 - Package Boarding/CGO-201 - Package Boarding.lua @@ -1,8 +1,6 @@ -local Mission = MISSION:New( "Pickup Cargo", "High", "Test for Cargo Pickup", coalition.side.RED ) - local DeliveryUnit = UNIT:FindByName( "Delivery" ) -local Letter = CARGO_PACKAGE:New( Mission, DeliveryUnit, "Letter", "Secret Orders", "0.3", 2000, 25 ) +local Letter = AI_CARGO_PACKAGE:New( DeliveryUnit, "Letter", "Secret Orders", "0.3", 2000, 25 ) local CargoCarrier = UNIT:FindByName( "Carrier" ) diff --git a/Moose Test Missions/CGO - Cargo/CGO-201 - Package Boarding/CGO-201 - Package Boarding.miz b/Moose Test Missions/CGO - Cargo/CGO-201 - Package Boarding/CGO-201 - Package Boarding.miz new file mode 100644 index 000000000..cc0ab2664 Binary files /dev/null and b/Moose Test Missions/CGO - Cargo/CGO-201 - Package Boarding/CGO-201 - Package Boarding.miz differ diff --git a/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_PACKAGE_UnBoard/Moose_Test_CARGO_PACKAGE_UnBoard.lua b/Moose Test Missions/CGO - Cargo/CGO-202 - Package Unboarding/CGO-202 - Package Unboarding.lua similarity index 71% rename from Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_PACKAGE_UnBoard/Moose_Test_CARGO_PACKAGE_UnBoard.lua rename to Moose Test Missions/CGO - Cargo/CGO-202 - Package Unboarding/CGO-202 - Package Unboarding.lua index 259cdc968..a611c8ad6 100644 --- a/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_PACKAGE_UnBoard/Moose_Test_CARGO_PACKAGE_UnBoard.lua +++ b/Moose Test Missions/CGO - Cargo/CGO-202 - Package Unboarding/CGO-202 - Package Unboarding.lua @@ -1,8 +1,6 @@ -local Mission = MISSION:New( "Pickup Cargo", "High", "Test for Cargo Pickup", coalition.side.RED ) - local CargoEngineer = UNIT:FindByName( "Engineer" ) -local InfantryCargo = CARGO_UNIT:New( Mission, CargoEngineer, "Engineer", "Engineer Sven", "81", 2000, 25 ) +local InfantryCargo = AI_CARGO_UNIT:New( CargoEngineer, "Engineer", "Engineer Sven", "81", 2000, 25 ) local CargoCarrier = UNIT:FindByName( "Carrier" ) diff --git a/Moose Test Missions/CGO - Cargo/CGO-202 - Package Unboarding/CGO-202 - Package Unboarding.miz b/Moose Test Missions/CGO - Cargo/CGO-202 - Package Unboarding/CGO-202 - Package Unboarding.miz new file mode 100644 index 000000000..c9b37144a Binary files /dev/null and b/Moose Test Missions/CGO - Cargo/CGO-202 - Package Unboarding/CGO-202 - Package Unboarding.miz differ diff --git a/Moose Test Missions/Moose_Test_DETECTION/Moose_Test_DETECTION.lua b/Moose Test Missions/DET - Detection/DET-001 - Detection Areas/DET-001 - Detection Areas.lua similarity index 100% rename from Moose Test Missions/Moose_Test_DETECTION/Moose_Test_DETECTION.lua rename to Moose Test Missions/DET - Detection/DET-001 - Detection Areas/DET-001 - Detection Areas.lua diff --git a/Moose Test Missions/DET - Detection/DET-001 - Detection Areas/DET-001 - Detection Areas.miz b/Moose Test Missions/DET - Detection/DET-001 - Detection Areas/DET-001 - Detection Areas.miz new file mode 100644 index 000000000..e72284e0c Binary files /dev/null and b/Moose Test Missions/DET - Detection/DET-001 - Detection Areas/DET-001 - Detection Areas.miz differ diff --git a/Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC.lua b/Moose Test Missions/DET - Detection/DET-101 - Detection Reporting/DET-101 - Detection Reporting.lua similarity index 100% rename from Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC.lua rename to Moose Test Missions/DET - Detection/DET-101 - Detection Reporting/DET-101 - Detection Reporting.lua diff --git a/Moose Test Missions/DET - Detection/DET-101 - Detection Reporting/DET-101 - Detection Reporting.miz b/Moose Test Missions/DET - Detection/DET-101 - Detection Reporting/DET-101 - Detection Reporting.miz new file mode 100644 index 000000000..48e2d4d4f Binary files /dev/null and b/Moose Test Missions/DET - Detection/DET-101 - Detection Reporting/DET-101 - Detection Reporting.miz differ diff --git a/Moose Test Missions/Moose_Test_ESCORT/MOOSE_Test_ESCORT.lua b/Moose Test Missions/ESC - Escorting/ESC-001 - Escorting Helicopters/ESC-001 - Escorting Helicopters.lua similarity index 100% rename from Moose Test Missions/Moose_Test_ESCORT/MOOSE_Test_ESCORT.lua rename to Moose Test Missions/ESC - Escorting/ESC-001 - Escorting Helicopters/ESC-001 - Escorting Helicopters.lua diff --git a/Moose Test Missions/ESC - Escorting/ESC-001 - Escorting Helicopters/ESC-001 - Escorting Helicopters.miz b/Moose Test Missions/ESC - Escorting/ESC-001 - Escorting Helicopters/ESC-001 - Escorting Helicopters.miz new file mode 100644 index 000000000..7303511a3 Binary files /dev/null and b/Moose Test Missions/ESC - Escorting/ESC-001 - Escorting Helicopters/ESC-001 - Escorting Helicopters.miz differ diff --git a/Moose Test Missions/Moose_Test_GROUP_TaskFollow/Moose_Test_GROUP_TaskFollow.lua b/Moose Test Missions/GRP - Group Commands/GRP-200 - Follow Group/GRP-200 - Follow Group.lua similarity index 100% rename from Moose Test Missions/Moose_Test_GROUP_TaskFollow/Moose_Test_GROUP_TaskFollow.lua rename to Moose Test Missions/GRP - Group Commands/GRP-200 - Follow Group/GRP-200 - Follow Group.lua diff --git a/Moose Test Missions/GRP - Group Commands/GRP-200 - Follow Group/GRP-200 - Follow Group.miz b/Moose Test Missions/GRP - Group Commands/GRP-200 - Follow Group/GRP-200 - Follow Group.miz new file mode 100644 index 000000000..8d3cefbbc Binary files /dev/null and b/Moose Test Missions/GRP - Group Commands/GRP-200 - Follow Group/GRP-200 - Follow Group.miz differ diff --git a/Moose Test Missions/Moose_Test_GROUP_SwitchWayPoint/Moose_Test_GROUP_SwitchWayPoint.lua b/Moose Test Missions/GRP - Group Commands/GRP-300 - Switch WayPoints/GRP-300 - Switch WayPoints.lua similarity index 100% rename from Moose Test Missions/Moose_Test_GROUP_SwitchWayPoint/Moose_Test_GROUP_SwitchWayPoint.lua rename to Moose Test Missions/GRP - Group Commands/GRP-300 - Switch WayPoints/GRP-300 - Switch WayPoints.lua diff --git a/Moose Test Missions/GRP - Group Commands/GRP-300 - Switch WayPoints/GRP-300 - Switch WayPoints.miz b/Moose Test Missions/GRP - Group Commands/GRP-300 - Switch WayPoints/GRP-300 - Switch WayPoints.miz new file mode 100644 index 000000000..d2c5679c9 Binary files /dev/null and b/Moose Test Missions/GRP - Group Commands/GRP-300 - Switch WayPoints/GRP-300 - Switch WayPoints.miz differ diff --git a/Moose Test Missions/Moose_Test_WRAPPER/Moose_Test_WRAPPER.lua b/Moose Test Missions/GRP - Group Commands/Moose_Test_WRAPPER.lua similarity index 100% rename from Moose Test Missions/Moose_Test_WRAPPER/Moose_Test_WRAPPER.lua rename to Moose Test Missions/GRP - Group Commands/Moose_Test_WRAPPER.lua diff --git a/Moose Test Missions/GRP - Group Commands/Moose_Test_WRAPPER.miz b/Moose Test Missions/GRP - Group Commands/Moose_Test_WRAPPER.miz new file mode 100644 index 000000000..b32feac07 Binary files /dev/null and b/Moose Test Missions/GRP - Group Commands/Moose_Test_WRAPPER.miz differ diff --git a/Moose Test Missions/Moose_Test_MENU_CLIENT/Moose_Test_MENU_CLIENT.lua b/Moose Test Missions/MEN - Menu Options/MEN-001 - Menu Client/MEN-001 - Menu Client.lua similarity index 97% rename from Moose Test Missions/Moose_Test_MENU_CLIENT/Moose_Test_MENU_CLIENT.lua rename to Moose Test Missions/MEN - Menu Options/MEN-001 - Menu Client/MEN-001 - Menu Client.lua index 148f193c8..fa483f103 100644 --- a/Moose Test Missions/Moose_Test_MENU_CLIENT/Moose_Test_MENU_CLIENT.lua +++ b/Moose Test Missions/MEN - Menu Options/MEN-001 - Menu Client/MEN-001 - Menu Client.lua @@ -21,7 +21,7 @@ do MenuStatus[MenuClientName]:Remove() end - --- @param Client#CLIENT MenuClient + --- @param Wrapper.Client#CLIENT MenuClient local function AddStatusMenu( MenuClient ) local MenuClientName = MenuClient:GetName() -- This would create a menu for the red coalition under the MenuCoalitionRed menu object. diff --git a/Moose Test Missions/MEN - Menu Options/MEN-001 - Menu Client/MEN-001 - Menu Client.miz b/Moose Test Missions/MEN - Menu Options/MEN-001 - Menu Client/MEN-001 - Menu Client.miz new file mode 100644 index 000000000..d3ca7eea0 Binary files /dev/null and b/Moose Test Missions/MEN - Menu Options/MEN-001 - Menu Client/MEN-001 - Menu Client.miz differ diff --git a/Moose Test Missions/Moose_Test_MENU_COALITION/Moose_Test_MENU_COALITION.lua b/Moose Test Missions/MEN - Menu Options/MEN-002 - Menu Coalition/MEN-002 - Menu Coalition.lua similarity index 100% rename from Moose Test Missions/Moose_Test_MENU_COALITION/Moose_Test_MENU_COALITION.lua rename to Moose Test Missions/MEN - Menu Options/MEN-002 - Menu Coalition/MEN-002 - Menu Coalition.lua diff --git a/Moose Test Missions/MEN - Menu Options/MEN-002 - Menu Coalition/MEN-002 - Menu Coalition.miz b/Moose Test Missions/MEN - Menu Options/MEN-002 - Menu Coalition/MEN-002 - Menu Coalition.miz new file mode 100644 index 000000000..d64a196dc Binary files /dev/null and b/Moose Test Missions/MEN - Menu Options/MEN-002 - Menu Coalition/MEN-002 - Menu Coalition.miz differ diff --git a/Moose Test Missions/Moose_Test_MENU_GROUP/Moose_Test_MENU_GROUP.lua b/Moose Test Missions/MEN - Menu Options/MEN-003 - Menu Group/MEN-003 - Menu Group.lua similarity index 98% rename from Moose Test Missions/Moose_Test_MENU_GROUP/Moose_Test_MENU_GROUP.lua rename to Moose Test Missions/MEN - Menu Options/MEN-003 - Menu Group/MEN-003 - Menu Group.lua index 35b688016..94d602abf 100644 --- a/Moose Test Missions/Moose_Test_MENU_GROUP/Moose_Test_MENU_GROUP.lua +++ b/Moose Test Missions/MEN - Menu Options/MEN-003 - Menu Group/MEN-003 - Menu Group.lua @@ -21,7 +21,7 @@ do MenuStatus[MenuGroupName]:Remove() end - --- @param Group#GROUP MenuGroup + --- @param Wrapper.Group#GROUP MenuGroup local function AddStatusMenu( MenuGroup ) local MenuGroupName = MenuGroup:GetName() -- This would create a menu for the red coalition under the MenuCoalitionRed menu object. diff --git a/Moose Test Missions/MEN - Menu Options/MEN-003 - Menu Group/MEN-003 - Menu Group.miz b/Moose Test Missions/MEN - Menu Options/MEN-003 - Menu Group/MEN-003 - Menu Group.miz new file mode 100644 index 000000000..cdf2f7527 Binary files /dev/null and b/Moose Test Missions/MEN - Menu Options/MEN-003 - Menu Group/MEN-003 - Menu Group.miz differ diff --git a/Moose Test Missions/Moose_Test_MISSILETRAINER/Moose_Test_MISSILETRAINER.lua b/Moose Test Missions/MIT - Missile Trainer/MIT-001 - Missile Trainer/MIT-001 - Missile Trainer.lua similarity index 100% rename from Moose Test Missions/Moose_Test_MISSILETRAINER/Moose_Test_MISSILETRAINER.lua rename to Moose Test Missions/MIT - Missile Trainer/MIT-001 - Missile Trainer/MIT-001 - Missile Trainer.lua diff --git a/Moose Test Missions/MIT - Missile Trainer/MIT-001 - Missile Trainer/MIT-001 - Missile Trainer.miz b/Moose Test Missions/MIT - Missile Trainer/MIT-001 - Missile Trainer/MIT-001 - Missile Trainer.miz new file mode 100644 index 000000000..14d80023e Binary files /dev/null and b/Moose Test Missions/MIT - Missile Trainer/MIT-001 - Missile Trainer/MIT-001 - Missile Trainer.miz differ diff --git a/Moose Test Missions/MOOSE_Test_Template.miz b/Moose Test Missions/MOOSE_Test_Template.miz index 38f0d34dd..56ca68cd5 100644 Binary files a/Moose Test Missions/MOOSE_Test_Template.miz and b/Moose Test Missions/MOOSE_Test_Template.miz differ diff --git a/Moose Test Missions/Moose_Test_AIBALANCER/Moose_Test_AIBALANCER.lua b/Moose Test Missions/Moose_Test_AIBALANCER/Moose_Test_AIBALANCER.lua deleted file mode 100644 index d0b8c75e0..000000000 --- a/Moose Test Missions/Moose_Test_AIBALANCER/Moose_Test_AIBALANCER.lua +++ /dev/null @@ -1,24 +0,0 @@ - -local US_PlanesClientSet = SET_CLIENT:New():FilterCountries( "USA" ):FilterCategories( "plane" ):FilterStart() -local US_PlanesSpawn1 = SPAWN:New( "AI US 1" ) -local US_PlanesSpawn2 = SPAWN:New( "AI US 2" ) -local US_AIBalancer = AIBALANCER:New( US_PlanesClientSet, { US_PlanesSpawn1, US_PlanesSpawn2 } ) - -local RU_PlanesClientSet = SET_CLIENT:New():FilterCountries( "RUSSIA" ):FilterCategories( "plane" ):FilterStart() -local RU_PlanesSpawn = SPAWN:New( "AI RU" ) -local RU_AIBalancer = AIBALANCER:New( RU_PlanesClientSet, RU_PlanesSpawn ) - -local RU_AirbasesSet = SET_AIRBASE:New():FilterCoalitions("red"):FilterStart() -RU_AirbasesSet:Flush() -RU_AIBalancer:ReturnToNearestAirbases( 10000, RU_AirbasesSet ) ---RU_AIBalancer:ReturnToHomeAirbase( 10000 ) - -local PatrolZoneGroup = GROUP:FindByName( "Patrol Zone Blue" ) -local PatrolZoneBlue = ZONE_POLYGON:New( "PatrolZone", PatrolZoneGroup ) -local PatrolZoneB = PATROLZONE:New( PatrolZoneBlue, 3000, 6000, 900, 1100 ):ManageFuel( 0.2, 180 ) -US_AIBalancer:SetPatrolZone( PatrolZoneB ) - -local PatrolZoneGroup = GROUP:FindByName( "Patrol Zone Red" ) -local PatrolZoneRed = ZONE_POLYGON:New( "PatrolZone", PatrolZoneGroup ) -local PatrolZoneR = PATROLZONE:New( PatrolZoneRed, 3000, 6000, 900, 1100 ):ManageFuel( 0.2, 180 ) -RU_AIBalancer:SetPatrolZone( PatrolZoneR ) diff --git a/Moose Test Missions/Moose_Test_AIBALANCER/Moose_Test_AIBALANCER.miz b/Moose Test Missions/Moose_Test_AIBALANCER/Moose_Test_AIBALANCER.miz deleted file mode 100644 index 877c701d9..000000000 Binary files a/Moose Test Missions/Moose_Test_AIBALANCER/Moose_Test_AIBALANCER.miz and /dev/null differ diff --git a/Moose Test Missions/Moose_Test_AIRBASEPOLICE/Moose_Test_AIRBASEPOLICE-DB.miz b/Moose Test Missions/Moose_Test_AIRBASEPOLICE/Moose_Test_AIRBASEPOLICE-DB.miz deleted file mode 100644 index 1455cf7bc..000000000 Binary files a/Moose Test Missions/Moose_Test_AIRBASEPOLICE/Moose_Test_AIRBASEPOLICE-DB.miz and /dev/null differ diff --git a/Moose Test Missions/Moose_Test_AIRBASEPOLICE/Moose_Test_AIRBASEPOLICE.miz b/Moose Test Missions/Moose_Test_AIRBASEPOLICE/Moose_Test_AIRBASEPOLICE.miz deleted file mode 100644 index 8264967b2..000000000 Binary files a/Moose Test Missions/Moose_Test_AIRBASEPOLICE/Moose_Test_AIRBASEPOLICE.miz and /dev/null differ diff --git a/Moose Test Missions/Moose_Test_AIRBASEPOLICE/Moose_Test_AIRBASEPOLICE_CAUCASUS.miz b/Moose Test Missions/Moose_Test_AIRBASEPOLICE/Moose_Test_AIRBASEPOLICE_CAUCASUS.miz deleted file mode 100644 index 947b27d26..000000000 Binary files a/Moose Test Missions/Moose_Test_AIRBASEPOLICE/Moose_Test_AIRBASEPOLICE_CAUCASUS.miz and /dev/null differ diff --git a/Moose Test Missions/Moose_Test_AIRBASEPOLICE/Moose_Test_AIRBASEPOLICE_NEVADA.miz b/Moose Test Missions/Moose_Test_AIRBASEPOLICE/Moose_Test_AIRBASEPOLICE_NEVADA.miz deleted file mode 100644 index 0d93a435e..000000000 Binary files a/Moose Test Missions/Moose_Test_AIRBASEPOLICE/Moose_Test_AIRBASEPOLICE_NEVADA.miz and /dev/null differ diff --git a/Moose Test Missions/Moose_Test_BASE/Moose_Test_AIRBLANCER_with_Moose.miz b/Moose Test Missions/Moose_Test_BASE/Moose_Test_AIRBLANCER_with_Moose.miz deleted file mode 100644 index 58e4ed8fd..000000000 Binary files a/Moose Test Missions/Moose_Test_BASE/Moose_Test_AIRBLANCER_with_Moose.miz and /dev/null differ diff --git a/Moose Test Missions/Moose_Test_BASE/Moose_Test_AIRBLANCER_without_Moose.miz b/Moose Test Missions/Moose_Test_BASE/Moose_Test_AIRBLANCER_without_Moose.miz deleted file mode 100644 index a2f8532b7..000000000 Binary files a/Moose Test Missions/Moose_Test_BASE/Moose_Test_AIRBLANCER_without_Moose.miz and /dev/null differ diff --git a/Moose Test Missions/Moose_Test_BASE/Moose_Test_BASE.miz b/Moose Test Missions/Moose_Test_BASE/Moose_Test_BASE.miz deleted file mode 100644 index 449927443..000000000 Binary files a/Moose Test Missions/Moose_Test_BASE/Moose_Test_BASE.miz and /dev/null differ diff --git a/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_PACKAGE_Board/MOOSE_Test_CARGO_PACKAGE_Board.miz b/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_PACKAGE_Board/MOOSE_Test_CARGO_PACKAGE_Board.miz deleted file mode 100644 index 0def4c99f..000000000 Binary files a/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_PACKAGE_Board/MOOSE_Test_CARGO_PACKAGE_Board.miz and /dev/null differ diff --git a/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_PACKAGE_UnBoard/MOOSE_Test_CARGO_PACKAGE_UnBoard.miz b/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_PACKAGE_UnBoard/MOOSE_Test_CARGO_PACKAGE_UnBoard.miz deleted file mode 100644 index ce5bc6adc..000000000 Binary files a/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_PACKAGE_UnBoard/MOOSE_Test_CARGO_PACKAGE_UnBoard.miz and /dev/null differ diff --git a/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_UNIT_Board/MOOSE_Test_CARGO_UNIT_Board.miz b/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_UNIT_Board/MOOSE_Test_CARGO_UNIT_Board.miz deleted file mode 100644 index 0d212d6e7..000000000 Binary files a/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_UNIT_Board/MOOSE_Test_CARGO_UNIT_Board.miz and /dev/null differ diff --git a/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_UNIT_Transfer/MOOSE_Test_CARGO_UNIT_Transfer.miz b/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_UNIT_Transfer/MOOSE_Test_CARGO_UNIT_Transfer.miz deleted file mode 100644 index 3705d1155..000000000 Binary files a/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_UNIT_Transfer/MOOSE_Test_CARGO_UNIT_Transfer.miz and /dev/null differ diff --git a/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_UNIT_UnBoard/MOOSE_Test_CARGO_UNIT_UnBoard.miz b/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_UNIT_UnBoard/MOOSE_Test_CARGO_UNIT_UnBoard.miz deleted file mode 100644 index 51caecd15..000000000 Binary files a/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_UNIT_UnBoard/MOOSE_Test_CARGO_UNIT_UnBoard.miz and /dev/null differ diff --git a/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_UNIT_UnBoard/Moose_Test_CARGO_UNIT_UnBoard.lua b/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_UNIT_UnBoard/Moose_Test_CARGO_UNIT_UnBoard.lua deleted file mode 100644 index 020b1073c..000000000 --- a/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_UNIT_UnBoard/Moose_Test_CARGO_UNIT_UnBoard.lua +++ /dev/null @@ -1,13 +0,0 @@ - -local Mission = MISSION:New( "Transfer Cargo", "High", "Test for Cargo", coalition.side.RED ) - -local CargoEngineer = UNIT:FindByName( "Engineer" ) -local InfantryCargo = CARGO_UNIT:New( Mission, CargoEngineer, "Engineer", "Engineer Sven", "81", 2000, 25 ) - -local CargoCarrier = UNIT:FindByName( "Carrier" ) - --- This will Load the Cargo into the Carrier, regardless where the Cargo is. -InfantryCargo:Load( CargoCarrier ) - --- This will Unboard the Cargo from the Carrier. -InfantryCargo:UnLoad() \ No newline at end of file diff --git a/Moose Test Missions/Moose_Test_CLEANUP/Moose_Test_CLEANUP.lua b/Moose Test Missions/Moose_Test_CLEANUP/Moose_Test_CLEANUP.lua deleted file mode 100644 index fe8b75910..000000000 --- a/Moose Test Missions/Moose_Test_CLEANUP/Moose_Test_CLEANUP.lua +++ /dev/null @@ -1,10 +0,0 @@ - - - - -Clean = CLEANUP:New( 'CLEAN_BATUMI', 180 ) - -SpawnRU = SPAWN:New( 'RU Attack Heli Batumi'):Limit( 2, 20 ):SpawnScheduled( 2, 0.2 ) - -SpawnUS = SPAWN:New( 'US Attack Heli Batumi'):Limit( 2, 20 ):SpawnScheduled( 2, 0.2 ) - diff --git a/Moose Test Missions/Moose_Test_CLEANUP/Moose_Test_CLEANUP.miz b/Moose Test Missions/Moose_Test_CLEANUP/Moose_Test_CLEANUP.miz deleted file mode 100644 index 919c94d36..000000000 Binary files a/Moose Test Missions/Moose_Test_CLEANUP/Moose_Test_CLEANUP.miz and /dev/null differ diff --git a/Moose Test Missions/Moose_Test_DETECTION/Moose_Test_DETECTION.miz b/Moose Test Missions/Moose_Test_DETECTION/Moose_Test_DETECTION.miz deleted file mode 100644 index 9eb053464..000000000 Binary files a/Moose Test Missions/Moose_Test_DETECTION/Moose_Test_DETECTION.miz and /dev/null differ diff --git a/Moose Test Missions/Moose_Test_DETECTION/Moose_Test_DETECTION_Laser.miz b/Moose Test Missions/Moose_Test_DETECTION/Moose_Test_DETECTION_Laser.miz deleted file mode 100644 index 33dd4ab92..000000000 Binary files a/Moose Test Missions/Moose_Test_DETECTION/Moose_Test_DETECTION_Laser.miz and /dev/null differ diff --git a/Moose Test Missions/Moose_Test_DETECTION_DISPATCHER/Moose_Test_DETECTION_DISPATCHER.lua b/Moose Test Missions/Moose_Test_DETECTION_DISPATCHER/Moose_Test_DETECTION_DISPATCHER.lua deleted file mode 100644 index 200b3b1c1..000000000 --- a/Moose Test Missions/Moose_Test_DETECTION_DISPATCHER/Moose_Test_DETECTION_DISPATCHER.lua +++ /dev/null @@ -1,12 +0,0 @@ - -local Scoring = SCORING:New( "Detect Demo" ) - -local Mission = MISSION:New( "Attack Detect Mission", "High", "Attack Detect Mission Briefing", coalition.side.RED ):AddScoring( Scoring ) - -local FACSet = SET_GROUP:New():FilterPrefixes( "FAC" ):FilterCoalitions("red"):FilterStart() -local FACDetection = DETECTION_AREAS:New( FACSet, 10000, 3000 ) - -local AttackGroups = SET_GROUP:New():FilterCoalitions( "red" ):FilterPrefixes( "Attack" ):FilterStart() -local CommandCenter = GROUP:FindByName( "HQ" ) -local TaskAssign = DETECTION_DISPATCHER:New( Mission, CommandCenter, AttackGroups, FACDetection ) - diff --git a/Moose Test Missions/Moose_Test_DETECTION_DISPATCHER/Moose_Test_DETECTION_DISPATCHER.miz b/Moose Test Missions/Moose_Test_DETECTION_DISPATCHER/Moose_Test_DETECTION_DISPATCHER.miz deleted file mode 100644 index 5df3254a2..000000000 Binary files a/Moose Test Missions/Moose_Test_DETECTION_DISPATCHER/Moose_Test_DETECTION_DISPATCHER.miz and /dev/null differ diff --git a/Moose Test Missions/Moose_Test_ESCORT/MOOSE_Test_ESCORT.miz b/Moose Test Missions/Moose_Test_ESCORT/MOOSE_Test_ESCORT.miz deleted file mode 100644 index ecc118ad9..000000000 Binary files a/Moose Test Missions/Moose_Test_ESCORT/MOOSE_Test_ESCORT.miz and /dev/null differ diff --git a/Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC.miz b/Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC.miz deleted file mode 100644 index cda45479f..000000000 Binary files a/Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC.miz and /dev/null differ diff --git a/Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC/ABRIS/Database/NAVIGATION.lua b/Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC/ABRIS/Database/NAVIGATION.lua deleted file mode 100644 index 80ab92c30..000000000 --- a/Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC/ABRIS/Database/NAVIGATION.lua +++ /dev/null @@ -1,3262 +0,0 @@ -navigation= -{ - ["date"]= - { - ["year"]=2011, - ["day"]=1, - ["month"]=6, - }, - ["expiration_date"]= - { - ["year"]=2011, - ["day"]=1, - ["month"]=6, - }, - ["region"]="", - ["waypoints"]= - { - ["{6D3E18FB-DC01-4dd7-8833-350C49D72638}"]= - { - ["band"]=115800000, - ["type"]=2, - ["name"]="Krasnodar-Pashkovsky", - ["callsign"]="KRD", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=39.163886994859, - ["latitude"]=45.020832065207, - ["course"]=0, - ["height"]=34.408920288086, - }, - }, - ["{EA7995CF-7D08-4b1a-9361-1F3A073A852F}"]= - { - ["band"]=381000, - ["type"]=2, - ["name"]="Agoy", - ["callsign"]="AG", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=39.034444546264, - ["latitude"]=44.133333334822, - ["course"]=0, - ["height"]=213.66403198242, - }, - ["sub_type"]=8, - }, - ["{451468DB-A355-4580-AECB-E76AB9099B0B}"]= - { - ["band"]=1065000, - ["type"]=2, - ["name"]="KHerson-CHernobaevka", - ["callsign"]="HS", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=32.499999899356, - ["latitude"]=46.666666721389, - ["course"]=0, - ["height"]=0.00014901164104231, - }, - ["sub_type"]=8, - }, - ["Sukhumi-Babushara"]= - { - ["type"]=1, - ["name"]="Sukhumi-Babushara", - ["callsign"]="", - ["runway_length"]=1418, - ["class"]="ABRIS_Waypoint_Airdrome", - ["position"]= - { - ["longitude"]=41.124569220701, - ["latitude"]=42.861288092232, - ["course"]=-1.1085398153117, - ["height"]=11.972095489502, - }, - ["sub_type"]=5, - }, - ["{AE406B8D-660B-4dd5-AD60-022026390323}"]= - { - ["band"]=682000, - ["type"]=2, - ["name"]="Maykop", - ["callsign"]="MA", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=40.14694442455, - ["latitude"]=44.623055556984, - ["course"]=0, - ["height"]=244.64227294922, - }, - ["sub_type"]=8, - }, - ["{16AB7446-613D-4317-B30D-292751D7FFCD}"]= - { - ["band"]=920000, - ["type"]=2, - ["name"]="Primorsko-Akhtarsk", - ["callsign"]="GW", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=38.233333335034, - ["latitude"]=46.050000027573, - ["course"]=0, - ["height"]=0, - }, - ["sub_type"]=8, - }, - ["{3B4BC574-82CB-4126-8675-78AB30E2AFDE}"]= - { - ["band"]=507000, - ["type"]=2, - ["name"]="Bolshevik", - ["callsign"]="ND", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=40.233333214237, - ["latitude"]=45.766666641333, - ["course"]=0, - ["height"]=0.00014901164104231, - }, - ["sub_type"]=8, - }, - ["{37036ACB-BE77-4989-B621-CC04A3A2B8DA}"]= - { - ["band"]=75000000, - ["type"]=2, - ["name"]="Maykop-KHanskaya", - ["callsign"]="", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=40.060506400589, - ["latitude"]=44.699959035543, - ["course"]=-2.4609196186066, - ["height"]=185.35874938965, - }, - ["sub_type"]=112, - }, - ["{7BC7638D-90C7-4d39-96F8-87F8DF0C7025}"]= - { - ["band"]=215000, - ["type"]=2, - ["name"]="Anapa-Vityazevo", - ["callsign"]="P", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=37.372305715442, - ["latitude"]=45.022565731494, - ["course"]=-2.4172778129578, - ["height"]=47.840591430664, - }, - ["sub_type"]=4104, - }, - ["{D408D5E5-E5D1-4907-B812-1E7477F4E3E5}"]= - { - ["band"]=283000, - ["type"]=2, - ["name"]="Mineralnye Vody", - ["callsign"]="M", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=43.046192426651, - ["latitude"]=44.243793576174, - ["course"]=2.0123660564423, - ["height"]=316.21087646484, - }, - ["sub_type"]=4104, - }, - ["{1ADA83B8-9925-4a16-950B-9AD1AB1BDD7F}"]= - { - ["band"]=907000, - ["type"]=2, - ["name"]="Sarmakovo", - ["callsign"]="SR", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=43.116666752873, - ["latitude"]=43.749999966496, - ["course"]=0, - ["height"]=1111.2216796875, - }, - ["sub_type"]=8, - }, - ["{75A75E4E-C026-4dcc-B866-F1B5188B72C8}"]= - { - ["band"]=1065000, - ["type"]=2, - ["name"]="Mozdok", - ["callsign"]="R", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=44.637370992226, - ["latitude"]=43.791323755707, - ["course"]=-1.6970900297165, - ["height"]=158.74685668945, - }, - ["sub_type"]=4104, - }, - ["{D38BD4F7-DEC1-4722-912F-F34891C60A16}"]= - { - ["band"]=443000, - ["type"]=2, - ["name"]="Anapa-Vityazevo", - ["callsign"]="AP", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=37.396339533105, - ["latitude"]=45.039844694135, - ["course"]=-2.4172778129578, - ["height"]=24.43581199646, - }, - ["sub_type"]=4104, - }, - ["Maykop-Khanskaya"]= - { - ["type"]=1, - ["name"]="Maykop-KHanskaya", - ["callsign"]="", - ["runway_length"]=1200, - ["class"]="ABRIS_Waypoint_Airdrome", - ["position"]= - { - ["longitude"]=40.035194185914, - ["latitude"]=44.681241298267, - ["course"]=-2.4609196073963, - ["height"]=180, - }, - ["sub_type"]=6, - }, - ["{C43F7CF2-3825-4d89-88B0-AA34DB0CAFD2}"]= - { - ["band"]=493000, - ["type"]=2, - ["name"]="Krasnodar-Pashkovsky", - ["callsign"]="KR", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=39.133186071592, - ["latitude"]=45.006849978673, - ["course"]=0.82039576768875, - ["height"]=31.459716796875, - }, - ["sub_type"]=4104, - }, - ["{A02321C0-3F1C-4275-879F-0D4FAB5E4602}"]= - { - ["band"]=114300000, - ["type"]=2, - ["name"]="Gelendzhik", - ["callsign"]="GNV", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=38.012222445868, - ["latitude"]=44.572498319582, - ["course"]=0, - ["height"]=25, - }, - }, - ["{B8918D50-540F-4ce9-8F67-D6EE903B5EEA}"]= - { - ["band"]=75000000, - ["type"]=2, - ["name"]="Krymsk", - ["callsign"]="", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=37.94918121689, - ["latitude"]=44.933067755635, - ["course"]=0.68975001573563, - ["height"]=70.84228515625, - }, - ["sub_type"]=112, - }, - ["{00AA1CD4-19CC-406b-96A4-3B82C69B33FD}"]= - { - ["band"]=75000000, - ["type"]=2, - ["name"]="Tbilisi-Lochini", - ["callsign"]="", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=44.977461919109, - ["latitude"]=41.650589722257, - ["course"]=-0.90759545564651, - ["height"]=474.58218383789, - }, - ["sub_type"]=112, - }, - ["{A5288D8F-35D5-46d7-BC47-27E08A40453A}"]= - { - ["band"]=307000, - ["type"]=2, - ["name"]="Lazarevskoe", - ["callsign"]="LA", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=39.337500040245, - ["latitude"]=43.917222231361, - ["course"]=0, - ["height"]=68.265350341797, - }, - ["sub_type"]=8, - }, - ["{79434585-6190-4c2b-8565-EA6C06EED62C}"]= - { - ["band"]=75000000, - ["type"]=2, - ["name"]="Krasnodar-Pashkovsky", - ["callsign"]="", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=39.242923047897, - ["latitude"]=45.068978349367, - ["course"]=-2.3211970329285, - ["height"]=39.660533905029, - }, - ["sub_type"]=112, - }, - ["{01575042-1FD8-48ba-819E-5B83E203ADC4}"]= - { - ["band"]=75000000, - ["type"]=2, - ["name"]="Krasnodar-TZentralny", - ["callsign"]="", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=38.972554727067, - ["latitude"]=45.086445138456, - ["course"]=-1.6230975389481, - ["height"]=29.684894561768, - }, - ["sub_type"]=112, - }, - ["{00B4A3C9-AB85-4547-A302-15B56CB2FDDD}"]= - { - ["band"]=342000, - ["type"]=2, - ["name"]="TZnori", - ["callsign"]="TO", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=46.015555585484, - ["latitude"]=41.630555462492, - ["course"]=0, - ["height"]=0.00014901164104231, - }, - ["sub_type"]=8, - }, - ["Vaziani"]= - { - ["type"]=1, - ["name"]="Vaziani", - ["callsign"]="", - ["runway_length"]=841, - ["class"]="ABRIS_Waypoint_Airdrome", - ["position"]= - { - ["longitude"]=45.027206848999, - ["latitude"]=41.629055409302, - ["course"]=2.3646639161617, - ["height"]=459.74453735352, - }, - ["sub_type"]=6, - }, - ["{2DF525C9-0BDB-4a59-94C6-0C673D2C5865}"]= - { - ["band"]=117100000, - ["type"]=2, - ["name"]="Mineralnye Vody", - ["callsign"]="MNV", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=43.054165210771, - ["latitude"]=44.239444709307, - ["course"]=0, - ["height"]=320, - }, - }, - ["{7904FBDC-946A-44a1-AE06-F42CD84EF8A4}"]= - { - ["band"]=515000, - ["type"]=2, - ["name"]="Rostov-na-Donu tzentr", - ["callsign"]="NV", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=39.633333392466, - ["latitude"]=47.283333305814, - ["course"]=0, - ["height"]=0.00014901164104231, - }, - ["sub_type"]=8, - }, - ["{E359DB92-2210-4bc2-BF28-2D4896AA1A9A}"]= - { - ["band"]=75000000, - ["type"]=2, - ["name"]="Krymsk", - ["callsign"]="", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=37.972489530998, - ["latitude"]=44.950801972146, - ["course"]=0.68975001573563, - ["height"]=27.12328338623, - }, - ["sub_type"]=112, - }, - ["{D83E4F74-98AA-4e95-83C3-6D3886C0063C}"]= - { - ["band"]=117350000, - ["type"]=2, - ["name"]="Krymsk", - ["callsign"]="KW", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=37.992110405136, - ["latitude"]=44.967297189351, - ["course"]=0.68975001573563, - ["height"]=20, - }, - ["sub_type"]=32, - }, - ["Kutaisi"]= - { - ["type"]=1, - ["name"]="Kutaisi", - ["callsign"]="", - ["runway_length"]=850, - ["class"]="ABRIS_Waypoint_Airdrome", - ["position"]= - { - ["longitude"]=42.481231059907, - ["latitude"]=42.177608840793, - ["course"]=1.2915590466874, - ["height"]=45, - }, - ["sub_type"]=5, - }, - ["{C602648E-3C89-4987-8334-881C1EBAC52F}"]= - { - ["band"]=845000, - ["type"]=2, - ["name"]="Kutaisi", - ["callsign"]="KN", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=42.625000275301, - ["latitude"]=42.268333186963, - ["course"]=0, - ["height"]=126.21682739258, - }, - ["sub_type"]=8, - }, - ["{838F92D0-0223-4375-8579-D56902421993}"]= - { - ["band"]=583000, - ["type"]=2, - ["name"]="Mineralnye Vody", - ["callsign"]="MD", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=43.017842556108, - ["latitude"]=44.256694325491, - ["course"]=2.0123660564423, - ["height"]=318.89764404297, - }, - ["sub_type"]=4104, - }, - ["{E420F52C-8772-4356-8043-96A6DD63304A}"]= - { - ["band"]=528000, - ["type"]=2, - ["name"]="Tikhoretzk", - ["callsign"]="UH", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=40.083333455866, - ["latitude"]=45.833333333627, - ["course"]=0, - ["height"]=0.00014901164104231, - }, - ["sub_type"]=8, - }, - ["{3CF9B849-8E92-4033-BFC2-7C59671ECEC2}"]= - { - ["band"]=720000, - ["type"]=2, - ["name"]="Taganrog-YUzhny", - ["callsign"]="UF", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=38.85000014935, - ["latitude"]=47.199999973589, - ["course"]=0, - ["height"]=0, - }, - ["sub_type"]=8, - }, - ["{E974F4EA-E315-4098-A130-9B59879C696A}"]= - { - ["band"]=75000000, - ["type"]=2, - ["name"]="Krasnodar-TZentralny", - ["callsign"]="", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=38.873723610789, - ["latitude"]=45.087918485615, - ["course"]=1.5184950828552, - ["height"]=20.947057723999, - }, - ["sub_type"]=112, - }, - ["{40489709-027A-4325-9425-DC17BACBC1B5}"]= - { - ["band"]=443000, - ["type"]=2, - ["name"]="Anapa-Vityazevo", - ["callsign"]="AN", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=37.299402775931, - ["latitude"]=44.970054188162, - ["course"]=0.72431498765945, - ["height"]=20.469882965088, - }, - ["sub_type"]=4104, - }, - ["{000A169C-D1FD-4058-8FB0-C51BAA217602}"]= - { - ["band"]=75000000, - ["type"]=2, - ["name"]="Krymsk", - ["callsign"]="", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=38.040778144691, - ["latitude"]=45.002664878554, - ["course"]=-2.451842546463, - ["height"]=10.494524002075, - }, - ["sub_type"]=112, - }, - ["{080038D8-492C-4b87-9ECC-DC6D50A7CD21}"]= - { - ["band"]=75000000, - ["type"]=2, - ["name"]="Maykop-KHanskaya", - ["callsign"]="", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=40.00989844675, - ["latitude"]=44.66251686896, - ["course"]=0.68067294359207, - ["height"]=180.70574951172, - }, - ["sub_type"]=112, - }, - ["Mozdok"]= - { - ["type"]=1, - ["name"]="Mozdok", - ["callsign"]="", - ["runway_length"]=1365, - ["class"]="ABRIS_Waypoint_Airdrome", - ["position"]= - { - ["longitude"]=44.599679347932, - ["latitude"]=43.791734916438, - ["course"]=1.4445026599326, - ["height"]=155.00003051758, - }, - ["sub_type"]=6, - }, - ["Gelendzhik"]= - { - ["type"]=1, - ["name"]="Gelendzhik", - ["callsign"]="", - ["runway_length"]=499, - ["class"]="ABRIS_Waypoint_Airdrome", - ["position"]= - { - ["longitude"]=38.011402844087, - ["latitude"]=44.572738119785, - ["course"]=0.69812567198489, - ["height"]=25, - }, - ["sub_type"]=5, - }, - ["{AE534DA8-FFF9-49df-973E-973499E462E7}"]= - { - ["band"]=591000, - ["type"]=2, - ["name"]="Maykop-KHanskaya", - ["callsign"]="D", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=40.00989844675, - ["latitude"]=44.66251686896, - ["course"]=0.68067294359207, - ["height"]=180.70574951172, - }, - ["sub_type"]=4104, - }, - ["Mineralnye Vody"]= - { - ["type"]=1, - ["name"]="Mineralnye Vody", - ["callsign"]="", - ["runway_length"]=1632, - ["class"]="ABRIS_Waypoint_Airdrome", - ["position"]= - { - ["longitude"]=43.081167856217, - ["latitude"]=44.22786123394, - ["course"]=-1.1292264568822, - ["height"]=320, - }, - ["sub_type"]=5, - }, - ["{165039F5-F4DC-4edd-8C37-A5EF2FDD7CF2}"]= - { - ["band"]=515000, - ["type"]=2, - ["name"]="Tiraspol", - ["callsign"]="TH", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=29.600000011301, - ["latitude"]=46.866666639115, - ["course"]=0, - ["height"]=0.00014901164104231, - }, - ["sub_type"]=8, - }, - ["{93747CB7-628A-4ba2-96D7-CD70A3089998}"]= - { - ["band"]=329000, - ["type"]=2, - ["name"]="Nikolaev-Kulbakovo", - ["callsign"]="LC", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=32.100000047281, - ["latitude"]=46.933333325333, - ["course"]=0, - ["height"]=0.00014901164104231, - }, - ["sub_type"]=8, - }, - ["{6479B3BB-1018-49a1-A78A-2250E643028D}"]= - { - ["band"]=740000, - ["type"]=2, - ["name"]="Armavir - TZentr", - ["callsign"]="WM", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=41.116666880031, - ["latitude"]=44.966666653241, - ["course"]=0, - ["height"]=0.00014901164104231, - }, - ["sub_type"]=8, - }, - ["{6146843A-2910-43e8-80AA-41A1935DCEB8}"]= - { - ["band"]=870000, - ["type"]=2, - ["name"]="Kobuleti", - ["callsign"]="KT", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=41.80304745528, - ["latitude"]=41.918633401388, - ["course"]=1.2217304706573, - ["height"]=10.535722732544, - }, - ["sub_type"]=4104, - }, - ["{0860C87D-F9D3-4b68-BB0A-B04BD7B584EE}"]= - { - ["band"]=75000000, - ["type"]=2, - ["name"]="Krymsk", - ["callsign"]="", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=38.017427440086, - ["latitude"]=44.984946612747, - ["course"]=-2.451842546463, - ["height"]=16.346725463867, - }, - ["sub_type"]=112, - }, - ["Nalchik"]= - { - ["type"]=1, - ["name"]="Nalchik", - ["callsign"]="", - ["runway_length"]=751, - ["class"]="ABRIS_Waypoint_Airdrome", - ["position"]= - { - ["longitude"]=43.636542729743, - ["latitude"]=43.514033270925, - ["course"]=0.96877320409637, - ["height"]=430, - }, - ["sub_type"]=5, - }, - ["{5A070E13-71C5-40af-B241-B101BA438AD4}"]= - { - ["band"]=866000, - ["type"]=2, - ["name"]="Sultanskoe", - ["callsign"]="SN", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=42.666666702721, - ["latitude"]=44.591666661132, - ["course"]=0, - ["height"]=260.27188110352, - }, - ["sub_type"]=8, - }, - ["{1B420597-B662-4c7a-B207-36417737871C}"]= - { - ["band"]=75000000, - ["type"]=2, - ["name"]="Mineralnye Vody", - ["callsign"]="", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=43.046192426651, - ["latitude"]=44.243793576174, - ["course"]=2.0123660564423, - ["height"]=316.21087646484, - }, - ["sub_type"]=112, - }, - ["{24A6AD99-F338-464d-914C-7E2191D86CC1}"]= - { - ["band"]=1064000, - ["type"]=2, - ["name"]="Mozdok", - ["callsign"]="D", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=44.561984548905, - ["latitude"]=43.792133293965, - ["course"]=1.4445027112961, - ["height"]=152.32843017578, - }, - ["sub_type"]=4104, - }, - ["{12BC910C-C8B0-45d5-882A-B448A470391D}"]= - { - ["band"]=215000, - ["type"]=2, - ["name"]="Anapa-Vityazevo", - ["callsign"]="N", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=37.323392564705, - ["latitude"]=44.987350312609, - ["course"]=0.72431498765945, - ["height"]=35.370002746582, - }, - ["sub_type"]=4104, - }, - ["{5DD6E99D-BF2E-4c74-8347-2D99E117DD7F}"]= - { - ["band"]=380000, - ["type"]=2, - ["name"]="Voznesensk", - ["callsign"]="MN", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=31.266666711966, - ["latitude"]=47.516666658291, - ["course"]=0, - ["height"]=0.00014901164104231, - }, - ["sub_type"]=8, - }, - ["{7F547727-1F60-4397-87F2-A5DD1C4C0C96}"]= - { - ["band"]=75000000, - ["type"]=2, - ["name"]="Nalchik", - ["callsign"]="", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=43.692770117382, - ["latitude"]=43.534973922883, - ["course"]=-2.1728196144104, - ["height"]=376.85397338867, - }, - ["sub_type"]=112, - }, - ["{42047254-94F7-4667-884F-DA7DC240A298}"]= - { - ["band"]=311000, - ["type"]=2, - ["name"]="Elista", - ["callsign"]="SA", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=44.333333476884, - ["latitude"]=46.373333331268, - ["course"]=0, - ["height"]=0.00014901164104231, - }, - ["sub_type"]=8, - }, - ["{9193E804-CC12-4d34-A2E6-BA8158E77CF4}"]= - { - ["band"]=337000, - ["type"]=2, - ["name"]="Zenzeli", - ["callsign"]="UP", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=47.066666630167, - ["latitude"]=45.933333270641, - ["course"]=0, - ["height"]=0.00014901164104231, - }, - ["sub_type"]=8, - }, - ["{918C5A7C-36AC-4a04-97C7-B9727CD0CF0E}"]= - { - ["band"]=75000000, - ["type"]=2, - ["name"]="Sukhumi-Babushara", - ["callsign"]="", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=41.156747561444, - ["latitude"]=42.846470447617, - ["course"]=-1.1085398197174, - ["height"]=18.967178344727, - }, - ["sub_type"]=112, - }, - ["{E164557E-F439-4b6d-8CD7-AEA59D3C7EB8}"]= - { - ["band"]=75000000, - ["type"]=2, - ["name"]="Anapa-Vityazevo", - ["callsign"]="", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=37.396339533105, - ["latitude"]=45.039844694135, - ["course"]=-2.4172778129578, - ["height"]=24.43581199646, - }, - ["sub_type"]=112, - }, - ["{5BF47F48-091D-44dc-980E-64C6AB62FE5B}"]= - { - ["band"]=352000, - ["type"]=2, - ["name"]="Prikaspysky", - ["callsign"]="PK", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=47.208333343616, - ["latitude"]=46.224999985984, - ["course"]=0, - ["height"]=0.00014901164104231, - }, - ["sub_type"]=8, - }, - ["{17F6D663-7FDE-44c2-8A81-B5D997610269}"]= - { - ["band"]=830000, - ["type"]=2, - ["name"]="Krymsk", - ["callsign"]="O", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=37.972489530998, - ["latitude"]=44.950801972146, - ["course"]=0.68975001573563, - ["height"]=27.12328338623, - }, - ["sub_type"]=4104, - }, - ["{E58FB2D2-9573-4a98-B2B1-63B6F374787D}"]= - { - ["band"]=75000000, - ["type"]=2, - ["name"]="Mineralnye Vody", - ["callsign"]="", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=43.144428100003, - ["latitude"]=44.198998202113, - ["course"]=-1.1292264461517, - ["height"]=303.2926940918, - }, - ["sub_type"]=112, - }, - ["{C8F5F433-463C-488c-A9E5-32A4CD5E3C6C}"]= - { - ["band"]=977000000, - ["type"]=2, - ["name"]="Batumi", - ["callsign"]="BTM", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=41.600418698334, - ["latitude"]=41.610899175899, - ["course"]=0, - ["height"]=9.9645166397095, - }, - ["sub_type"]=4, - }, - ["{F79CFDB7-02CA-4b94-8BCB-685D09BD6E57}"]= - { - ["band"]=602000, - ["type"]=2, - ["name"]="Kuznetzovka", - ["callsign"]="KC", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=40.966666702314, - ["latitude"]=47.400000050324, - ["course"]=0, - ["height"]=0.00014901164104231, - }, - ["sub_type"]=8, - }, - ["{A39B6CCC-2BB4-4e35-B384-F3DA5483B5D2}"]= - { - ["band"]=353000, - ["type"]=2, - ["name"]="Ali", - ["callsign"]="BT", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=43.644722125523, - ["latitude"]=42.096111078745, - ["course"]=0, - ["height"]=720, - }, - ["sub_type"]=8, - }, - ["{D4D9D6BB-0D39-4916-8817-CCE1CCB07E58}"]= - { - ["band"]=75000000, - ["type"]=2, - ["name"]="Tbilisi-Lochini", - ["callsign"]="", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=44.909702215984, - ["latitude"]=41.703205066957, - ["course"]=2.2339971065521, - ["height"]=566.43151855469, - }, - ["sub_type"]=112, - }, - ["{541D404A-D701-41a6-AD4F-79A3EC956EDD}"]= - { - ["band"]=641000, - ["type"]=2, - ["name"]="Vesely", - ["callsign"]="WS", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=40.716666661574, - ["latitude"]=47.116666655808, - ["course"]=0, - ["height"]=0.00014901164104231, - }, - ["sub_type"]=8, - }, - ["{F8BD2A4D-FB6F-4678-AF85-6C2EE7FB3F5B}"]= - { - ["band"]=300500, - ["type"]=2, - ["name"]="Akhilleonsky", - ["callsign"]="AN", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=36.853055551249, - ["latitude"]=45.440000007044, - ["course"]=0, - ["height"]=77.428512573242, - }, - ["sub_type"]=8, - }, - ["{5CFFEBC9-D3B9-4e7c-B0BF-455E68EEAC26}"]= - { - ["band"]=396000, - ["type"]=2, - ["name"]="Sochoy", - ["callsign"]="SH", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=41.350000287381, - ["latitude"]=47.100000003956, - ["course"]=0, - ["height"]=0.00014901164104231, - }, - ["sub_type"]=8, - }, - ["{C6A3D589-C529-4944-86C7-F99B2817B08A}"]= - { - ["band"]=830000, - ["type"]=2, - ["name"]="Krymsk", - ["callsign"]="K", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=38.017427440086, - ["latitude"]=44.984946612747, - ["course"]=-2.451842546463, - ["height"]=16.346725463867, - }, - ["sub_type"]=4104, - }, - ["{F771C182-D706-493c-A878-D4A296FDB897}"]= - { - ["band"]=435000, - ["type"]=2, - ["name"]="Egrikskaya", - ["callsign"]="QG", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=40.666666615451, - ["latitude"]=46.883333402189, - ["course"]=0, - ["height"]=0.00014901164104231, - }, - ["sub_type"]=8, - }, - ["{D2A3D3A9-5250-42d6-9D8C-D92D6B9ABB86}"]= - { - ["band"]=348000, - ["type"]=2, - ["name"]="Odessa - TZentralny", - ["callsign"]="OD", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=30.661111289097, - ["latitude"]=46.306388883259, - ["course"]=0, - ["height"]=0, - }, - ["sub_type"]=8, - }, - ["{627C3B78-4A8A-4013-9B0F-3BB58FD76356}"]= - { - ["band"]=923000, - ["type"]=2, - ["name"]="Tbilisi-Lochini", - ["callsign"]="W", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=44.931974291323, - ["latitude"]=41.685923549921, - ["course"]=2.2339971065521, - ["height"]=518.65625, - }, - ["sub_type"]=4104, - }, - ["{28AD4D52-29A2-4cf6-9AF7-BFDEBFC29194}"]= - { - ["band"]=75000000, - ["type"]=2, - ["name"]="Gudauta", - ["callsign"]="", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=40.578915959804, - ["latitude"]=43.099088468953, - ["course"]=-0.50608837604523, - ["height"]=21, - }, - ["sub_type"]=112, - }, - ["{45D91C15-75E1-411d-9E56-B8418BF69D20}"]= - { - ["band"]=1175000, - ["type"]=2, - ["name"]="Bagaevsky", - ["callsign"]="BA", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=40.366666740007, - ["latitude"]=47.316666743178, - ["course"]=0, - ["height"]=0.00014901164104231, - }, - ["sub_type"]=8, - }, - ["{1EC4E87A-A3AE-4817-8B27-90657B9AD267}"]= - { - ["band"]=320000, - ["type"]=2, - ["name"]="Rostov-na-donu Vostochny", - ["callsign"]="RN", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=39.769999944682, - ["latitude"]=47.225000044919, - ["course"]=0, - ["height"]=0.00014901164104231, - }, - ["sub_type"]=8, - }, - ["{3C275B48-0ED2-45ca-9290-A8DD9936A121}"]= - { - ["band"]=1050000, - ["type"]=2, - ["name"]="Beslan", - ["callsign"]="CH", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=44.539993803635, - ["latitude"]=43.215477513059, - ["course"]=1.6318835020065, - ["height"]=543.41900634766, - }, - ["sub_type"]=4104, - }, - ["{09C449BB-9725-4bdd-B6E8-68BE9A7639BF}"]= - { - ["band"]=75000000, - ["type"]=2, - ["name"]="Senaki-Kolkhi", - ["callsign"]="", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=42.018370629355, - ["latitude"]=42.245052975629, - ["course"]=1.6528304815292, - ["height"]=23.467279434204, - }, - ["sub_type"]=112, - }, - ["{30B5CBDE-6BD6-4592-9609-60A3D813A0B6}"]= - { - ["band"]=1210000, - ["type"]=2, - ["name"]="Peredovaya", - ["callsign"]="PR", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=41.466666760671, - ["latitude"]=44.116666648632, - ["course"]=0, - ["height"]=632.06207275391, - }, - ["sub_type"]=8, - }, - ["{8CEAEA62-A738-4dea-9A02-1025DE47813E}"]= - { - ["band"]=1025000, - ["type"]=2, - ["name"]="Krasny", - ["callsign"]="KS", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=40.349999897763, - ["latitude"]=47.19999997768, - ["course"]=0, - ["height"]=0.00014901164104231, - }, - ["sub_type"]=8, - }, - ["{CAA378D7-D3A3-4f3a-9049-1572540279AB}"]= - { - ["band"]=730000, - ["type"]=2, - ["name"]="Stavropol-SHpakovskoe", - ["callsign"]="KT", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=42.181666600404, - ["latitude"]=45.121666657445, - ["course"]=0, - ["height"]=0.00014901164104231, - }, - ["sub_type"]=8, - }, - ["{D988C10B-A2ED-41f2-B9BB-6F67813416F2}"]= - { - ["band"]=214000, - ["type"]=2, - ["name"]="Kropotkin", - ["callsign"]="KP", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=40.566666483437, - ["latitude"]=45.450000023144, - ["course"]=0, - ["height"]=0.00014901164104231, - }, - ["sub_type"]=8, - }, - ["{23F1A703-1D37-4be7-988A-B36AB86834F9}"]= - { - ["band"]=324000, - ["type"]=2, - ["name"]="Ladozhskaya", - ["callsign"]="RF", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=39.916666860444, - ["latitude"]=45.283333318466, - ["course"]=0, - ["height"]=80.809173583984, - }, - ["sub_type"]=8, - }, - ["{F84B9F47-2400-433d-BC6F-63A9E8C73765}"]= - { - ["band"]=662000, - ["type"]=2, - ["name"]="Smolenskaya", - ["callsign"]="SM", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=38.796388878985, - ["latitude"]=44.787499998116, - ["course"]=0, - ["height"]=78.472930908203, - }, - ["sub_type"]=8, - }, - ["{44689E55-C104-4400-8896-69F4C81290C5}"]= - { - ["band"]=156000, - ["type"]=2, - ["name"]="Senaki-Kolkhi", - ["callsign"]="TI", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=41.986436464281, - ["latitude"]=42.249570732027, - ["course"]=1.6528304815292, - ["height"]=21.472751617432, - }, - ["sub_type"]=4104, - }, - ["{140E2DC1-6A9B-49a9-8C9B-9100458ACCDF}"]= - { - ["band"]=312000, - ["type"]=2, - ["name"]="Ryazanskaya", - ["callsign"]="XT", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=39.566666698538, - ["latitude"]=44.96666666409, - ["course"]=0, - ["height"]=40.001003265381, - }, - ["sub_type"]=8, - }, - ["{76038C66-8A7F-45df-93BB-4FC7DBE221B3}"]= - { - ["band"]=75000000, - ["type"]=2, - ["name"]="Sochi-Adler", - ["callsign"]="", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=39.911168781419, - ["latitude"]=43.43519586845, - ["course"]=1.0821976661682, - ["height"]=4.3798685073853, - }, - ["sub_type"]=112, - }, - ["{3F715664-543F-4521-97AE-70DDE04904D1}"]= - { - ["band"]=240000, - ["type"]=2, - ["name"]="Krasnodar-Pashkovsky", - ["callsign"]="L", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=39.216192032071, - ["latitude"]=45.053867564375, - ["course"]=-2.3211970329285, - ["height"]=38.959575653076, - }, - ["sub_type"]=4104, - }, - ["{192BA661-61AB-4353-936D-ABF3A5F504E4}"]= - { - ["band"]=1182000, - ["type"]=2, - ["name"]="Teplorechensky", - ["callsign"]="TP", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=43.533333031236, - ["latitude"]=44.155000024262, - ["course"]=0, - ["height"]=263.61557006836, - }, - ["sub_type"]=8, - }, - ["{8CED2827-A2B9-4eac-9302-CB5F0FEE14C4}"]= - { - ["band"]=1005000000, - ["type"]=2, - ["name"]="Kutaisi", - ["callsign"]="KTS", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=42.479213330824, - ["latitude"]=42.178443019783, - ["course"]=0, - ["height"]=45, - }, - ["sub_type"]=4, - }, - ["{70065F51-9EBE-4a8a-8667-047BD1B789B9}"]= - { - ["band"]=995000, - ["type"]=2, - ["name"]="Sukhumi-Babushara", - ["callsign"]="A", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=41.156747561444, - ["latitude"]=42.846470447617, - ["course"]=-1.1085398197174, - ["height"]=18.967178344727, - }, - ["sub_type"]=4104, - }, - ["{F3578977-50C9-45de-A9A3-21491A314857}"]= - { - ["band"]=384000, - ["type"]=2, - ["name"]="Alushta", - ["callsign"]="AL", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=34.398333334698, - ["latitude"]=44.675000017156, - ["course"]=0, - ["height"]=-2.8605400075321e-006, - }, - ["sub_type"]=8, - }, - ["{A14E485A-E9E6-4ecf-BDC8-7834DAAC7E83}"]= - { - ["band"]=493000, - ["type"]=2, - ["name"]="Krasnodar-Pashkovsky", - ["callsign"]="LD", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=39.242923047897, - ["latitude"]=45.068978349367, - ["course"]=-2.3211970329285, - ["height"]=39.660533905029, - }, - ["sub_type"]=4104, - }, - ["{CF2EA740-F402-4000-837C-D16CBB7274E4}"]= - { - ["band"]=625000, - ["type"]=2, - ["name"]="Krasnodar-TZentralny", - ["callsign"]="MB", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=38.873723610789, - ["latitude"]=45.087918485615, - ["course"]=1.5184950828552, - ["height"]=20.947057723999, - }, - ["sub_type"]=4104, - }, - ["Krasnodar-Pashkovsky"]= - { - ["type"]=1, - ["name"]="Krasnodar-Pashkovsky", - ["callsign"]="", - ["runway_length"]=1146, - ["class"]="ABRIS_Waypoint_Airdrome", - ["position"]= - { - ["longitude"]=39.18802521083, - ["latitude"]=45.037929060373, - ["course"]=0.8203957479386, - ["height"]=34, - }, - ["sub_type"]=5, - }, - ["{5652EE65-0F0A-4cdc-83CF-ED292F942469}"]= - { - ["band"]=75000000, - ["type"]=2, - ["name"]="Krasnodar-Pashkovsky", - ["callsign"]="", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=39.216192032071, - ["latitude"]=45.053867564375, - ["course"]=-2.3211970329285, - ["height"]=38.959575653076, - }, - ["sub_type"]=112, - }, - ["{FA95D7EE-453B-49bc-A693-E254CF3F04FB}"]= - { - ["band"]=75000000, - ["type"]=2, - ["name"]="Anapa-Vityazevo", - ["callsign"]="", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=37.323392564705, - ["latitude"]=44.987350312609, - ["course"]=0.72431498765945, - ["height"]=35.370002746582, - }, - ["sub_type"]=112, - }, - ["{F4507C36-4CD0-415c-92C2-715CBFBA4D35}"]= - { - ["band"]=75000000, - ["type"]=2, - ["name"]="Krasnodar-Pashkovsky", - ["callsign"]="", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=39.159873850437, - ["latitude"]=45.02198271643, - ["course"]=0.82039576768875, - ["height"]=34.374855041504, - }, - ["sub_type"]=112, - }, - ["Novorossiysk"]= - { - ["type"]=1, - ["name"]="Novorossysk", - ["callsign"]="", - ["runway_length"]=499, - ["class"]="ABRIS_Waypoint_Airdrome", - ["position"]= - { - ["longitude"]=37.778238633922, - ["latitude"]=44.668068985973, - ["course"]=0.73304063019746, - ["height"]=40, - }, - ["sub_type"]=6, - }, - ["{EE57F7DA-163A-4b5c-A64B-FB0ED146243D}"]= - { - ["band"]=75000000, - ["type"]=2, - ["name"]="Tbilisi-Lochini", - ["callsign"]="", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=44.999697078343, - ["latitude"]=41.633299706948, - ["course"]=-0.90759545564651, - ["height"]=459.41839599609, - }, - ["sub_type"]=112, - }, - ["{314E0FD7-2696-4a5c-82B4-2011ADF8B2A0}"]= - { - ["band"]=462000, - ["type"]=2, - ["name"]="Gori", - ["callsign"]="OZ", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=44.132221940757, - ["latitude"]=42.016666555471, - ["course"]=0, - ["height"]=604.78430175781, - }, - ["sub_type"]=8, - }, - ["{4F81A9F3-CC8A-42c1-944A-E7F7D569E73B}"]= - { - ["band"]=283000, - ["type"]=2, - ["name"]="Mineralnye Vody", - ["callsign"]="N", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=43.116124501349, - ["latitude"]=44.211919658446, - ["course"]=-1.1292264461517, - ["height"]=320, - }, - ["sub_type"]=4104, - }, - ["{720C9A85-7BE6-427e-A35F-356397226419}"]= - { - ["band"]=240000, - ["type"]=2, - ["name"]="Krasnodar-Pashkovsky", - ["callsign"]="K", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=39.159873850437, - ["latitude"]=45.02198271643, - ["course"]=0.82039576768875, - ["height"]=34.374855041504, - }, - ["sub_type"]=4104, - }, - ["{A3CD46FC-1C4B-4caf-9665-76EB09596E97}"]= - { - ["band"]=75000000, - ["type"]=2, - ["name"]="Krasnodar-Pashkovsky", - ["callsign"]="", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=39.133186071592, - ["latitude"]=45.006849978673, - ["course"]=0.82039576768875, - ["height"]=31.459716796875, - }, - ["sub_type"]=112, - }, - ["{5EEEA845-728A-4617-B73D-AB7B95C0BF14}"]= - { - ["band"]=770000, - ["type"]=2, - ["name"]="Mariupol", - ["callsign"]="MA", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=37.449999910781, - ["latitude"]=47.083333342844, - ["course"]=0, - ["height"]=0.00014901164104231, - }, - ["sub_type"]=8, - }, - ["{9C4955CF-8F2E-4447-8ADA-D34BAD346FA1}"]= - { - ["band"]=342000, - ["type"]=2, - ["name"]="Berdyansk", - ["callsign"]="BD", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=36.766666662967, - ["latitude"]=46.816666632055, - ["course"]=0, - ["height"]=0.00014901164104231, - }, - ["sub_type"]=8, - }, - ["{F825D676-6710-4ab0-ACDD-D4CCE63018EA}"]= - { - ["band"]=470000, - ["type"]=2, - ["name"]="Taganrog-TZentr", - ["callsign"]="TN", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=38.833333449616, - ["latitude"]=47.250000040204, - ["course"]=0, - ["height"]=0.00014901164104231, - }, - ["sub_type"]=8, - }, - ["{1C17D867-0AC8-48cd-BE17-769F10A3F5A9}"]= - { - ["band"]=745000, - ["type"]=2, - ["name"]="Astrakhan", - ["callsign"]="AD", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=47.88333319274, - ["latitude"]=46.349999974477, - ["course"]=0, - ["height"]=0.00014901164104231, - }, - ["sub_type"]=8, - }, - ["{78C892D0-62A0-41f4-B216-04D6590D370B}"]= - { - ["band"]=75000000, - ["type"]=2, - ["name"]="Krasnodar-TZentralny", - ["callsign"]="", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=39.006761833256, - ["latitude"]=45.0859151671, - ["course"]=-1.6230975389481, - ["height"]=26.556413650513, - }, - ["sub_type"]=112, - }, - ["{8AE7C2EE-7B4E-4e28-B01A-BB9212B906B4}"]= - { - ["band"]=75000000, - ["type"]=2, - ["name"]="Nalchik", - ["callsign"]="", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=43.663293698368, - ["latitude"]=43.524002039126, - ["course"]=-2.1728196144104, - ["height"]=430, - }, - ["sub_type"]=112, - }, - ["{F6384C59-B29A-493f-9BC5-AF5333DB076B}"]= - { - ["band"]=625000, - ["type"]=2, - ["name"]="Krasnodar-TZentralny", - ["callsign"]="Oyo", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=39.006761833256, - ["latitude"]=45.0859151671, - ["course"]=-1.6230975389481, - ["height"]=26.556413650513, - }, - ["sub_type"]=4104, - }, - ["{F0D00BE3-D414-4504-BFA8-57456343F983}"]= - { - ["band"]=750000, - ["type"]=2, - ["name"]="Limanskoe", - ["callsign"]="KE", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=30.00000011322, - ["latitude"]=46.666666667637, - ["course"]=0, - ["height"]=0.00014901164104231, - }, - ["sub_type"]=8, - }, - ["{6A4D7CD3-15A6-45de-A551-C9CE55BC23F5}"]= - { - ["band"]=303000, - ["type"]=2, - ["name"]="Krasnodar-TZentralny", - ["callsign"]="O", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=38.972554727067, - ["latitude"]=45.086445138456, - ["course"]=-1.6230975389481, - ["height"]=29.684894561768, - }, - ["sub_type"]=4104, - }, - ["{657FD001-F740-41b7-A9D1-DC8EE6AF85B4}"]= - { - ["band"]=75000000, - ["type"]=2, - ["name"]="Maykop-KHanskaya", - ["callsign"]="", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=40.084087240926, - ["latitude"]=44.717379887168, - ["course"]=-2.4609196186066, - ["height"]=216.91372680664, - }, - ["sub_type"]=112, - }, - ["{603826C0-DA1C-4b20-9DEF-C75B680D6F7C}"]= - { - ["band"]=303000, - ["type"]=2, - ["name"]="Krasnodar-TZentralny", - ["callsign"]="M", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=38.907935555841, - ["latitude"]=45.087418185601, - ["course"]=1.5184950828552, - ["height"]=30, - }, - ["sub_type"]=4104, - }, - ["{4352EE5E-8A18-4807-BE5F-FEE655FCC959}"]= - { - ["band"]=430000, - ["type"]=2, - ["name"]="Batumi", - ["callsign"]="LU", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=41.606666808765, - ["latitude"]=41.613887679663, - ["course"]=0, - ["height"]=9.984808921814, - }, - ["sub_type"]=4104, - }, - ["{0431F814-C3D7-45a4-AF13-A67A43CCAAAC}"]= - { - ["band"]=116350000, - ["type"]=2, - ["name"]="Krasnodar-TZentralny", - ["callsign"]="MB", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=38.928128326203, - ["latitude"]=45.084424147438, - ["course"]=1.5184950828552, - ["height"]=30, - }, - ["sub_type"]=32, - }, - ["{C382D774-5121-4c23-86E6-7116164FE603}"]= - { - ["band"]=75000000, - ["type"]=2, - ["name"]="Kobuleti", - ["callsign"]="", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=41.83442330282, - ["latitude"]=41.924511693408, - ["course"]=1.2217304706573, - ["height"]=18.219362258911, - }, - ["sub_type"]=112, - }, - ["{FC180E41-CAE0-4bcc-A657-E1DFE3C861BF}"]= - { - ["band"]=75000000, - ["type"]=2, - ["name"]="Krasnodar-TZentralny", - ["callsign"]="", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=38.907935555841, - ["latitude"]=45.087418185601, - ["course"]=1.5184950828552, - ["height"]=30, - }, - ["sub_type"]=112, - }, - ["{F9C00BBF-3CDC-4d8b-AB37-0273E9315F75}"]= - { - ["band"]=950000, - ["type"]=2, - ["name"]="Komissarovo", - ["callsign"]="KM", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=35.016666687172, - ["latitude"]=46.266666673198, - ["course"]=0, - ["height"]=0, - }, - ["sub_type"]=8, - }, - ["{039B4712-1F13-48e1-A67F-F8CB815216BF}"]= - { - ["band"]=75000000, - ["type"]=2, - ["name"]="Tbilisi-Lochini", - ["callsign"]="", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=44.931974291323, - ["latitude"]=41.685923549921, - ["course"]=2.2339971065521, - ["height"]=518.65625, - }, - ["sub_type"]=112, - }, - ["{3672E620-C61C-4318-B474-69B4CE6034C5}"]= - { - ["band"]=862000, - ["type"]=2, - ["name"]="Sukhoy", - ["callsign"]="SH", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=41.350000287381, - ["latitude"]=47.100000003956, - ["course"]=0, - ["height"]=0.00014901164104231, - }, - ["sub_type"]=8, - }, - ["{C5931F6D-6525-41e3-8F22-9994690BED2E}"]= - { - ["band"]=389000, - ["type"]=2, - ["name"]="SHiryaevo", - ["callsign"]="SH", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=30.280000014199, - ["latitude"]=47.403333287882, - ["course"]=0, - ["height"]=0.00014901164104231, - }, - ["sub_type"]=8, - }, - ["{9D7CEAEB-8E52-4775-B825-CA62E0EEB43F}"]= - { - ["band"]=75000000, - ["type"]=2, - ["name"]="Kutaisi", - ["callsign"]="", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=42.419028299913, - ["latitude"]=42.169763922025, - ["course"]=1.2915591001511, - ["height"]=34.913619995117, - }, - ["sub_type"]=112, - }, - ["{322D5B49-F59D-4d7f-9655-1DAA7AF94344}"]= - { - ["band"]=420000, - ["type"]=2, - ["name"]="Dzhubga", - ["callsign"]="RQ", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=38.701389026511, - ["latitude"]=44.320555530626, - ["course"]=0, - ["height"]=63.587341308594, - }, - ["sub_type"]=8, - }, - ["{498DF95B-CAA4-4642-BCD6-2ED7AC366214}"]= - { - ["band"]=408000, - ["type"]=2, - ["name"]="Krymsk", - ["callsign"]="KW", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=38.040778144691, - ["latitude"]=45.002664878554, - ["course"]=-2.451842546463, - ["height"]=10.494524002075, - }, - ["sub_type"]=4104, - }, - ["{6E655001-7D12-400e-A4F5-DF3B48005E06}"]= - { - ["band"]=690000, - ["type"]=2, - ["name"]="Dmitrovka", - ["callsign"]="DM", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=35.058611107534, - ["latitude"]=45.489444431369, - ["course"]=0, - ["height"]=31.682126998901, - }, - ["sub_type"]=8, - }, - ["{839D524E-BB23-4a7c-87B6-436BBDFABCC8}"]= - { - ["band"]=408000, - ["type"]=2, - ["name"]="Krymsk", - ["callsign"]="yuO", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=37.94918121689, - ["latitude"]=44.933067755635, - ["course"]=0.68975001573563, - ["height"]=70.84228515625, - }, - ["sub_type"]=4104, - }, - ["{099B1B66-552F-44e7-817A-9E395150DBA0}"]= - { - ["band"]=1050000, - ["type"]=2, - ["name"]="Kerch", - ["callsign"]="KS", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=36.400000028215, - ["latitude"]=45.366666665613, - ["course"]=0, - ["height"]=36.203407287598, - }, - ["sub_type"]=8, - }, - ["{7B6744C4-3945-4a19-AD08-8ED6A1E8BCDD}"]= - { - ["band"]=485000, - ["type"]=2, - ["name"]="Kakhovka", - ["callsign"]="KH", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=33.496666642802, - ["latitude"]=46.808333264695, - ["course"]=0, - ["height"]=0.00014901164104231, - }, - ["sub_type"]=8, - }, - ["{0B6C84AE-2A8C-4623-8103-B85C66F9F627}"]= - { - ["band"]=440000, - ["type"]=2, - ["name"]="Artziz", - ["callsign"]="BT", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=29.383333152565, - ["latitude"]=45.950000001714, - ["course"]=0, - ["height"]=0.00014901164104231, - }, - ["sub_type"]=8, - }, - ["Batumi"]= - { - ["type"]=1, - ["name"]="Batumi", - ["callsign"]="", - ["runway_length"]=822, - ["class"]="ABRIS_Waypoint_Airdrome", - ["position"]= - { - ["longitude"]=41.600567780743, - ["latitude"]=41.609371050613, - ["course"]=2.1918691046878, - ["height"]=9.9645166397095, - }, - ["sub_type"]=5, - }, - ["{DB73D633-0760-4765-88D9-2125C3B61A28}"]= - { - ["band"]=395000, - ["type"]=2, - ["name"]="Gudauta", - ["callsign"]="ZC", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=40.578915959804, - ["latitude"]=43.099088468953, - ["course"]=-0.50608837604523, - ["height"]=21, - }, - ["sub_type"]=4104, - }, - ["{6DBC6B40-38E2-457d-BC84-50521AC3D072}"]= - { - ["band"]=350000, - ["type"]=2, - ["name"]="Nalchik", - ["callsign"]="N", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=43.663293698368, - ["latitude"]=43.524002039126, - ["course"]=-2.1728196144104, - ["height"]=430, - }, - ["sub_type"]=4104, - }, - ["Sochi-Adler"]= - { - ["type"]=1, - ["name"]="Sochi-Adler", - ["callsign"]="", - ["runway_length"]=1151, - ["class"]="ABRIS_Waypoint_Airdrome", - ["position"]= - { - ["longitude"]=39.941488914396, - ["latitude"]=43.444479177936, - ["course"]=-2.059394842015, - ["height"]=30, - }, - ["sub_type"]=5, - }, - ["{2F54CFDB-24E0-4d59-B9E4-0D0A9923194D}"]= - { - ["band"]=591000, - ["type"]=2, - ["name"]="Maykop-KHanskaya", - ["callsign"]="R", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=40.060506400589, - ["latitude"]=44.699959035543, - ["course"]=-2.4609196186066, - ["height"]=185.35874938965, - }, - ["sub_type"]=4104, - }, - ["{791B532E-7081-43fd-9F90-4484BCEC2CE5}"]= - { - ["band"]=670000, - ["type"]=2, - ["name"]="Lyubimovka", - ["callsign"]="LB", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=34.898333338244, - ["latitude"]=45.625000014443, - ["course"]=0, - ["height"]=50.001346588135, - }, - ["sub_type"]=8, - }, - ["{F908523F-DF9E-4f63-9423-913582798F99}"]= - { - ["band"]=435000, - ["type"]=2, - ["name"]="Tbilisi-Lochini", - ["callsign"]="N", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=44.977461919109, - ["latitude"]=41.650589722257, - ["course"]=-0.90759545564651, - ["height"]=474.58218383789, - }, - ["sub_type"]=4104, - }, - ["{9286A133-DEE7-4006-9B75-B6279CCDF74F}"]= - { - ["band"]=525000, - ["type"]=2, - ["name"]="Mozdok", - ["callsign"]="DO", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=44.528781245533, - ["latitude"]=43.792473730478, - ["course"]=1.4445027112961, - ["height"]=153.11080932617, - }, - ["sub_type"]=4104, - }, - ["{7D1F43A3-D30B-40d3-A3B1-4D7D546B8E53}"]= - { - ["band"]=288000, - ["type"]=2, - ["name"]="Maykop-KHanskaya", - ["callsign"]="DG", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=39.986362565764, - ["latitude"]=44.645077755977, - ["course"]=0.68067294359207, - ["height"]=177.95664978027, - }, - ["sub_type"]=4104, - }, - ["{FB1CF904-A634-44ca-9AB7-DE7357600758}"]= - { - ["band"]=1030000, - ["type"]=2, - ["name"]="Mikolaivka", - ["callsign"]="NK", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=31.973055617388, - ["latitude"]=47.087500015661, - ["course"]=0, - ["height"]=0.00014901164104231, - }, - ["sub_type"]=8, - }, - ["Anapa-Vityazevo"]= - { - ["type"]=1, - ["name"]="Anapa-Vityazevo", - ["callsign"]="", - ["runway_length"]=1050, - ["class"]="ABRIS_Waypoint_Airdrome", - ["position"]= - { - ["longitude"]=37.34784162046, - ["latitude"]=45.004960953476, - ["course"]=0.72431498648244, - ["height"]=45, - }, - ["sub_type"]=5, - }, - ["{FA899F12-3708-4520-BD03-AC5B5FDAABFE}"]= - { - ["band"]=300500, - ["type"]=2, - ["name"]="KHenichesky", - ["callsign"]="GE", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=34.816111103122, - ["latitude"]=46.182500024812, - ["course"]=0, - ["height"]=0, - }, - ["sub_type"]=8, - }, - ["{7418FEDC-377B-40ba-BC1D-48D15E804B89}"]= - { - ["band"]=75000000, - ["type"]=2, - ["name"]="Mozdok", - ["callsign"]="", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=44.637370992226, - ["latitude"]=43.791323755707, - ["course"]=-1.6970900297165, - ["height"]=158.74685668945, - }, - ["sub_type"]=112, - }, - ["Gudauta"]= - { - ["type"]=1, - ["name"]="Gudauta", - ["callsign"]="", - ["runway_length"]=850, - ["class"]="ABRIS_Waypoint_Airdrome", - ["position"]= - { - ["longitude"]=40.569781849787, - ["latitude"]=43.11425961416, - ["course"]=-0.50608837006882, - ["height"]=21, - }, - ["sub_type"]=6, - }, - ["{9D2D723F-CD98-46a3-AA95-444DD810AE6C}"]= - { - ["band"]=718000, - ["type"]=2, - ["name"]="Nalchik", - ["callsign"]="NL", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=43.692770117382, - ["latitude"]=43.534973922883, - ["course"]=-2.1728196144104, - ["height"]=376.85397338867, - }, - ["sub_type"]=4104, - }, - ["{6D4B161C-F96D-4e66-BB97-E25774343A67}"]= - { - ["band"]=129000, - ["type"]=2, - ["name"]="Senaki-Kolkhi", - ["callsign"]="I", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=42.018370629355, - ["latitude"]=42.245052975629, - ["course"]=1.6528304815292, - ["height"]=23.467279434204, - }, - ["sub_type"]=4104, - }, - ["{3B9069B3-92F0-474c-9629-35F940A7D6CB}"]= - { - ["band"]=1175000, - ["type"]=2, - ["name"]="Dobrushino", - ["callsign"]="DO", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=33.361666685089, - ["latitude"]=45.378055556009, - ["course"]=0, - ["height"]=50.001235961914, - }, - ["sub_type"]=8, - }, - ["{3CF293EE-56CF-464c-9695-E85D44690077}"]= - { - ["band"]=905000, - ["type"]=2, - ["name"]="Parutyne", - ["callsign"]="PA", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=31.905000082189, - ["latitude"]=46.695000058446, - ["course"]=0, - ["height"]=0, - }, - ["sub_type"]=8, - }, - ["{EE522F8B-8046-4d7a-A3C2-C166B81CBA14}"]= - { - ["band"]=680000, - ["type"]=2, - ["name"]="Skadovsk", - ["callsign"]="SK", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=32.916666617971, - ["latitude"]=46.13333333967, - ["course"]=0, - ["height"]=0.00014901164104231, - }, - ["sub_type"]=8, - }, - ["{879CF29F-5127-4638-ABEA-408B83CE1520}"]= - { - ["band"]=625000, - ["type"]=2, - ["name"]="Buyalyk", - ["callsign"]="DW", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=30.699999929771, - ["latitude"]=46.899999972094, - ["course"]=0, - ["height"]=0.00014901164104231, - }, - ["sub_type"]=8, - }, - ["{E2AA221C-41BD-4ba0-94D4-7A25521A4A02}"]= - { - ["band"]=845000, - ["type"]=2, - ["name"]="Kutaisi", - ["callsign"]="KN", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=42.419028299913, - ["latitude"]=42.169763922025, - ["course"]=1.2915591001511, - ["height"]=34.913619995117, - }, - ["sub_type"]=4104, - }, - ["{E9E09B4F-A4F7-402b-AB07-2F3501C5003E}"]= - { - ["band"]=309500, - ["type"]=2, - ["name"]="Vorontzovsky front - Odessa", - ["callsign"]="WR", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=30.723888999574, - ["latitude"]=46.496111141931, - ["course"]=0, - ["height"]=0, - }, - ["sub_type"]=8, - }, - ["{F82C7B3B-58AB-4f37-A7A9-D8CA6CD7F845}"]= - { - ["band"]=75000000, - ["type"]=2, - ["name"]="Beslan", - ["callsign"]="", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=44.539993803635, - ["latitude"]=43.215477513059, - ["course"]=1.6318835020065, - ["height"]=543.41900634766, - }, - ["sub_type"]=112, - }, - ["{3AAD8E39-71D3-4952-BC6A-6FAA66100740}"]= - { - ["band"]=117650000, - ["type"]=2, - ["name"]="Maykop-KHanskaya", - ["callsign"]="DG", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=40.04064281205, - ["latitude"]=44.683721927129, - ["course"]=-2.4609196186066, - ["height"]=180, - }, - ["sub_type"]=32, - }, - ["{B379E11C-7F88-4389-89FB-6F53E7388747}"]= - { - ["band"]=75000000, - ["type"]=2, - ["name"]="Anapa-Vityazevo", - ["callsign"]="", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=37.299402775931, - ["latitude"]=44.970054188162, - ["course"]=0.72431498765945, - ["height"]=20.469882965088, - }, - ["sub_type"]=112, - }, - ["{14A2F917-A2D6-483e-9E49-77E90E074B3A}"]= - { - ["band"]=455000, - ["type"]=2, - ["name"]="Kizlyar", - ["callsign"]="KZ", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=46.71666661869, - ["latitude"]=43.83333334862, - ["course"]=0, - ["height"]=0.00014901164104231, - }, - ["sub_type"]=8, - }, - ["{91DCC6FD-AE23-4f03-BB94-228873913F50}"]= - { - ["band"]=75000000, - ["type"]=2, - ["name"]="Senaki-Kolkhi", - ["callsign"]="", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=41.986436464281, - ["latitude"]=42.249570732027, - ["course"]=1.6528304815292, - ["height"]=21.472751617432, - }, - ["sub_type"]=112, - }, - ["{B4DD584A-3268-4ed8-A6BC-0F16BB1FFFC1}"]= - { - ["band"]=309500, - ["type"]=2, - ["name"]="Tendorovsky", - ["callsign"]="TD", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=31.522777764021, - ["latitude"]=46.318611082896, - ["course"]=0, - ["height"]=0, - }, - ["sub_type"]=8, - }, - ["{D2BF3722-93DC-4e25-BD1F-97B99641315A}"]= - { - ["band"]=580000, - ["type"]=2, - ["name"]="Kacha", - ["callsign"]="KC", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=33.566666669312, - ["latitude"]=44.749999997154, - ["course"]=0, - ["height"]=44.705688476563, - }, - ["sub_type"]=8, - }, - ["{B08554A5-CFF1-444b-86B9-F2D4A36AC450}"]= - { - ["band"]=525000, - ["type"]=2, - ["name"]="Gali", - ["callsign"]="DA", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=41.783333639785, - ["latitude"]=42.600000022704, - ["course"]=0, - ["height"]=153.07803344727, - }, - ["sub_type"]=8, - }, - ["{C4DA1D59-0135-4810-9FF1-98A3D0FD368C}"]= - { - ["band"]=75000000, - ["type"]=2, - ["name"]="Mozdok", - ["callsign"]="", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=44.670566359666, - ["latitude"]=43.790951233713, - ["course"]=-1.6970900297165, - ["height"]=154.5718536377, - }, - ["sub_type"]=112, - }, - ["{C21DB5FB-78BF-440d-9645-D31513B98BAF}"]= - { - ["band"]=992000000, - ["type"]=2, - ["name"]="Senaki-Kolkhi", - ["callsign"]="TSK", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=42.04702008109, - ["latitude"]=42.242086543242, - ["course"]=0, - ["height"]=13.082180023193, - }, - ["sub_type"]=4, - }, - ["{818BCBB4-141F-4eb1-9199-593346420D68}"]= - { - ["band"]=761000, - ["type"]=2, - ["name"]="Sochi-Adler", - ["callsign"]="SO", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=39.911168781419, - ["latitude"]=43.43519586845, - ["course"]=1.0821976661682, - ["height"]=4.3798685073853, - }, - ["sub_type"]=4104, - }, - ["Krymsk"]= - { - ["type"]=1, - ["name"]="Krymsk", - ["callsign"]="", - ["runway_length"]=900, - ["class"]="ABRIS_Waypoint_Airdrome", - ["position"]= - { - ["longitude"]=37.994951766799, - ["latitude"]=44.967876819866, - ["course"]=-2.4518425368978, - ["height"]=20, - }, - ["sub_type"]=6, - }, - ["{2F6A39A0-1F26-406d-BCE6-48274102904D}"]= - { - ["band"]=288000, - ["type"]=2, - ["name"]="Maykop-KHanskaya", - ["callsign"]="RK", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=40.084087240926, - ["latitude"]=44.717379887168, - ["course"]=-2.4609196186066, - ["height"]=216.91372680664, - }, - ["sub_type"]=4104, - }, - ["{9D270DB1-9A0D-4704-8B9A-D00843BA6AF8}"]= - { - ["band"]=342000, - ["type"]=2, - ["name"]="Tbilisi-Lochini", - ["callsign"]="WP", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=44.909702215984, - ["latitude"]=41.703205066957, - ["course"]=2.2339971065521, - ["height"]=566.43151855469, - }, - ["sub_type"]=4104, - }, - ["{641A95FC-D90C-4313-9A4B-783DEB01D5E5}"]= - { - ["band"]=995000, - ["type"]=2, - ["name"]="Kislovodsk", - ["callsign"]="KW", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=42.650000029649, - ["latitude"]=43.941666665735, - ["course"]=0, - ["height"]=800.17553710938, - }, - ["sub_type"]=8, - }, - ["{86B21180-D7E9-47c3-8012-74C6092DDF4E}"]= - { - ["band"]=300500, - ["type"]=2, - ["name"]="Ilichevsk", - ["callsign"]="IO", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=35.419722196701, - ["latitude"]=45.014444444519, - ["course"]=0, - ["height"]=1.8064863979816e-005, - }, - ["sub_type"]=8, - }, - ["{7D1BE209-B33E-4e6f-88DE-FA4D55E9AFE9}"]= - { - ["band"]=735000, - ["type"]=2, - ["name"]="Kalaus", - ["callsign"]="BJ", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=45.216666759111, - ["latitude"]=43.466666663757, - ["course"]=0, - ["height"]=343.54925537109, - }, - ["sub_type"]=8, - }, - ["{65B77E50-8B99-48a8-A355-3A6844C86A2A}"]= - { - ["band"]=830000, - ["type"]=2, - ["name"]="Gronzy - Vostochny", - ["callsign"]="WK", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=45.700000076209, - ["latitude"]=43.383333345666, - ["course"]=0, - ["height"]=0.00014901164104231, - }, - ["sub_type"]=8, - }, - ["Kobuleti"]= - { - ["type"]=1, - ["name"]="Kobuleti", - ["callsign"]="", - ["runway_length"]=799, - ["class"]="ABRIS_Waypoint_Airdrome", - ["position"]= - { - ["longitude"]=41.863479703668, - ["latitude"]=41.929946253786, - ["course"]=1.2217305208975, - ["height"]=18, - }, - ["sub_type"]=5, - }, - ["Soganlug"]= - { - ["type"]=1, - ["name"]="Soganlug", - ["callsign"]="", - ["runway_length"]=835, - ["class"]="ABRIS_Waypoint_Airdrome", - ["position"]= - { - ["longitude"]=44.938407241324, - ["latitude"]=41.649498903439, - ["course"]=2.3086751290292, - ["height"]=458.72555541992, - }, - ["sub_type"]=5, - }, - ["{93E74802-597F-4d4f-B0E0-7A5A43A79EA9}"]= - { - ["band"]=525000, - ["type"]=2, - ["name"]="Mozdok", - ["callsign"]="RM", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=44.670566359666, - ["latitude"]=43.790951233713, - ["course"]=-1.6970900297165, - ["height"]=154.5718536377, - }, - ["sub_type"]=4104, - }, - ["{B8A2BBE0-959D-4856-AFEB-AC092A6C76E4}"]= - { - ["band"]=705000, - ["type"]=2, - ["name"]="Manychsky", - ["callsign"]="MN", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=40.366666550758, - ["latitude"]=47.049999993999, - ["course"]=0, - ["height"]=0.00014901164104231, - }, - ["sub_type"]=8, - }, - ["{654A7048-AE8C-4932-A060-E351F4A047A9}"]= - { - ["band"]=75000000, - ["type"]=2, - ["name"]="Beslan", - ["callsign"]="", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=44.572220563029, - ["latitude"]=43.210694410745, - ["course"]=1.6318835020065, - ["height"]=543.05224609375, - }, - ["sub_type"]=112, - }, - ["{2CF16904-80B3-4bdd-9659-E949BA3F4E78}"]= - { - ["band"]=330000, - ["type"]=2, - ["name"]="Ust-Labinsk", - ["callsign"]="NZ", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=39.654166830374, - ["latitude"]=45.226666650569, - ["course"]=0, - ["height"]=80.053146362305, - }, - ["sub_type"]=8, - }, - ["{AC45C2C1-6D32-4e03-B52D-3051A01D7B1D}"]= - { - ["band"]=75000000, - ["type"]=2, - ["name"]="Sukhumi-Babushara", - ["callsign"]="", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=41.184595069577, - ["latitude"]=42.833634213401, - ["course"]=-1.1085398197174, - ["height"]=23.724117279053, - }, - ["sub_type"]=112, - }, - ["Krasnodar-Center"]= - { - ["type"]=1, - ["name"]="Krasnodar-TZentralny", - ["callsign"]="", - ["runway_length"]=850, - ["class"]="ABRIS_Waypoint_Airdrome", - ["position"]= - { - ["longitude"]=38.940245888497, - ["latitude"]=45.086936242812, - ["course"]=-1.6230975387504, - ["height"]=30, - }, - ["sub_type"]=6, - }, - ["{B2B0EA6D-30FA-4254-9F85-FE00D13A981A}"]= - { - ["band"]=395000, - ["type"]=2, - ["name"]="Krasnolesye", - ["callsign"]="LE", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=34.250000000095, - ["latitude"]=44.83333333867, - ["course"]=0, - ["height"]=419.28756713867, - }, - ["sub_type"]=8, - }, - ["{CB0B82C9-16F5-4299-A646-47747AD20253}"]= - { - ["band"]=983000000, - ["type"]=2, - ["name"]="Vaziani", - ["callsign"]="VAS", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=45.027339550197, - ["latitude"]=41.6307070249, - ["course"]=0, - ["height"]=456.05590820313, - }, - ["sub_type"]=4, - }, - ["{52F5B421-C5AE-4e31-BB82-82655FC79F80}"]= - { - ["band"]=75000000, - ["type"]=2, - ["name"]="Mozdok", - ["callsign"]="", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=44.528781245533, - ["latitude"]=43.792473730478, - ["course"]=1.4445027112961, - ["height"]=153.11080932617, - }, - ["sub_type"]=112, - }, - ["{D0AE8C50-F6B3-4f8f-93EA-C64344183CCB}"]= - { - ["band"]=250000, - ["type"]=2, - ["name"]="Beslan", - ["callsign"]="C", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=44.572220563029, - ["latitude"]=43.210694410745, - ["course"]=1.6318835020065, - ["height"]=543.05224609375, - }, - ["sub_type"]=4104, - }, - ["Tbilisi-Lochini"]= - { - ["type"]=1, - ["name"]="Tbilisi-Lochini", - ["callsign"]="", - ["runway_length"]=1059, - ["class"]="ABRIS_Waypoint_Airdrome", - ["position"]= - { - ["longitude"]=44.954725173474, - ["latitude"]=41.668258016603, - ["course"]=-0.90759545507394, - ["height"]=464.50350952148, - }, - ["sub_type"]=5, - }, - ["{72467CBD-F38A-4a44-B6A3-E8A8C6D2560F}"]= - { - ["band"]=75000000, - ["type"]=2, - ["name"]="Maykop-KHanskaya", - ["callsign"]="", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=39.986362565764, - ["latitude"]=44.645077755977, - ["course"]=0.68067294359207, - ["height"]=177.95664978027, - }, - ["sub_type"]=112, - }, - ["{AFD16F93-F82A-467d-8F46-DD6E953264EA}"]= - { - ["band"]=113700000, - ["type"]=2, - ["name"]="Tbilisi-Lochini", - ["callsign"]="TBS", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=44.946945391997, - ["latitude"]=41.670555186786, - ["course"]=0, - ["height"]=469.86624145508, - }, - }, - ["{7B562F00-3CCA-4d91-BE5A-E2D1E97E4877}"]= - { - ["band"]=75000000, - ["type"]=2, - ["name"]="Mineralnye Vody", - ["callsign"]="", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=43.116124501349, - ["latitude"]=44.211919658446, - ["course"]=-1.1292264461517, - ["height"]=320, - }, - ["sub_type"]=112, - }, - ["{6BFE6F5C-4121-4646-ABEA-A31E72381C88}"]= - { - ["band"]=1000000, - ["type"]=2, - ["name"]="Gelendzhik", - ["callsign"]="GN", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=38.015277672561, - ["latitude"]=44.564445491859, - ["course"]=0, - ["height"]=0, - }, - ["sub_type"]=4104, - }, - ["{2541CFCF-AE47-4eb8-A2D5-49785B244912}"]= - { - ["band"]=740000, - ["type"]=2, - ["name"]="Melitopol", - ["callsign"]="NE", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=35.299999969228, - ["latitude"]=46.866666696345, - ["course"]=0, - ["height"]=0.00014901164104231, - }, - ["sub_type"]=8, - }, - ["{FC2C1E65-B8E8-40f7-8D00-CBF1800D3D7B}"]= - { - ["band"]=211000, - ["type"]=2, - ["name"]="Tbilisi-Lochini", - ["callsign"]="NA", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=44.999697078343, - ["latitude"]=41.633299706948, - ["course"]=-0.90759545564651, - ["height"]=459.41839599609, - }, - ["sub_type"]=4104, - }, - ["{4E2A763F-5528-4d03-9D2A-3396F93CA516}"]= - { - ["band"]=117600000, - ["type"]=2, - ["name"]="Mineralnye Vody", - ["callsign"]="MW", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=43.070295945672, - ["latitude"]=44.228586168404, - ["course"]=2.0123660564423, - ["height"]=320, - }, - ["sub_type"]=32, - }, - ["{867AB331-3908-47b5-B977-4A88BCB0E41C}"]= - { - ["band"]=520000, - ["type"]=2, - ["name"]="Mukhrani", - ["callsign"]="DF", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=44.565555194134, - ["latitude"]=41.916666643449, - ["course"]=0, - ["height"]=515.787109375, - }, - ["sub_type"]=8, - }, - ["{AC8FD1BA-08A3-4a73-B13E-4F7D7077607D}"]= - { - ["band"]=489000, - ["type"]=2, - ["name"]="Sukhumi-Babushara", - ["callsign"]="AV", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=41.184595069577, - ["latitude"]=42.833634213401, - ["course"]=-1.1085398197174, - ["height"]=23.724117279053, - }, - ["sub_type"]=4104, - }, - ["{05A67A03-1287-4f18-8477-52DD0CF8FF73}"]= - { - ["band"]=435000, - ["type"]=2, - ["name"]="Egorlykskaya", - ["callsign"]="ER", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=40.666666758319, - ["latitude"]=46.583333276832, - ["course"]=0, - ["height"]=0.00014901164104231, - }, - ["sub_type"]=8, - }, - ["{D9016381-E3DD-4348-8D43-D12C681A7EF9}"]= - { - ["band"]=75000000, - ["type"]=2, - ["name"]="Mineralnye Vody", - ["callsign"]="", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=43.017842556108, - ["latitude"]=44.256694325491, - ["course"]=2.0123660564423, - ["height"]=318.89764404297, - }, - ["sub_type"]=112, - }, - ["{2C860AD8-14CC-466d-B530-B07FB0B06EBA}"]= - { - ["band"]=75000000, - ["type"]=2, - ["name"]="Mozdok", - ["callsign"]="", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=44.561984548905, - ["latitude"]=43.792133293965, - ["course"]=1.4445027112961, - ["height"]=152.32843017578, - }, - ["sub_type"]=112, - }, - ["{BD9C7F8E-B6EF-4df6-AE6A-FA6F07F279B7}"]= - { - ["band"]=722000, - ["type"]=2, - ["name"]="Biryuchya Kosa", - ["callsign"]="YO", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=47.600000064833, - ["latitude"]=45.716666602437, - ["course"]=0, - ["height"]=0.00014901164104231, - }, - ["sub_type"]=8, - }, - ["{8ABE5D65-1EED-464f-B5FF-164FC525CEB6}"]= - { - ["band"]=75000000, - ["type"]=2, - ["name"]="Anapa-Vityazevo", - ["callsign"]="", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=37.372305715442, - ["latitude"]=45.022565731494, - ["course"]=-2.4172778129578, - ["height"]=47.840591430664, - }, - ["sub_type"]=112, - }, - ["{5750696B-22FB-4b75-BA00-9D9F8D6C66C0}"]= - { - ["band"]=583000, - ["type"]=2, - ["name"]="Mineralnye Vody", - ["callsign"]="NR", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=43.144428100003, - ["latitude"]=44.198998202113, - ["course"]=-1.1292264461517, - ["height"]=303.2926940918, - }, - ["sub_type"]=4104, - }, - ["{4EA94532-05FD-4be9-9DCF-AB6E62614B07}"]= - { - ["band"]=490000, - ["type"]=2, - ["name"]="Kobuleti", - ["callsign"]="T", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=41.83442330282, - ["latitude"]=41.924511693408, - ["course"]=1.2217304706573, - ["height"]=18.219362258911, - }, - ["sub_type"]=4104, - }, - ["{9FB9FADD-714D-4040-9358-7D8B97F7F5B1}"]= - { - ["band"]=75000000, - ["type"]=2, - ["name"]="Kobuleti", - ["callsign"]="", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=41.80304745528, - ["latitude"]=41.918633401388, - ["course"]=1.2217304706573, - ["height"]=10.535722732544, - }, - ["sub_type"]=112, - }, - ["{3C9AD127-83CE-41c6-9D07-3478B16BBCB4}"]= - { - ["band"]=113600000, - ["type"]=2, - ["name"]="Kutaisi", - ["callsign"]="KTS", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=42.456112245443, - ["latitude"]=42.173610536789, - ["course"]=0, - ["height"]=45, - }, - }, - ["{71F34A61-6419-48d1-BF78-02F8D97395CA}"]= - { - ["band"]=1154000000, - ["type"]=2, - ["name"]="Kobuleti", - ["callsign"]="KBL", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=41.861969842226, - ["latitude"]=41.930769894946, - ["course"]=0, - ["height"]=18, - }, - ["sub_type"]=4, - }, - ["Beslan"]= - { - ["type"]=1, - ["name"]="Beslan", - ["callsign"]="", - ["runway_length"]=1110, - ["class"]="ABRIS_Waypoint_Airdrome", - ["position"]= - { - ["longitude"]=44.605763506287, - ["latitude"]=43.205705800612, - ["course"]=1.6318834439122, - ["height"]=540.13690185547, - }, - ["sub_type"]=5, - }, - ["{FAD02FC6-6BDC-48c3-B380-CDEF4481EE6D}"]= - { - ["band"]=822000, - ["type"]=2, - ["name"]="Makhachkala", - ["callsign"]="TA", - ["class"]="ABRIS_Waypoint_Beacon", - ["position"]= - { - ["longitude"]=47.520000672166, - ["latitude"]=42.974999903165, - ["course"]=0, - ["height"]=0.00014901164104231, - }, - ["sub_type"]=8, - }, - ["Senaki-Kolkhi"]= - { - ["type"]=1, - ["name"]="Senaki-Kolkhi", - ["callsign"]="", - ["runway_length"]=778, - ["class"]="ABRIS_Waypoint_Airdrome", - ["position"]= - { - ["longitude"]=42.047685627404, - ["latitude"]=42.240897304126, - ["course"]=1.6528304225881, - ["height"]=10.602717399597, - }, - ["sub_type"]=6, - }, - }, -} diff --git a/Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC/ABRIS/Database/ROUTES.lua b/Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC/ABRIS/Database/ROUTES.lua deleted file mode 100644 index 1ca8329db..000000000 --- a/Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC/ABRIS/Database/ROUTES.lua +++ /dev/null @@ -1,3 +0,0 @@ -routes= -{ -} diff --git a/Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC/Config/View/Server.lua b/Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC/Config/View/Server.lua deleted file mode 100644 index df77a168a..000000000 --- a/Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC/Config/View/Server.lua +++ /dev/null @@ -1,209 +0,0 @@ --- View scripts --- Copyright (C) 2004, Eagle Dynamics. -DisableCombatViews = false -- F5 & Ctrl-F5 -ExternalObjectsLockDistance = 10000.0 -ShowTargetInfo = false -CameraTerrainRestriction = true -hAngleRearDefault = 180 -vAngleRearDefault = -8.0 -vAngleRearMin = -90 -- -8.0 -vAngleRearMax = 90.0 - -dbg_shell = "weapons.shells.PKT_7_62_T" -- 23mm shell --- dbg_shell = "weapons.shells.2A64_152" -- 152mm shell -dbg_shell_v0 = -1 -- Muzzle speed m/s (-1 - speed from shall database) -dbg_shell_fire_rate = 60 ---reformatted per-unit data to be mod system friendly ---this file is no longer should be edited for adding new flyable aircraft , DCS automatically check core database (i.e. where you define your aircraft in aircraft table just define ViewSettings and SnapViews tables) - -function default_fighter_player(t) - local res = { - CameraViewAngleLimits = {20.000000,140.000000}, - CameraAngleRestriction = {false ,90.000000,0.500000}, - EyePoint = {0.05 ,0.000000 ,0.000000}, - limits_6DOF = {x = {-0.050000,0.4500000},y ={-0.300000,0.100000},z = {-0.220000,0.220000},roll = 90.000000}, - Allow360rotation = false, - CameraAngleLimits = {200,-80.000000,110.000000}, - ShoulderSize = 0.2, -- move body when azimuth value more then 90 degrees - } - if t then - for i,o in pairs(t) do - res[i] = o - end - end - return res -end - -function fulcrum() - return { - Cockpit = { - default_fighter_player({CockpitLocalPoint = {4.71,1.28,0.000000}}) - }, - Chase = { - LocalPoint = {1.220000,3.750000,0.000000}, - AnglesDefault = {180.000000,-8.000000}, - }, -- Chase - Arcade = { - LocalPoint = {-15.080000,6.350000,0.000000}, - AnglesDefault = {0.000000,-8.000000}, - }, -- Arcade - } -end - -ViewSettings = {} -ViewSettings["A-10A"] = { - Cockpit = { - [1] = default_fighter_player({CockpitLocalPoint = {4.300000,1.282000,0.000000}, - EyePoint = {0.000000,0.000000,0.000000}, - limits_6DOF = {x = {-0.050000,0.600000}, - y = {-0.300000,0.100000}, - z = {-0.250000,0.250000}, - roll = 90.000000}}), - }, -- Cockpit - Chase = { - LocalPoint = {0.600000,3.682000,0.000000}, - AnglesDefault = {180.000000,-8.000000}, - }, -- Chase - Arcade = { - LocalPoint = {-27.000000,12.000000,0.000000}, - AnglesDefault = {0.000000,-12.000000}, - }, -- Arcade -} -ViewSettings["A-10C"] = { - Cockpit = { - [1] = default_fighter_player({CockpitLocalPoint = {4.300000,1.282000,0.000000}, - EyePoint = {0.000000,0.000000,0.000000}, - limits_6DOF = {x = {-0.050000,0.600000}, - y = {-0.300000,0.100000}, - z = {-0.250000,0.250000}, - roll = 90.000000}}), - }, -- Cockpit - Chase = { - LocalPoint = {0.600000,3.682000,0.000000}, - AnglesDefault = {180.000000,-8.000000}, - }, -- Chase - Arcade = { - LocalPoint = {-27.000000,12.000000,0.000000}, - AnglesDefault = {0.000000,-12.000000}, - }, -- Arcade -} -ViewSettings["F-15C"] = { - Cockpit = { - [1] = default_fighter_player({CockpitLocalPoint = {6.210000,1.204000,0.000000}})-- player slot 1 - }, -- Cockpit - Chase = { - LocalPoint = {2.510000,3.604000,0.000000}, - AnglesDefault = {180.000000,-8.000000}, - }, -- Chase - Arcade = { - LocalPoint = {-13.790000,6.204000,0.000000}, - AnglesDefault = {0.000000,-8.000000}, - }, -- Arcade -} -ViewSettings["Ka-50"] = { - Cockpit = { - [1] = {-- player slot 1 - CockpitLocalPoint = {3.188000,0.390000,0.000000}, - CameraViewAngleLimits = {20.000000,120.000000}, - CameraAngleRestriction = {false,60.000000,0.400000}, - CameraAngleLimits = {140.000000,-65.000000,90.000000}, - EyePoint = {0.090000,0.000000,0.000000}, - limits_6DOF = {x = {-0.020000,0.350000},y ={-0.150000,0.165000},z = {-0.170000,0.170000},roll = 90.000000}, - }, - }, -- Cockpit - Chase = { - LocalPoint = {-0.512000,2.790000,0.000000}, - AnglesDefault = {180.000000,-8.000000}, - }, -- Chase - Arcade = { - LocalPoint = {-16.812000,5.390000,0.000000}, - AnglesDefault = {0.000000,-8.000000}, - }, -- Arcade -} -ViewSettings["MiG-29A"] = fulcrum() -ViewSettings["MiG-29G"] = fulcrum() -ViewSettings["MiG-29S"] = fulcrum() - -ViewSettings["P-51D"] = { - Cockpit = { - [1] = {-- player slot 1 - CockpitLocalPoint = {-1.500000,0.618000,0.000000}, - CameraViewAngleLimits = {20.000000,120.000000}, - CameraAngleRestriction = {false,90.000000,0.500000}, - CameraAngleLimits = {200,-80.000000,90.000000}, - EyePoint = {0.025000,0.100000,0.000000}, - ShoulderSize = 0.15, - Allow360rotation = false, - limits_6DOF = {x = {-0.050000,0.450000},y ={-0.200000,0.200000},z = {-0.220000,0.220000},roll = 90.000000}, - }, - }, -- Cockpit - Chase = { - LocalPoint = {0.200000,-0.652000,-0.650000}, - AnglesDefault = {0.000000,0.000000}, - }, -- Chase - Arcade = { - LocalPoint = {-21.500000,5.618000,0.000000}, - AnglesDefault = {0.000000,-8.000000}, - }, -- Arcade -} -ViewSettings["Su-25"] = { - Cockpit = { - [1] = default_fighter_player({CockpitLocalPoint = {3.352000,0.506000,0.000000}}),-- player slot 1 - }, -- Cockpit - Chase = { - LocalPoint = {-0.348000,2.906000,0.000000}, - AnglesDefault = {180.000000,-8.000000}, - }, -- Chase - Arcade = { - LocalPoint = {-16.648001,5.506000,0.000000}, - AnglesDefault = {0.000000,-8.000000}, - }, -- Arcade -} -ViewSettings["Su-25T"] = { - Cockpit = { - [1] = default_fighter_player({CockpitLocalPoint = {3.406000,0.466000,0.000000}}),-- player slot 1 - }, -- Cockpit - Chase = { - LocalPoint = {-0.294000,2.866000,0.000000}, - AnglesDefault = {180.000000,-8.000000}, - }, -- Chase - Arcade = { - LocalPoint = {-16.594000,5.466000,0.000000}, - AnglesDefault = {0.000000,-8.000000}, - }, -- Arcade -} -ViewSettings["Su-25TM"] = { - Cockpit = { - [1] = {-- player slot 1 - CockpitLocalPoint = {4.000000,1.000000,0.000000}, - CameraViewAngleLimits = {20.000000,140.000000}, - CameraAngleRestriction = {true,90.000000,0.400000}, - CameraAngleLimits = {160.000000,-70.000000,90.000000}, - EyePoint = {0.000000,0.000000,0.000000}, - limits_6DOF = {x = {-0.200000,0.200000},y ={-0.200000,0.200000},z = {-0.200000,0.200000},roll = 60.000000}, - }, - }, -- Cockpit - Chase = { - LocalPoint = {4.000000,2.000000,0.000000}, - AnglesDefault = {180.000000,-8.000000}, - }, -- Chase - Arcade = { - LocalPoint = {4.000000,2.000000,0.000000}, - AnglesDefault = {180.000000,-8.000000}, - }, -- Arcade -} -ViewSettings["Su-27"] = { - Cockpit = { - [1] = default_fighter_player({CockpitLocalPoint = {7.959000,1.419000,0.000000}})-- player slot 1 - }, -- Cockpit - Chase = { - LocalPoint = {4.259000,3.819000,0.000000}, - AnglesDefault = {180.000000,-8.000000}, - }, -- Chase - Arcade = { - LocalPoint = {-12.041000,6.419000,0.000000}, - AnglesDefault = {0.000000,-8.000000}, - }, -- Arcade -} - -ViewSettings["Su-33"] = ViewSettings["Su-27"] diff --git a/Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC/Config/View/SnapViewsDefault.lua b/Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC/Config/View/SnapViewsDefault.lua deleted file mode 100644 index 754522d55..000000000 --- a/Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC/Config/View/SnapViewsDefault.lua +++ /dev/null @@ -1,1698 +0,0 @@ ---reformatted per-unit data to be mod system friendly ---this file is no longer should be edited for adding new flyable aircraft , DCS automatically check core database for this data(i.e. where you define your aircraft in aircraft table just define ViewSettings and SnapViews tables) --- result of ingame editing is saved to Saved Games//DCS/Config/View/SnapViews.lua -SnapViews = {} -SnapViews["A-10A"] = { -[1] = {-- player slot 1 - [1] = { - viewAngle = 65.000000,--FOV - hAngle = 0.000000, - vAngle = -26.000000, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [2] = { - viewAngle = 49.626770,--FOV - hAngle = 0.000000, - vAngle = -90.631294, - x_trans = 0.180499, - y_trans = -0.137064, - z_trans = -0.250000, - rollAngle = 0.000000, - }, - [3] = { - viewAngle = 30.395041,--FOV - hAngle = 0.000000, - vAngle = -94.329208, - x_trans = 0.372718, - y_trans = -0.054055, - z_trans = 0.250000, - rollAngle = 0.000000, - }, - [4] = { - viewAngle = 55.238567,--FOV - hAngle = 0.000000, - vAngle = -90.631294, - x_trans = 0.158523, - y_trans = -0.137064, - z_trans = 0.250000, - rollAngle = 0.000000, - }, - [5] = { - viewAngle = 35.000000,--FOV - hAngle = 0.000000, - vAngle = -10.651850, - x_trans = 0.327622, - y_trans = -0.278207, - z_trans = -0.244799, - rollAngle = 0.000000, - }, - [6] = { - viewAngle = 34.340549,--FOV - hAngle = 0.000000, - vAngle = -9.500000, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [7] = { - viewAngle = 35.000000,--FOV - hAngle = 0.000000, - vAngle = -10.651850, - x_trans = 0.327622, - y_trans = -0.278207, - z_trans = 0.244799, - rollAngle = 0.000000, - }, - [8] = { - viewAngle = 68.628296,--FOV - hAngle = 68.292320, - vAngle = -11.477349, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [9] = { - viewAngle = 68.628296,--FOV - hAngle = 0.000000, - vAngle = 30.227919, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [10] = { - viewAngle = 68.628296,--FOV - hAngle = -67.172974, - vAngle = -11.477349, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [11] = {--look at left mirror - viewAngle = 70.000000,--FOV - hAngle = 20.000000, - vAngle = 8.000000, - x_trans = 0.360000, - y_trans = -0.041337, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [12] = {--look at right mirror - viewAngle = 70.000000,--FOV - hAngle = -20.000000, - vAngle = 8.000000, - x_trans = 0.360000, - y_trans = -0.041337, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [13] = {--default view - viewAngle = 75.000000,--FOV - hAngle = 0.000000, - vAngle = -23.000000, - x_trans = 0.360000, - y_trans = -0.041337, - z_trans = 0.000000, - rollAngle = 0.000000, - }, -}, -} -SnapViews["A-10C"] = { -[1] = {-- player slot 1 - [1] = { - viewAngle = 65.000000,--FOV - hAngle = 0.000000, - vAngle = -26.000000, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [2] = { - viewAngle = 49.626770,--FOV - hAngle = 0.000000, - vAngle = -90.631294, - x_trans = 0.180499, - y_trans = -0.137064, - z_trans = -0.250000, - rollAngle = 0.000000, - }, - [3] = { - viewAngle = 30.395041,--FOV - hAngle = 0.000000, - vAngle = -94.329208, - x_trans = 0.372718, - y_trans = -0.054055, - z_trans = 0.250000, - rollAngle = 0.000000, - }, - [4] = { - viewAngle = 55.238567,--FOV - hAngle = 0.000000, - vAngle = -90.631294, - x_trans = 0.158523, - y_trans = -0.137064, - z_trans = 0.250000, - rollAngle = 0.000000, - }, - [5] = { - viewAngle = 35.000000,--FOV - hAngle = 0.000000, - vAngle = -10.651850, - x_trans = 0.327622, - y_trans = -0.278207, - z_trans = -0.244799, - rollAngle = 0.000000, - }, - [6] = { - viewAngle = 34.340549,--FOV - hAngle = 0.000000, - vAngle = -9.500000, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [7] = { - viewAngle = 35.000000,--FOV - hAngle = 0.000000, - vAngle = -10.651850, - x_trans = 0.327622, - y_trans = -0.278207, - z_trans = 0.244799, - rollAngle = 0.000000, - }, - [8] = { - viewAngle = 68.628296,--FOV - hAngle = 68.292320, - vAngle = -11.477349, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [9] = { - viewAngle = 68.628296,--FOV - hAngle = 0.000000, - vAngle = 30.227919, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [10] = { - viewAngle = 68.628296,--FOV - hAngle = -67.172974, - vAngle = -11.477349, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [11] = {--look at left mirror - viewAngle = 70.000000,--FOV - hAngle = 20.000000, - vAngle = 8.000000, - x_trans = 0.360000, - y_trans = -0.041337, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [12] = {--look at right mirror - viewAngle = 70.000000,--FOV - hAngle = -20.000000, - vAngle = 8.000000, - x_trans = 0.360000, - y_trans = -0.041337, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [13] = {--default view - viewAngle = 75.000000,--FOV - hAngle = 0.000000, - vAngle = -23.000000, - x_trans = 0.360000, - y_trans = -0.041337, - z_trans = 0.000000, - rollAngle = 0.000000, - }, -}, -} -SnapViews["F-15C"] = { -[1] = {-- player slot 1 - [1] = { - viewAngle = 70.611748,--FOV - hAngle = -1.240272, - vAngle = -33.850250, - x_trans = 0.264295, - y_trans = -0.064373, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [2] = { - viewAngle = 32.704346,--FOV - hAngle = 25.696522, - vAngle = -34.778103, - x_trans = 0.264295, - y_trans = -0.064373, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [3] = { - viewAngle = 32.704346,--FOV - hAngle = 0.000000, - vAngle = -47.845268, - x_trans = 0.264295, - y_trans = -0.064373, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [4] = { - viewAngle = 36.106045,--FOV - hAngle = -28.878576, - vAngle = -36.780628, - x_trans = 0.264295, - y_trans = -0.064373, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [5] = { - viewAngle = 88.727844,--FOV - hAngle = 128.508865, - vAngle = 13.131046, - x_trans = 0.264295, - y_trans = -0.064373, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [6] = { - viewAngle = 41.928593,--FOV - hAngle = 0.000000, - vAngle = -4.630446, - x_trans = 0.264295, - y_trans = -0.064373, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [7] = { - viewAngle = 88.727844,--FOV - hAngle = -128.508865, - vAngle = 13.131046, - x_trans = 0.264295, - y_trans = -0.064373, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [8] = { - viewAngle = 88.727844,--FOV - hAngle = 81.648369, - vAngle = -9.500000, - x_trans = 0.264295, - y_trans = -0.064373, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [9] = { - viewAngle = 88.727844,--FOV - hAngle = 0.000000, - vAngle = 34.180634, - x_trans = 0.264295, - y_trans = -0.064373, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [10] = { - viewAngle = 88.727844,--FOV - hAngle = -80.997551, - vAngle = -9.500000, - x_trans = 0.264295, - y_trans = -0.064373, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [11] = {--look at left mirror - viewAngle = 56.032040,--FOV - hAngle = 14.803060, - vAngle = 3.332499, - x_trans = 0.264295, - y_trans = -0.064373, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [12] = {--look at right mirror - viewAngle = 56.032040,--FOV - hAngle = -14.414484, - vAngle = 3.332499, - x_trans = 0.264295, - y_trans = -0.064373, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [13] = {--default view - viewAngle = 88.727844,--FOV - hAngle = 0.000000, - vAngle = -9.678451, - x_trans = 0.264295, - y_trans = -0.064373, - z_trans = 0.000000, - rollAngle = 0.000000, - }, -}, -} -SnapViews["Ka-50"] = { -[1] = {-- player slot 1 - [1] = { - viewAngle = 67.452896,--FOV - hAngle = 0.000000, - vAngle = -40.067383, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [2] = { - viewAngle = 37.846794,--FOV - hAngle = 51.644135, - vAngle = -51.870411, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [3] = { - viewAngle = 36.178646,--FOV - hAngle = -1.912186, - vAngle = -34.446247, - x_trans = 0.000000, - y_trans = -0.025421, - z_trans = 0.073226, - rollAngle = 0.000000, - }, - [4] = { - viewAngle = 73.605141,--FOV - hAngle = -90.361992, - vAngle = -44.103138, - x_trans = 0.169696, - y_trans = -0.073508, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [5] = { - viewAngle = 91.348198,--FOV - hAngle = 109.752129, - vAngle = 1.484382, - x_trans = 0.190306, - y_trans = 0.044778, - z_trans = -0.150335, - rollAngle = 0.000000, - }, - [6] = { - viewAngle = 42.512844,--FOV - hAngle = 0.000000, - vAngle = -4.478010, - x_trans = 0.154018, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [7] = { - viewAngle = 91.348198,--FOV - hAngle = -108.852020, - vAngle = 0.085984, - x_trans = 0.190306, - y_trans = 0.044778, - z_trans = 0.139404, - rollAngle = 0.000000, - }, - [8] = { - viewAngle = 89.777542,--FOV - hAngle = 16.411518, - vAngle = -27.209915, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = -0.218292, - rollAngle = 0.000000, - }, - [9] = { - viewAngle = 88.727844,--FOV - hAngle = 0.000000, - vAngle = 34.042202, - x_trans = 0.142145, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [10] = { - viewAngle = 59.208893,--FOV - hAngle = -32.128311, - vAngle = -5.720805, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [11] = {--look at left mirror - viewAngle = 56.032040,--FOV - hAngle = 14.803060, - vAngle = 3.332499, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [12] = {--look at right mirror - viewAngle = 56.032040,--FOV - hAngle = -14.414484, - vAngle = 3.332499, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [13] = {--default view - viewAngle = 89.777542,--FOV - hAngle = 0.000000, - vAngle = -15.592758, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, -}, -} -SnapViews["MiG-29A"] = { -[1] = {-- player slot 1 - [1] = { - viewAngle = 76.124840,--FOV - hAngle = -2.623254, - vAngle = -26.566959, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [2] = { - viewAngle = 34.911949,--FOV - hAngle = 24.601770, - vAngle = -32.350807, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [3] = { - viewAngle = 26.184198,--FOV - hAngle = 12.026249, - vAngle = -40.075508, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [4] = { - viewAngle = 39.454399,--FOV - hAngle = -26.664328, - vAngle = -32.355324, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [5] = { - viewAngle = 81.240005,--FOV - hAngle = 131.503998, - vAngle = 10.804660, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [6] = { - viewAngle = 44.201855,--FOV - hAngle = 0.000000, - vAngle = -2.378299, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [7] = { - viewAngle = 81.240005,--FOV - hAngle = -131.503998, - vAngle = 10.804660, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [8] = { - viewAngle = 81.240005,--FOV - hAngle = 76.013145, - vAngle = 2.248441, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [9] = { - viewAngle = 81.240005,--FOV - hAngle = 0.000000, - vAngle = 36.304676, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [10] = { - viewAngle = 81.240005,--FOV - hAngle = -74.774559, - vAngle = 2.248441, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [11] = {--look at left mirror - viewAngle = 68.250000,--FOV - hAngle = 13.070938, - vAngle = 7.522498, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [12] = {--look at right mirror - viewAngle = 68.250000,--FOV - hAngle = -13.070938, - vAngle = 7.522498, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [13] = {--default view - viewAngle = 81.240005,--FOV - hAngle = 0.000000, - vAngle = -9.500000, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, -}, -} -SnapViews["MiG-29G"] = { -[1] = {-- player slot 1 - [1] = { - viewAngle = 76.124840,--FOV - hAngle = -2.623254, - vAngle = -26.566959, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [2] = { - viewAngle = 34.911949,--FOV - hAngle = 24.601770, - vAngle = -32.350807, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [3] = { - viewAngle = 26.184198,--FOV - hAngle = 12.026249, - vAngle = -40.075508, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [4] = { - viewAngle = 39.454399,--FOV - hAngle = -26.664328, - vAngle = -32.355324, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [5] = { - viewAngle = 81.240005,--FOV - hAngle = 131.503998, - vAngle = 10.804660, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [6] = { - viewAngle = 44.201855,--FOV - hAngle = 0.000000, - vAngle = -2.378299, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [7] = { - viewAngle = 81.240005,--FOV - hAngle = -131.503998, - vAngle = 10.804660, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [8] = { - viewAngle = 81.240005,--FOV - hAngle = 76.013145, - vAngle = 2.248441, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [9] = { - viewAngle = 81.240005,--FOV - hAngle = 0.000000, - vAngle = 36.304676, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [10] = { - viewAngle = 81.240005,--FOV - hAngle = -74.774559, - vAngle = 2.248441, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [11] = {--look at left mirror - viewAngle = 68.250000,--FOV - hAngle = 13.070938, - vAngle = 7.522498, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [12] = {--look at right mirror - viewAngle = 68.250000,--FOV - hAngle = -13.070938, - vAngle = 7.522498, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [13] = {--default view - viewAngle = 81.240005,--FOV - hAngle = 0.000000, - vAngle = -9.500000, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, -}, -} -SnapViews["MiG-29K"] = { -[1] = {-- player slot 1 - [1] = { - viewAngle = 65.000000,--FOV - hAngle = 0.000000, - vAngle = -26.000000, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [2] = { - viewAngle = 30.000000,--FOV - hAngle = 20.000000, - vAngle = -43.000000, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [3] = { - viewAngle = 30.000000,--FOV - hAngle = 0.000000, - vAngle = -43.000000, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [4] = { - viewAngle = 30.000000,--FOV - hAngle = -20.000000, - vAngle = -43.000000, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [5] = { - viewAngle = 30.000000,--FOV - hAngle = 20.000000, - vAngle = -23.000000, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [6] = { - viewAngle = 30.000000,--FOV - hAngle = 0.000000, - vAngle = -23.000000, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [7] = { - viewAngle = 30.000000,--FOV - hAngle = -20.000000, - vAngle = -23.000000, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [8] = { - viewAngle = 30.000000,--FOV - hAngle = 20.000000, - vAngle = 2.000000, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [9] = { - viewAngle = 30.000000,--FOV - hAngle = 0.000000, - vAngle = 2.000000, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [10] = { - viewAngle = 30.000000,--FOV - hAngle = -20.000000, - vAngle = 2.000000, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [11] = {--look at left mirror - viewAngle = 68.250000,--FOV - hAngle = 13.070938, - vAngle = 7.522498, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [12] = {--look at right mirror - viewAngle = 68.250000,--FOV - hAngle = -13.070938, - vAngle = 7.522498, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [13] = {--default view - viewAngle = 60.000000,--FOV - hAngle = 0.000000, - vAngle = -9.500000, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, -}, -} -SnapViews["MiG-29S"] = { -[1] = {-- player slot 1 - [1] = { - viewAngle = 76.124840,--FOV - hAngle = -2.623254, - vAngle = -26.566959, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [2] = { - viewAngle = 34.911949,--FOV - hAngle = 24.601770, - vAngle = -32.350807, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [3] = { - viewAngle = 26.184198,--FOV - hAngle = 12.026249, - vAngle = -40.075508, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [4] = { - viewAngle = 39.454399,--FOV - hAngle = -26.664328, - vAngle = -32.355324, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [5] = { - viewAngle = 81.240005,--FOV - hAngle = 131.503998, - vAngle = 10.804660, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [6] = { - viewAngle = 44.201855,--FOV - hAngle = 0.000000, - vAngle = -2.378299, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [7] = { - viewAngle = 81.240005,--FOV - hAngle = -131.503998, - vAngle = 10.804660, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [8] = { - viewAngle = 81.240005,--FOV - hAngle = 76.013145, - vAngle = 2.248441, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [9] = { - viewAngle = 81.240005,--FOV - hAngle = 0.000000, - vAngle = 36.304676, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [10] = { - viewAngle = 81.240005,--FOV - hAngle = -74.774559, - vAngle = 2.248441, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [11] = {--look at left mirror - viewAngle = 68.250000,--FOV - hAngle = 13.070938, - vAngle = 7.522498, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [12] = {--look at right mirror - viewAngle = 68.250000,--FOV - hAngle = -13.070938, - vAngle = 7.522498, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [13] = {--default view - viewAngle = 81.240005,--FOV - hAngle = 0.000000, - vAngle = -9.500000, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, -}, -} -SnapViews["P-51D"] = { -[1] = {-- player slot 1 - [1] = { - viewAngle = 80.000000,--FOV - hAngle = 0.000000, - vAngle = -45.000000, - x_trans = 0.120000, - y_trans = 0.059000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [2] = { - viewAngle = 80.000000,--FOV - hAngle = 45.000000, - vAngle = -45.000000, - x_trans = 0.120000, - y_trans = 0.059000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [3] = { - viewAngle = 80.000000,--FOV - hAngle = 0.000000, - vAngle = -75.000000, - x_trans = 0.120000, - y_trans = 0.059000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [4] = { - viewAngle = 80.000000,--FOV - hAngle = -45.000000, - vAngle = -45.000000, - x_trans = 0.120000, - y_trans = 0.059000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [5] = { - viewAngle = 91.040001,--FOV - hAngle = 157.332764, - vAngle = -28.359503, - x_trans = 0.063872, - y_trans = 0.082888, - z_trans = -0.116148, - rollAngle = 0.000000, - }, - [6] = { - viewAngle = 50.000000,--FOV - hAngle = 0.000000, - vAngle = -8.722581, - x_trans = 0.212078, - y_trans = 0.057813, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [7] = { - viewAngle = 80.000000,--FOV - hAngle = -143.000000, - vAngle = 0.000000, - x_trans = 0.350000, - y_trans = 0.059000, - z_trans = 0.100000, - rollAngle = 0.000000, - }, - [8] = { - viewAngle = 80.000000,--FOV - hAngle = 45.000000, - vAngle = -5.000000, - x_trans = 0.120000, - y_trans = 0.059000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [9] = { - viewAngle = 80.000000,--FOV - hAngle = 0.000000, - vAngle = 10.000000, - x_trans = 0.120000, - y_trans = 0.059000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [10] = { - viewAngle = 80.000000,--FOV - hAngle = -45.000000, - vAngle = -5.000000, - x_trans = 0.120000, - y_trans = 0.059000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [11] = {--look at left mirror - viewAngle = 80.000000,--FOV - hAngle = 0.000000, - vAngle = 10.000000, - x_trans = 0.120000, - y_trans = 0.059000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [12] = {--look at right mirror - viewAngle = 80.000000,--FOV - hAngle = -20.000000, - vAngle = 8.000000, - x_trans = 0.120000, - y_trans = 0.059000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [13] = {--default view - viewAngle = 80.000000,--FOV - hAngle = 0.000000, - vAngle = -9.500000, - x_trans = 0.120000, - y_trans = 0.059000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, -}, -} -SnapViews["Su-25"] = { -[1] = {-- player slot 1 - [1] = { - viewAngle = 68.767799,--FOV - hAngle = 1.929517, - vAngle = -30.846605, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [2] = { - viewAngle = 29.223452,--FOV - hAngle = 37.489525, - vAngle = -38.883888, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [3] = { - viewAngle = 40.635601,--FOV - hAngle = -0.438357, - vAngle = -33.138290, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [4] = { - viewAngle = 24.797405,--FOV - hAngle = -34.382549, - vAngle = -34.808853, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [5] = { - viewAngle = 69.302101,--FOV - hAngle = 89.405373, - vAngle = 1.213156, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [6] = { - viewAngle = 29.761202,--FOV - hAngle = 0.000000, - vAngle = -6.880077, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [7] = { - viewAngle = 69.302101,--FOV - hAngle = -89.691940, - vAngle = 4.554290, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [8] = { - viewAngle = 69.302101,--FOV - hAngle = 52.113377, - vAngle = -3.970644, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [9] = { - viewAngle = 72.856201,--FOV - hAngle = 0.000000, - vAngle = 30.866713, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [10] = { - viewAngle = 69.302101,--FOV - hAngle = -50.664936, - vAngle = -3.970644, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [11] = {--look at left mirror - viewAngle = 47.680202,--FOV - hAngle = 43.054649, - vAngle = -7.799250, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [12] = {--look at right mirror - viewAngle = 47.680202,--FOV - hAngle = -41.743240, - vAngle = -7.799250, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [13] = {--default view - viewAngle = 69.302101,--FOV - hAngle = 0.000000, - vAngle = -15.137112, - x_trans = 0.050000, - y_trans = 0.010000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, -}, -} -SnapViews["Su-25T"] = { -[1] = {-- player slot 1 - [1] = { - viewAngle = 80.663399,--FOV - hAngle = 0.000000, - vAngle = -30.619938, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [2] = { - viewAngle = 39.764698,--FOV - hAngle = 28.661316, - vAngle = -41.406044, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [3] = { - viewAngle = 38.090847,--FOV - hAngle = -24.622110, - vAngle = -45.153934, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [4] = { - viewAngle = 36.062012,--FOV - hAngle = -20.779360, - vAngle = -23.755520, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [5] = { - viewAngle = 80.663399,--FOV - hAngle = 99.816956, - vAngle = 8.032285, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [6] = { - viewAngle = 58.718098,--FOV - hAngle = 0.000000, - vAngle = -5.000803, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [7] = { - viewAngle = 80.663399,--FOV - hAngle = -99.999687, - vAngle = 8.032285, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [8] = { - viewAngle = 80.663399,--FOV - hAngle = 58.382488, - vAngle = -6.648195, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [9] = { - viewAngle = 94.037704,--FOV - hAngle = 0.000000, - vAngle = 41.421227, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [10] = { - viewAngle = 80.663399,--FOV - hAngle = -57.531212, - vAngle = -6.648195, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [11] = {--look at left mirror - viewAngle = 60.203396,--FOV - hAngle = 55.124939, - vAngle = -8.400513, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [12] = {--look at right mirror - viewAngle = 60.203396,--FOV - hAngle = -52.633553, - vAngle = -8.400513, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [13] = {--default view - viewAngle = 90.000000,--FOV - hAngle = 0.000000, - vAngle = -18.382137, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, -}, -} -SnapViews["Su-25TM"] = { -[1] = {-- player slot 1 - [1] = { - viewAngle = 80.663399,--FOV - hAngle = 0.000000, - vAngle = -30.619938, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [2] = { - viewAngle = 39.764698,--FOV - hAngle = 28.661316, - vAngle = -41.406044, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [3] = { - viewAngle = 38.090847,--FOV - hAngle = -24.622110, - vAngle = -45.153934, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [4] = { - viewAngle = 33.645596,--FOV - hAngle = -36.653450, - vAngle = -23.703861, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [5] = { - viewAngle = 80.663399,--FOV - hAngle = 99.816956, - vAngle = 8.032285, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [6] = { - viewAngle = 58.718098,--FOV - hAngle = 0.000000, - vAngle = -5.000803, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [7] = { - viewAngle = 80.663399,--FOV - hAngle = -99.999687, - vAngle = 8.032285, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [8] = { - viewAngle = 80.663399,--FOV - hAngle = 58.382488, - vAngle = -6.648195, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [9] = { - viewAngle = 94.037704,--FOV - hAngle = 0.000000, - vAngle = 41.421227, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [10] = { - viewAngle = 80.663399,--FOV - hAngle = -57.531212, - vAngle = -6.648195, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [11] = {--look at left mirror - viewAngle = 60.203396,--FOV - hAngle = 55.124939, - vAngle = -8.400513, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [12] = {--look at right mirror - viewAngle = 60.203396,--FOV - hAngle = -52.633553, - vAngle = -8.400513, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [13] = {--default view - viewAngle = 90.000000,--FOV - hAngle = 0.000000, - vAngle = -18.382137, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, -}, -} -SnapViews["Su-27"] = { -[1] = {-- player slot 1 - [1] = { - viewAngle = 71.824692,--FOV - hAngle = 0.000000, - vAngle = -32.458889, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [2] = { - viewAngle = 33.361835,--FOV - hAngle = 41.045925, - vAngle = -40.805656, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [3] = { - viewAngle = 30.427544,--FOV - hAngle = 0.000000, - vAngle = -41.808968, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [4] = { - viewAngle = 34.392349,--FOV - hAngle = -32.597401, - vAngle = -35.293747, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [5] = { - viewAngle = 87.468338,--FOV - hAngle = 129.012665, - vAngle = 14.547977, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [6] = { - viewAngle = 43.977936,--FOV - hAngle = 0.000000, - vAngle = -4.951577, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [7] = { - viewAngle = 87.468338,--FOV - hAngle = -129.012665, - vAngle = 14.491872, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [8] = { - viewAngle = 87.468338,--FOV - hAngle = 82.862923, - vAngle = -9.500000, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [9] = { - viewAngle = 87.468338,--FOV - hAngle = 0.000000, - vAngle = 38.979362, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [10] = { - viewAngle = 87.468338,--FOV - hAngle = -82.461266, - vAngle = -12.843998, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [11] = {--look at left mirror - viewAngle = 68.786629,--FOV - hAngle = 15.618313, - vAngle = 7.522498, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [12] = {--look at right mirror - viewAngle = 69.165199,--FOV - hAngle = -15.683434, - vAngle = 8.549150, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [13] = {--default view - viewAngle = 87.468338,--FOV - hAngle = 0.000000, - vAngle = -9.500000, - x_trans = 0.113927, - y_trans = -0.004946, - z_trans = 0.000000, - rollAngle = 0.000000, - }, -}, -} -SnapViews["Su-33"] = { -[1] = {-- player slot 1 - [1] = { - viewAngle = 71.824692,--FOV - hAngle = 0.000000, - vAngle = -32.458889, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [2] = { - viewAngle = 33.361835,--FOV - hAngle = 41.045925, - vAngle = -40.805656, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [3] = { - viewAngle = 30.427544,--FOV - hAngle = 0.000000, - vAngle = -41.808968, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [4] = { - viewAngle = 34.392349,--FOV - hAngle = -32.597401, - vAngle = -35.293747, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [5] = { - viewAngle = 87.468338,--FOV - hAngle = 129.012665, - vAngle = 14.547977, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [6] = { - viewAngle = 43.977936,--FOV - hAngle = 0.000000, - vAngle = -4.951577, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [7] = { - viewAngle = 87.468338,--FOV - hAngle = -129.012665, - vAngle = 14.491872, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [8] = { - viewAngle = 87.468338,--FOV - hAngle = 82.862923, - vAngle = -9.500000, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [9] = { - viewAngle = 87.468338,--FOV - hAngle = 0.000000, - vAngle = 38.979362, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [10] = { - viewAngle = 87.468338,--FOV - hAngle = -82.461266, - vAngle = -12.843998, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [11] = {--look at left mirror - viewAngle = 68.786629,--FOV - hAngle = 15.618313, - vAngle = 7.522498, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [12] = {--look at right mirror - viewAngle = 69.165199,--FOV - hAngle = -15.683434, - vAngle = 8.549150, - x_trans = 0.000000, - y_trans = 0.000000, - z_trans = 0.000000, - rollAngle = 0.000000, - }, - [13] = {--default view - viewAngle = 87.468338,--FOV - hAngle = 0.000000, - vAngle = -9.500000, - x_trans = 0.113927, - y_trans = -0.004946, - z_trans = 0.000000, - rollAngle = 0.000000, - }, -}, -} \ No newline at end of file diff --git a/Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC/Config/View/View.lua b/Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC/Config/View/View.lua deleted file mode 100644 index 9baf3b7df..000000000 --- a/Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC/Config/View/View.lua +++ /dev/null @@ -1,128 +0,0 @@ --- View scripts --- Copyright (C) 2004, Eagle Dynamics. - -CockpitMouse = true --false -CockpitMouseSpeedSlow = 1.0 -CockpitMouseSpeedNormal = 10.0 -CockpitMouseSpeedFast = 20.0 -CockpitKeyboardAccelerationSlow = 5.0 -CockpitKeyboardAccelerationNormal = 30.0 -CockpitKeyboardAccelerationFast = 80.0 -CockpitKeyboardZoomAcceleration = 300.0 -DisableSnapViewsSaving = false -UseDefaultSnapViews = true -CockpitPanStepHor = 45.0 -CockpitPanStepVert = 30.0 -CockpitNyMove = true - -CockpitHAngleAccelerateTimeMax = 0.15 -CockpitVAngleAccelerateTimeMax = 0.15 -CockpitZoomAccelerateTimeMax = 0.2 - -function NaturalHeadMoving(tang, roll, omz) - local r = roll - if r > 90.0 then - r = 180.0 - r - elseif roll < -90.0 then - r = -180.0 - r - end - local hAngle = -0.25 * r - local vAngle = math.min(math.max(0.0, 0.4 * tang + 45.0 * omz), 90.0) - return hAngle, vAngle -end - -ExternalMouse = true -ExternalMouseSpeedSlow = 1.0 -ExternalMouseSpeedNormal = 5.0 -ExternalMouseSpeedFast = 20.0 -ExternalViewAngleMin = 3.0 -ExternalViewAngleMax = 170.0 -ExternalViewAngleDefault = 60.0 -ExternalKeyboardZoomAcceleration = 30.0 -ExternalKeyboardZoomAccelerateTimeMax = 1.0 -ExplosionExpoTime = 4.0 -ExternalKeyboardAccelerationSlow = 1.0 -ExternalKeyboardAccelerationNormal = 10.0 -ExternalKeyboardAccelerationFast = 30.0 -ExternalHAngleAccelerateTimeMax = 3.0 -ExternalVAngleAccelerateTimeMax = 3.0 -ExternalDistAccelerateTimeMax = 3.0 -ExternalHAngleLocalAccelerateTimeMax = 3.0 -ExternalVAngleLocalAccelerateTimeMax = 3.0 -ExternalAngleNormalDiscreteStep = 15.0/ExternalKeyboardAccelerationNormal -- When 'S' is pressed only -ChaseCameraNyMove = true -FreeCameraAngleIncrement = 3.0 -FreeCameraDistanceIncrement = 200.0 -FreeCameraLeftRightIncrement = 2.0 -FreeCameraAltitudeIncrement = 2.0 -FreeCameraScalarSpeedAcceleration = 0.1 -xMinMap = -300000 -xMaxMap = 500000 -yMinMap = -400000 -yMaxMap = 200000 -dxMap = 150000 -dyMap = 100000 - -head_roll_shaking = true -head_roll_shaking_max = 30.0 -head_roll_shaking_compensation_gain = 0.3 - --- CameraJiggle() and CameraFloat() functions make camera position --- dependent on FPS so be careful in using the Shift-J command with tracks, please. --- uncomment to use custom jiggle functions ---[[ -function CameraJiggle(t,rnd1,rnd2,rnd3) - local rotX, rotY, rotZ - rotX = 0.05 * rnd1 * math.sin(37.0 * (t - 0.0)) - rotY = 0.05 * rnd2 * math.sin(41.0 * (t - 1.0)) - rotZ = 0.05 * rnd3 * math.sin(53.0 * (t - 2.0)) - return rotX, rotY, rotZ -end - -function CameraFloat(t) - local dX, dY, dZ - dX = 0.61 * math.sin(0.7 * t) + 0.047 * math.sin(1.6 * t); - dY = 0.43 * math.sin(0.6 * t) + 0.067 * math.sin(1.7 * t); - dZ = 0.53 * math.sin(1.0 * t) + 0.083 * math.sin(1.9 * t); - return dX, dY, dZ -end ---]] ---Debug keys - -DEBUG_TEXT = 1 -DEBUG_GEOMETRY = 2 - -debug_keys = { - [DEBUG_TEXT] = 1, - [DEBUG_GEOMETRY] = 1 -} - -function onDebugCommand(command) - if command == 10000 then - if debug_keys[DEBUG_TEXT] ~= 0 or debug_keys[DEBUG_GEOMETRY] ~= 0 then - debug_keys[DEBUG_GEOMETRY] = 0 - debug_keys[DEBUG_TEXT] = 0 - else - debug_keys[DEBUG_GEOMETRY] = 1 - debug_keys[DEBUG_TEXT] = 1 - end - elseif command == 10001 then - if debug_keys[DEBUG_TEXT] ~= 0 then - debug_keys[DEBUG_TEXT] = 0 - else - debug_keys[DEBUG_TEXT] = 1 - end - elseif command == 10002 then - if debug_keys[DEBUG_GEOMETRY] ~= 0 then - debug_keys[DEBUG_GEOMETRY] = 0 - else - debug_keys[DEBUG_GEOMETRY] = 1 - end - end -end - --- gain values for TrackIR , to unify responce on diffrent types of aircraft -TrackIR_gain_x = -0.6 -TrackIR_gain_y = 0.3 -TrackIR_gain_z = -0.25 -TrackIR_gain_roll = -90 \ No newline at end of file diff --git a/Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC/Mods/aircraft/Ka-50/Cockpit/Scripts/ARK/ARK.lua b/Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC/Mods/aircraft/Ka-50/Cockpit/Scripts/ARK/ARK.lua deleted file mode 100644 index 61aa112a2..000000000 --- a/Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC/Mods/aircraft/Ka-50/Cockpit/Scripts/ARK/ARK.lua +++ /dev/null @@ -1,53 +0,0 @@ -mode = ADF_ADF -receiver_mode = ADF_RECEIVER_TLF -homer_selection_method = ADF_HOMER_SELECTION_AUTO -channel = 1 -volume = 0.5 - -local theatre = theatre or "none" -if theatre == 'Caucasus' then - -channels = { - [1] = runway_homer_pair(Airdrome[Krasnodar],nil,localizedAirdromeName(terrainAirdromes[Krasnodar])), - [2] = runway_homer_pair(Airdrome[Maykop] ,nil,localizedAirdromeName(terrainAirdromes[Maykop])), - [3] = runway_homer_pair(Airdrome[Krymsk] ,nil,localizedAirdromeName(terrainAirdromes[Krymsk])), - [4] = runway_homer_pair(Airdrome[Anapa] ,nil,localizedAirdromeName(terrainAirdromes[Anapa])), - [5] = runway_homer_pair(Airdrome[Mozdok] ,nil,localizedAirdromeName(terrainAirdromes[Mozdok])), - [6] = runway_homer_pair(Airdrome[Nalchick] ,nil,localizedAirdromeName(terrainAirdromes[Nalchick])), - [7] = runway_homer_pair(Airdrome[MinVody] ,nil,localizedAirdromeName(terrainAirdromes[MinVody])), - [8] = { - [ADF_HOMER_FAR] = NDB(beacons["NDB_KISLOVODSK"]), - [ADF_HOMER_NEAR] = NDB(beacons["NDB_PEREDOVAIA"]) - } -} - -elseif theatre == 'Nevada' then - - local beacons_by_name = {} - - for i,o in pairs(beacons) do - if o.name == '' then - beacons_by_name[o.beaconId] = o - else - beacons_by_name[o.name] = o - end - end - - local nevada_pair = function (id_1,id_2) return { - [ADF_HOMER_FAR] = NDB(beacons_by_name[id_1]), - [ADF_HOMER_NEAR] = NDB(beacons_by_name[id_2]) - } - end - - channels = { - nevada_pair('IndianSprings','Groom_Lake'), - nevada_pair('LasVegas','Nellis'), - nevada_pair("Milford","GOFFS"), - nevada_pair("Tonopah","Mina"), - nevada_pair("WilsonCreek","CedarCity"), - nevada_pair("BryceCanyon","MormonMesa"), - nevada_pair("Beatty","Bishop"), - nevada_pair("Coaldale","PeachSprings"), - nevada_pair("BoulderCity","Mercury"), -} -end \ No newline at end of file diff --git a/Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC/Scripts/World/GPS_GNSS.lua b/Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC/Scripts/World/GPS_GNSS.lua deleted file mode 100644 index 2cb4a8e8d..000000000 --- a/Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC/Scripts/World/GPS_GNSS.lua +++ /dev/null @@ -1,880 +0,0 @@ -SAT_SYS_GLONASS = 0 -SAT_SYS_GPS = 1 - -almanac = {} ---GPS -almanac[0] = {} -almanac[0]["System"] = SAT_SYS_GPS -almanac[0]["Number"] = 1 -almanac[0]["Orbital"] = "F" -almanac[0]["Eccentricity"] = 6.294000e-003 -almanac[0]["Time_of_Applicability"] = 5.898240e+005 -almanac[0]["Orbital_Inclination"] = 9.885676e-001 -almanac[0]["Rate_of_Right_Ascen"] = -7.862702e-009 -almanac[0]["SQRT_A"] = 5.153700e+003 -almanac[0]["Right_Ascen_at_Week"] = 8.096750e-001 -almanac[0]["Argument_of_Perigee"] = -1.777773e+000 -almanac[0]["Mean_Anom"] = -5.315745e-001 -almanac[0]["week"] = 1390 - -almanac[1] = {} -almanac[1]["System"] = SAT_SYS_GPS -almanac[1]["Number"] = 2 -almanac[1]["Orbital"] = "C" -almanac[1]["Eccentricity"] = 8.794000e-003 -almanac[1]["Time_of_Applicability"] = 5.898240e+005 -almanac[1]["Orbital_Inclination"] = 9.487811e-001 -almanac[1]["Rate_of_Right_Ascen"] = 0.000000e+000 -almanac[1]["SQRT_A"] = 5.153700e+003 -almanac[1]["Right_Ascen_at_Week"] = -1.329172e+000 -almanac[1]["Argument_of_Perigee"] = 2.138637e+000 -almanac[1]["Mean_Anom"] = 7.311702e-001 -almanac[1]["week"] = 1390 - -almanac[2] = {} -almanac[2]["System"] = SAT_SYS_GPS -almanac[2]["Number"] = 3 -almanac[2]["Orbital"] = "F" -almanac[2]["Eccentricity"] = 8.424000e-003 -almanac[2]["Time_of_Applicability"] = 5.898240e+005 -almanac[2]["Orbital_Inclination"] = 9.262804e-001 -almanac[2]["Rate_of_Right_Ascen"] = 0.000000e+000 -almanac[2]["SQRT_A"] = 5.153600e+003 -almanac[2]["Right_Ascen_at_Week"] = -2.341514e+000 -almanac[2]["Argument_of_Perigee"] = 6.749357e-001 -almanac[2]["Mean_Anom"] = -2.296153e-001 -almanac[2]["week"] = 1389 - -almanac[3] = {} -almanac[3]["System"] = SAT_SYS_GPS -almanac[3]["Number"] = 4 -almanac[3]["Orbital"] = "D" -almanac[3]["Eccentricity"] = 7.413000e-003 -almanac[3]["Time_of_Applicability"] = 5.898240e+005 -almanac[3]["Orbital_Inclination"] = 9.482889e-001 -almanac[3]["Rate_of_Right_Ascen"] = 0.000000e+000 -almanac[3]["SQRT_A"] = 5.153600e+003 -almanac[3]["Right_Ascen_at_Week"] = -1.309589e+000 -almanac[3]["Argument_of_Perigee"] = 1.623504e-001 -almanac[3]["Mean_Anom"] = -3.022943e+000 -almanac[3]["week"] = 1390 - -almanac[4] = {} -almanac[4]["System"] = SAT_SYS_GPS -almanac[4]["Number"] = 5 -almanac[4]["Orbital"] = "B" -almanac[4]["Eccentricity"] = 7.432000e-003 -almanac[4]["Time_of_Applicability"] = 5.898240e+005 -almanac[4]["Orbital_Inclination"] = 9.387437e-001 -almanac[4]["Rate_of_Right_Ascen"] = 0.000000e+000 -almanac[4]["SQRT_A"] = 5.153700e+003 -almanac[4]["Right_Ascen_at_Week"] = 2.779487e+000 -almanac[4]["Argument_of_Perigee"] = 1.099033e+000 -almanac[4]["Mean_Anom"] = 2.970984e+000 -almanac[4]["week"] = 1390 - -almanac[5] = {} -almanac[5]["System"] = SAT_SYS_GPS -almanac[5]["Number"] = 6 -almanac[5]["Orbital"] = "C" -almanac[5]["Eccentricity"] = 6.020000e-003 -almanac[5]["Time_of_Applicability"] = 5.898240e+005 -almanac[5]["Orbital_Inclination"] = 9.337591e-001 -almanac[5]["Rate_of_Right_Ascen"] = 0.000000e+000 -almanac[5]["SQRT_A"] = 5.153600e+003 -almanac[5]["Right_Ascen_at_Week"] = -2.407627e+000 -almanac[5]["Argument_of_Perigee"] = -1.788263e+000 -almanac[5]["Mean_Anom"] = -2.149877e+000 -almanac[5]["week"] = 1390 - -almanac[6] = {} -almanac[6]["System"] = SAT_SYS_GPS -almanac[6]["Number"] = 7 -almanac[6]["Orbital"] = "C" -almanac[6]["Eccentricity"] = 1.052400e-002 -almanac[6]["Time_of_Applicability"] = 5.898240e+005 -almanac[6]["Orbital_Inclination"] = 9.353229e-001 -almanac[6]["Rate_of_Right_Ascen"] = -8.080868e-009 -almanac[6]["SQRT_A"] = 5.153700e+003 -almanac[6]["Right_Ascen_at_Week"] = -2.433580e+000 -almanac[6]["Argument_of_Perigee"] = -1.767301e+000 -almanac[6]["Mean_Anom"] = -3.141503e+000 -almanac[6]["week"] = 1390 - -almanac[7] = {} -almanac[7]["System"] = SAT_SYS_GPS -almanac[7]["Number"] = 8 -almanac[7]["Orbital"] = "A" -almanac[7]["Eccentricity"] = 9.822000e-003 -almanac[7]["Time_of_Applicability"] = 5.898240e+005 -almanac[7]["Orbital_Inclination"] = 9.741390e-001 -almanac[7]["Rate_of_Right_Ascen"] = 0.000000e+000 -almanac[7]["SQRT_A"] = 5.153600e+003 -almanac[7]["Right_Ascen_at_Week"] = 1.857849e+000 -almanac[7]["Argument_of_Perigee"] = 2.674034e+000 -almanac[7]["Mean_Anom"] = -2.009745e+000 -almanac[7]["week"] = 1390 - -almanac[8] = {} -almanac[8]["System"] = SAT_SYS_GPS -almanac[8]["Number"] = 9 -almanac[8]["Orbital"] = "A" -almanac[8]["Eccentricity"] = 1.839300e-002 -almanac[8]["Time_of_Applicability"] = 5.898240e+005 -almanac[8]["Orbital_Inclination"] = 9.617541e-001 -almanac[8]["Rate_of_Right_Ascen"] = 0.000000e+000 -almanac[8]["SQRT_A"] = 5.153600e+003 -almanac[8]["Right_Ascen_at_Week"] = 1.777005e+000 -almanac[8]["Argument_of_Perigee"] = 1.274962e+000 -almanac[8]["Mean_Anom"] = -2.349578e+000 -almanac[8]["week"] = 1390 - -almanac[9] = {} -almanac[9]["System"] = SAT_SYS_GPS -almanac[9]["Number"] = 10 -almanac[9]["Orbital"] = "E" -almanac[9]["Eccentricity"] = 7.061000e-003 -almanac[9]["Time_of_Applicability"] = 5.898240e+005 -almanac[9]["Orbital_Inclination"] = 9.728876e-001 -almanac[9]["Rate_of_Right_Ascen"] = 0.000000e+000 -almanac[9]["SQRT_A"] = 5.153600e+003 -almanac[9]["Right_Ascen_at_Week"] = -2.563014e-001 -almanac[9]["Argument_of_Perigee"] = 4.377980e-001 -almanac[9]["Mean_Anom"] = 1.210716e+000 -almanac[9]["week"] = 1390 - -almanac[10] = {} -almanac[10]["System"] = SAT_SYS_GPS -almanac[10]["Number"] = 11 -almanac[10]["Orbital"] = "D" -almanac[10]["Eccentricity"] = 5.744000e-003 -almanac[10]["Time_of_Applicability"] = 5.898240e+005 -almanac[10]["Orbital_Inclination"] = 8.959309e-001 -almanac[10]["Rate_of_Right_Ascen"] = 0.000000e+000 -almanac[10]["SQRT_A"] = 5.153600e+003 -almanac[10]["Right_Ascen_at_Week"] = -1.478816e+000 -almanac[10]["Argument_of_Perigee"] = 3.750011e-001 -almanac[10]["Mean_Anom"] = -1.522048e+000 -almanac[10]["week"] = 1390 - -almanac[11] = {} -almanac[11]["System"] = SAT_SYS_GPS -almanac[11]["Number"] = 13 -almanac[11]["Orbital"] = "F" -almanac[11]["Eccentricity"] = 3.088000e-003 -almanac[11]["Time_of_Applicability"] = 5.898240e+005 -almanac[11]["Orbital_Inclination"] = 9.927564e-001 -almanac[11]["Rate_of_Right_Ascen"] = 0.000000e+000 -almanac[11]["SQRT_A"] = 5.153700e+003 -almanac[11]["Right_Ascen_at_Week"] = 7.956600e-001 -almanac[11]["Argument_of_Perigee"] = 1.279395e+000 -almanac[11]["Mean_Anom"] = 1.004349e+000 -almanac[11]["week"] = 1390 - -almanac[12] = {} -almanac[12]["System"] = SAT_SYS_GPS -almanac[12]["Number"] = 14 -almanac[12]["Orbital"] = "F" -almanac[12]["Eccentricity"] = 2.591000e-003 -almanac[12]["Time_of_Applicability"] = 5.898240e+005 -almanac[12]["Orbital_Inclination"] = 9.868729e-001 -almanac[12]["Rate_of_Right_Ascen"] = -7.885391e-009 -almanac[12]["SQRT_A"] = 5.153600e+003 -almanac[12]["Right_Ascen_at_Week"] = 7.819592e-001 -almanac[12]["Argument_of_Perigee"] = -2.158621e+000 -almanac[12]["Mean_Anom"] = 5.412611e-001 -almanac[12]["week"] = 1390 - -almanac[13] = {} -almanac[13]["System"] = SAT_SYS_GPS -almanac[13]["Number"] = 15 -almanac[13]["Orbital"] = "D" -almanac[13]["Eccentricity"] = 9.828000e-003 -almanac[13]["Time_of_Applicability"] = 3.194880e+005 -almanac[13]["Orbital_Inclination"] = 9.554204e-001 -almanac[13]["Rate_of_Right_Ascen"] = 0.000000e+000 -almanac[13]["SQRT_A"] = 5.153600e+003 -almanac[13]["Right_Ascen_at_Week"] = -1.123869e+000 -almanac[13]["Argument_of_Perigee"] = 2.690266e+000 -almanac[13]["Mean_Anom"] = 2.220476e+000 -almanac[13]["week"] = 1389 - -almanac[14] = {} -almanac[14]["System"] = SAT_SYS_GPS -almanac[14]["Number"] = 16 -almanac[14]["Orbital"] = "B" -almanac[14]["Eccentricity"] = 3.494000e-003 -almanac[14]["Time_of_Applicability"] = 5.898240e+005 -almanac[14]["Orbital_Inclination"] = 9.629340e-001 -almanac[14]["Rate_of_Right_Ascen"] = 0.000000e+000 -almanac[14]["SQRT_A"] = 5.153700e+003 -almanac[14]["Right_Ascen_at_Week"] = 2.873124e+000 -almanac[14]["Argument_of_Perigee"] = -7.819243e-001 -almanac[14]["Mean_Anom"] = 2.623629e+000 -almanac[14]["week"] = 1390 - -almanac[15] = {} -almanac[15]["System"] = SAT_SYS_GPS -almanac[15]["Number"] = 17 -almanac[15]["Orbital"] = "C" -almanac[15]["Eccentricity"] = 2.141000e-003 -almanac[15]["Time_of_Applicability"] = 5.898240e+005 -almanac[15]["Orbital_Inclination"] = 9.601170e-001 -almanac[15]["Rate_of_Right_Ascen"] = 0.000000e+000 -almanac[15]["SQRT_A"] = 5.153700e+003 -almanac[15]["Right_Ascen_at_Week"] = -2.371499e+000 -almanac[15]["Argument_of_Perigee"] = 3.087694e+000 -almanac[15]["Mean_Anom"] = 1.611217e+000 -almanac[15]["week"] = 1390 - -almanac[16] = {} -almanac[16]["System"] = SAT_SYS_GPS -almanac[16]["Number"] = 18 -almanac[16]["Orbital"] = "E" -almanac[16]["Eccentricity"] = 7.636000e-003 -almanac[16]["Time_of_Applicability"] = 5.898240e+005 -almanac[16]["Orbital_Inclination"] = 9.569597e-001 -almanac[16]["Rate_of_Right_Ascen"] = 0.000000e+000 -almanac[16]["SQRT_A"] = 5.153700e+003 -almanac[16]["Right_Ascen_at_Week"] = -2.359858e-001 -almanac[16]["Argument_of_Perigee"] = -2.649216e+000 -almanac[16]["Mean_Anom"] = 2.675029e+000 -almanac[16]["week"] = 1390 - -almanac[17] = {} -almanac[17]["System"] = SAT_SYS_GPS -almanac[17]["Number"] = 19 -almanac[17]["Orbital"] = "C" -almanac[17]["Eccentricity"] = 3.602000e-003 -almanac[17]["Time_of_Applicability"] = 5.898240e+005 -almanac[17]["Orbital_Inclination"] = 9.580209e-001 -almanac[17]["Rate_of_Right_Ascen"] = 0.000000e+000 -almanac[17]["SQRT_A"] = 5.153600e+003 -almanac[17]["Right_Ascen_at_Week"] = -2.312385e+000 -almanac[17]["Argument_of_Perigee"] = -1.161079e+000 -almanac[17]["Mean_Anom"] = 1.310619e+000 -almanac[17]["week"] = 1390 - -almanac[18] = {} -almanac[18]["System"] = SAT_SYS_GPS -almanac[18]["Number"] = 20 -almanac[18]["Orbital"] = "E" -almanac[18]["Eccentricity"] = 2.796000e-003 -almanac[18]["Time_of_Applicability"] = 5.898240e+005 -almanac[18]["Orbital_Inclination"] = 9.564693e-001 -almanac[18]["Rate_of_Right_Ascen"] = -7.908080e-009 -almanac[18]["SQRT_A"] = 5.153600e+003 -almanac[18]["Right_Ascen_at_Week"] = -2.889565e-001 -almanac[18]["Argument_of_Perigee"] = 1.379612e+000 -almanac[18]["Mean_Anom"] = 2.461750e+000 -almanac[18]["week"] = 1390 - -almanac[19] = {} -almanac[19]["System"] = SAT_SYS_GPS -almanac[19]["Number"] = 21 -almanac[19]["Orbital"] = "D" -almanac[19]["Eccentricity"] = 1.162900e-002 -almanac[19]["Time_of_Applicability"] = 5.898240e+005 -almanac[19]["Orbital_Inclination"] = 9.418592e-001 -almanac[19]["Rate_of_Right_Ascen"] = 0.000000e+000 -almanac[19]["SQRT_A"] = 5.153600e+003 -almanac[19]["Right_Ascen_at_Week"] = -1.289972e+000 -almanac[19]["Argument_of_Perigee"] = -2.923686e+000 -almanac[19]["Mean_Anom"] = -2.349194e+000 -almanac[19]["week"] = 1390 - -almanac[20] = {} -almanac[20]["System"] = SAT_SYS_GPS -almanac[20]["Number"] = 22 -almanac[20]["Orbital"] = "E" -almanac[20]["Eccentricity"] = 4.893000e-003 -almanac[20]["Time_of_Applicability"] = 5.898240e+005 -almanac[20]["Orbital_Inclination"] = 9.545093e-001 -almanac[20]["Rate_of_Right_Ascen"] = 0.000000e+000 -almanac[20]["SQRT_A"] = 5.153600e+003 -almanac[20]["Right_Ascen_at_Week"] = -2.280969e-001 -almanac[20]["Argument_of_Perigee"] = -1.674502e+000 -almanac[20]["Mean_Anom"] = 1.106852e+000 -almanac[20]["week"] = 1390 - -almanac[21] = {} -almanac[21]["System"] = SAT_SYS_GPS -almanac[21]["Number"] = 23 -almanac[21]["Orbital"] = "F" -almanac[21]["Eccentricity"] = 4.822000e-003 -almanac[21]["Time_of_Applicability"] = 5.898240e+005 -almanac[21]["Orbital_Inclination"] = 9.691247e-001 -almanac[21]["Rate_of_Right_Ascen"] = 0.000000e+000 -almanac[21]["SQRT_A"] = 5.153700e+003 -almanac[21]["Right_Ascen_at_Week"] = 7.667399e-001 -almanac[21]["Argument_of_Perigee"] = 2.497634e+000 -almanac[21]["Mean_Anom"] = 3.184700e-001 -almanac[21]["week"] = 1390 - -almanac[22] = {} -almanac[22]["System"] = SAT_SYS_GPS -almanac[22]["Number"] = 24 -almanac[22]["Orbital"] = "D" -almanac[22]["Eccentricity"] = 9.277000e-003 -almanac[22]["Time_of_Applicability"] = 5.898240e+005 -almanac[22]["Orbital_Inclination"] = 9.585183e-001 -almanac[22]["Rate_of_Right_Ascen"] = 0.000000e+000 -almanac[22]["SQRT_A"] = 5.153900e+003 -almanac[22]["Right_Ascen_at_Week"] = -1.274840e+000 -almanac[22]["Argument_of_Perigee"] = -8.815651e-001 -almanac[22]["Mean_Anom"] = -1.695551e+000 -almanac[22]["week"] = 1390 - -almanac[23] = {} -almanac[23]["System"] = SAT_SYS_GPS -almanac[23]["Number"] = 25 -almanac[23]["Orbital"] = "A" -almanac[23]["Eccentricity"] = 1.257400e-002 -almanac[23]["Time_of_Applicability"] = 5.898240e+005 -almanac[23]["Orbital_Inclination"] = 9.551027e-001 -almanac[23]["Rate_of_Right_Ascen"] = 0.000000e+000 -almanac[23]["SQRT_A"] = 5.153600e+003 -almanac[23]["Right_Ascen_at_Week"] = 1.721853e+000 -almanac[23]["Argument_of_Perigee"] = -1.329870e+000 -almanac[23]["Mean_Anom"] = -1.769623e+000 -almanac[23]["week"] = 1390 - -almanac[24] = {} -almanac[24]["System"] = SAT_SYS_GPS -almanac[24]["Number"] = 26 -almanac[24]["Orbital"] = "F" -almanac[24]["Eccentricity"] = 1.745700e-002 -almanac[24]["Time_of_Applicability"] = 5.898240e+005 -almanac[24]["Orbital_Inclination"] = 9.908749e-001 -almanac[24]["Rate_of_Right_Ascen"] = -7.840012e-009 -almanac[24]["SQRT_A"] = 5.153700e+003 -almanac[24]["Right_Ascen_at_Week"] = 7.961836e-001 -almanac[24]["Argument_of_Perigee"] = 8.161502e-001 -almanac[24]["Mean_Anom"] = -5.841961e-001 -almanac[24]["week"] = 1390 - -almanac[25] = {} -almanac[25]["System"] = SAT_SYS_GPS -almanac[25]["Number"] = 27 -almanac[25]["Orbital"] = "A" -almanac[25]["Eccentricity"] = 1.991000e-002 -almanac[25]["Time_of_Applicability"] = 5.898240e+005 -almanac[25]["Orbital_Inclination"] = 9.596563e-001 -almanac[25]["Rate_of_Right_Ascen"] = 0.000000e+000 -almanac[25]["SQRT_A"] = 5.153600e+003 -almanac[25]["Right_Ascen_at_Week"] = 1.754124e+000 -almanac[25]["Argument_of_Perigee"] = -1.900854e+000 -almanac[25]["Mean_Anom"] = 3.046487e+000 -almanac[25]["week"] = 1390 - -almanac[26] = {} -almanac[26]["System"] = SAT_SYS_GPS -almanac[26]["Number"] = 28 -almanac[26]["Orbital"] = "B" -almanac[26]["Eccentricity"] = 1.162800e-002 -almanac[26]["Time_of_Applicability"] = 5.898240e+005 -almanac[26]["Orbital_Inclination"] = 9.610106e-001 -almanac[26]["Rate_of_Right_Ascen"] = 0.000000e+000 -almanac[26]["SQRT_A"] = 5.153600e+003 -almanac[26]["Right_Ascen_at_Week"] = 2.882583e+000 -almanac[26]["Argument_of_Perigee"] = -2.242868e+000 -almanac[26]["Mean_Anom"] = 1.860642e+000 -almanac[26]["week"] = 1390 - -almanac[27] = {} -almanac[27]["System"] = SAT_SYS_GPS -almanac[27]["Number"] = 29 -almanac[27]["Orbital"] = "F" -almanac[27]["Eccentricity"] = 9.462000e-003 -almanac[27]["Time_of_Applicability"] = 1.474560e+005 -almanac[27]["Orbital_Inclination"] = 9.874838e-001 -almanac[27]["Rate_of_Right_Ascen"] = 0.000000e+000 -almanac[27]["SQRT_A"] = 5.153700e+003 -almanac[27]["Right_Ascen_at_Week"] = 7.647503e-001 -almanac[27]["Argument_of_Perigee"] = -8.614589e-001 -almanac[27]["Mean_Anom"] = -4.488983e-001 -almanac[27]["week"] = 1390 - -almanac[28] = {} -almanac[28]["System"] = SAT_SYS_GPS -almanac[28]["Number"] = 30 -almanac[28]["Orbital"] = "B" -almanac[28]["Eccentricity"] = 9.296000e-003 -almanac[28]["Time_of_Applicability"] = 5.898240e+005 -almanac[28]["Orbital_Inclination"] = 9.452992e-001 -almanac[28]["Rate_of_Right_Ascen"] = 0.000000e+000 -almanac[28]["SQRT_A"] = 5.153600e+003 -almanac[28]["Right_Ascen_at_Week"] = 2.826698e+000 -almanac[28]["Argument_of_Perigee"] = 1.306413e+000 -almanac[28]["Mean_Anom"] = 2.148725e+000 -almanac[28]["week"] = 1390 - - - - - - ---GLONASS ---1 îðáèòàëüíàÿ ïëîñêîñòü, íîìåðà 1-8 -almanac[29] = {} -almanac[29]["System"] = SAT_SYS_GLONASS -almanac[29]["Number"] = 1 -almanac[29]["Orbital"] = 1 -almanac[29]["GLONASS_Data"] = {} -almanac[29]["GLONASS_Data"]["NKU_Number"] = 796 -almanac[29]["GLONASS_Data"]["Cosmos_Number"] = 2411 -almanac[29]["Eccentricity"] = 1.184000e-003 -almanac[29]["Time_of_Applicability"] = 0.000000e+000 -almanac[29]["Orbital_Inclination"] = 1.126443e+000 -almanac[29]["Rate_of_Right_Ascen"] = 0.000000e+000 -almanac[29]["SQRT_A"] = 5.050500e+003 -almanac[29]["Right_Ascen_at_Week"] = 5.979807e+000 -almanac[29]["Argument_of_Perigee"] = 2.622634e+000 -almanac[29]["Mean_Anom"] = -5.519651e+000 -almanac[29]["week"] = 1390 -almanac[29]["Commit_date"] = "06.02.2005" -almanac[29]["Life_dates"] = {} - -almanac[30] = {} -almanac[30]["System"] = SAT_SYS_GLONASS -almanac[30]["Number"] = 2 -almanac[30]["Orbital"] = 1 -almanac[30]["GLONASS_Data"] = {} -almanac[30]["GLONASS_Data"]["NKU_Number"] = 794 -almanac[30]["GLONASS_Data"]["Cosmos_Number"] = 2401 -almanac[30]["Eccentricity"] = 4.486000e-003 -almanac[30]["Time_of_Applicability"] = 0.000000e+000 -almanac[30]["Orbital_Inclination"] = 1.128459e+000 -almanac[30]["Rate_of_Right_Ascen"] = -6.759654e-009 -almanac[30]["SQRT_A"] = 5.050500e+003 -almanac[30]["Right_Ascen_at_Week"] = 5.997871e+000 -almanac[30]["Argument_of_Perigee"] = 1.709531e+000 -almanac[30]["Mean_Anom"] = -5.367633e+000 -almanac[30]["week"] = 1390 -almanac[30]["Commit_date"] = "02.02.2004" -almanac[30]["Life_dates"] = {} - -almanac[31] = {} -almanac[31]["System"] = SAT_SYS_GLONASS -almanac[31]["Number"] = 3 -almanac[31]["Orbital"] = 1 -almanac[31]["GLONASS_Data"] = {} -almanac[31]["GLONASS_Data"]["NKU_Number"] = 789 -almanac[31]["GLONASS_Data"]["Cosmos_Number"] = 2381 -almanac[31]["Eccentricity"] = 2.459000e-003 -almanac[31]["Time_of_Applicability"] = 0.000000e+000 -almanac[31]["Orbital_Inclination"] = 1.122958e+000 -almanac[31]["Rate_of_Right_Ascen"] = 0.000000e+000 -almanac[31]["SQRT_A"] = 5.050500e+003 -almanac[31]["Right_Ascen_at_Week"] = 5.960713e+000 -almanac[31]["Argument_of_Perigee"] = -2.683407e+000 -almanac[31]["Mean_Anom"] = -1.791788e+000 -almanac[31]["week"] = 1390 -almanac[31]["Commit_date"] = "04.01.2002" -almanac[31]["Life_dates"] = {} - -almanac[32] = {} -almanac[32]["System"] = SAT_SYS_GLONASS -almanac[32]["Number"] = 4 -almanac[32]["Orbital"] = 1 -almanac[32]["GLONASS_Data"] = {} -almanac[32]["GLONASS_Data"]["NKU_Number"] = 795 -almanac[29]["GLONASS_Data"]["Cosmos_Number"] = 2403 -almanac[32]["Eccentricity"] = 4.054000e-003 -almanac[32]["Time_of_Applicability"] = 0.000000e+000 -almanac[32]["Orbital_Inclination"] = 1.128543e+000 -almanac[32]["Rate_of_Right_Ascen"] = 0.000000e+000 -almanac[32]["SQRT_A"] = 5.050500e+003 -almanac[32]["Right_Ascen_at_Week"] = 5.998081e+000 -almanac[32]["Argument_of_Perigee"] = 1.497160e+000 -almanac[32]["Mean_Anom"] = -4.293681e-001 -almanac[32]["week"] = 1390 -almanac[32]["Commit_date"] = "29.01.2004" -almanac[32]["Life_dates"] = {} - -almanac[33] = {} -almanac[33]["System"] = SAT_SYS_GLONASS -almanac[33]["Number"] = 5 -almanac[33]["Orbital"] = 1 -almanac[33]["GLONASS_Data"] = {} -almanac[33]["GLONASS_Data"]["NKU_Number"] = 711 -almanac[33]["GLONASS_Data"]["Cosmos_Number"] = 2382 -almanac[33]["Eccentricity"] = 7.040000e-004 -almanac[33]["Time_of_Applicability"] = 0.000000e+000 -almanac[33]["Orbital_Inclination"] = 1.122886e+000 -almanac[33]["Rate_of_Right_Ascen"] = 0.000000e+000 -almanac[33]["SQRT_A"] = 5.050600e+003 -almanac[33]["Right_Ascen_at_Week"] = 5.960713e+000 -almanac[33]["Argument_of_Perigee"] = 2.740933e+000 -almanac[33]["Mean_Anom"] = -2.523604e+000 -almanac[33]["week"] = 1390 -almanac[33]["Commit_date"] = "13.02.2003" -almanac[33]["Life_dates"] = {} - -almanac[34] = {} -almanac[34]["System"] = SAT_SYS_GLONASS -almanac[34]["Number"] = 6 -almanac[34]["Orbital"] = 1 -almanac[34]["GLONASS_Data"] = {} -almanac[34]["GLONASS_Data"]["NKU_Number"] = 701 -almanac[34]["GLONASS_Data"]["Cosmos_Number"] = 2404 -almanac[34]["Eccentricity"] = 4.766000e-003 -almanac[34]["Time_of_Applicability"] = 0.000000e+000 -almanac[34]["Orbital_Inclination"] = 1.128276e+000 -almanac[34]["Rate_of_Right_Ascen"] = 0.000000e+000 -almanac[34]["SQRT_A"] = 5.050500e+003 -almanac[34]["Right_Ascen_at_Week"] = 5.997906e+000 -almanac[34]["Argument_of_Perigee"] = 1.802417e+000 -almanac[34]["Mean_Anom"] = -2.426512e+000 -almanac[34]["week"] = 1390 -almanac[34]["Commit_date"] = "08.12.2004" -almanac[34]["Life_dates"] = {} - -almanac[35] = {} -almanac[35]["System"] = SAT_SYS_GLONASS -almanac[35]["Number"] = 7 -almanac[35]["Orbital"] = 1 -almanac[35]["GLONASS_Data"] = {} -almanac[35]["GLONASS_Data"]["NKU_Number"] = 712 -almanac[35]["GLONASS_Data"]["Cosmos_Number"] = 2413 -almanac[35]["Eccentricity"] = 7.570000e-004 -almanac[35]["Time_of_Applicability"] = 0.000000e+000 -almanac[35]["Orbital_Inclination"] = 1.126344e+000 -almanac[35]["Rate_of_Right_Ascen"] = 0.000000e+000 -almanac[35]["SQRT_A"] = 5.050500e+003 -almanac[35]["Right_Ascen_at_Week"] = 5.979388e+000 -almanac[35]["Argument_of_Perigee"] = 2.566068e+000 -almanac[35]["Mean_Anom"] = -3.921228e+000 -almanac[35]["week"] = 1390 -almanac[35]["Commit_date"] = "07.10.2005" -almanac[35]["Life_dates"] = {} - -almanac[36] = {} -almanac[36]["System"] = SAT_SYS_GLONASS -almanac[36]["GLONASS_Data"] = {} -almanac[36]["Number"] = 8 -almanac[36]["Orbital"] = 1 -almanac[36]["GLONASS_Data"] = {} -almanac[36]["GLONASS_Data"]["NKU_Number"] = 797 -almanac[36]["GLONASS_Data"]["Cosmos_Number"] = 2412 -almanac[36]["Eccentricity"] = 4.060000e-004 -almanac[36]["Time_of_Applicability"] = 0.000000e+000 -almanac[36]["Orbital_Inclination"] = 1.126564e+000 -almanac[36]["Rate_of_Right_Ascen"] = -6.785834e-009 -almanac[36]["SQRT_A"] = 5.050600e+003 -almanac[36]["Right_Ascen_at_Week"] = 5.980069e+000 -almanac[36]["Argument_of_Perigee"] = 2.673633e+000 -almanac[36]["Mean_Anom"] = -4.812026e+000 -almanac[36]["week"] = 1390 -almanac[36]["Commit_date"] = "06.02.2005" -almanac[36]["Life_dates"] = {} - ---3 îðáèòàëüíàÿ ïëîñêîñòü, íîìåðà 17-24 -almanac[37] = {} -almanac[37]["System"] = SAT_SYS_GLONASS -almanac[37]["Number"] = 17 -almanac[37]["Orbital"] = 3 -almanac[37]["GLONASS_Data"] = {} -almanac[37]["GLONASS_Data"]["NKU_Number"] = 787 -almanac[37]["GLONASS_Data"]["Cosmos_Number"] = 2375 -almanac[37]["Eccentricity"] = 5.670000e-004 -almanac[37]["Time_of_Applicability"] = 0.000000e+000 -almanac[37]["Orbital_Inclination"] = 1.126524e+000 -almanac[37]["Rate_of_Right_Ascen"] = 0.000000e+000 -almanac[37]["SQRT_A"] = 5.050500e+003 -almanac[37]["Right_Ascen_at_Week"] = 3.895554e+000 -almanac[37]["Argument_of_Perigee"] = 6.085085e-001 -almanac[37]["Mean_Anom"] = -2.977407e+000 -almanac[37]["week"] = 1390 -almanac[37]["Commit_date"] = "04.11.2000" -almanac[37]["Life_dates"] = {} - - -almanac[38] = {} -almanac[38]["System"] = SAT_SYS_GLONASS -almanac[38]["Number"] = 18 -almanac[38]["Orbital"] = 3 -almanac[38]["GLONASS_Data"] = {} -almanac[38]["GLONASS_Data"]["NKU_Number"] = 783 -almanac[38]["GLONASS_Data"]["Cosmos_Number"] = 2374 -almanac[38]["Eccentricity"] = 4.520000e-003 -almanac[38]["Time_of_Applicability"] = 0.000000e+000 -almanac[38]["Orbital_Inclination"] = 1.126239e+000 -almanac[38]["Rate_of_Right_Ascen"] = 0.000000e+000 -almanac[38]["SQRT_A"] = 5.050600e+003 -almanac[38]["Right_Ascen_at_Week"] = 3.894071e+000 -almanac[38]["Argument_of_Perigee"] = -2.509589e+000 -almanac[38]["Mean_Anom"] = -1.020057e+000 -almanac[38]["week"] = 1390 -almanac[38]["Commit_date"] = "05.01.2001" -almanac[38]["Life_dates"] = {} - -almanac[39] = {} -almanac[39]["System"] = SAT_SYS_GLONASS -almanac[39]["Number"] = 19 -almanac[39]["Orbital"] = 3 -almanac[39]["GLONASS_Data"] = {} -almanac[39]["GLONASS_Data"]["NKU_Number"] = 798 -almanac[39]["GLONASS_Data"]["Cosmos_Number"] = 2417 -almanac[39]["Eccentricity"] = 2.023000e-003 -almanac[39]["Time_of_Applicability"] = 0.000000e+000 -almanac[39]["Orbital_Inclination"] = 1.132205e+000 -almanac[39]["Rate_of_Right_Ascen"] = 0.000000e+000 -almanac[39]["SQRT_A"] = 5.050500e+003 -almanac[39]["Right_Ascen_at_Week"] = 3.884018e+000 -almanac[39]["Argument_of_Perigee"] = 2.718313e+000 -almanac[39]["Mean_Anom"] = -3.933620e-001 -almanac[39]["week"] = 1390 -almanac[39]["Commit_date"] = "22.01.2006" -almanac[39]["Life_dates"] = {} - -almanac[40] = {} -almanac[40]["System"] = SAT_SYS_GLONASS -almanac[40]["Number"] = 20 -almanac[40]["Orbital"] = 3 -almanac[40]["GLONASS_Data"] = {} -almanac[40]["GLONASS_Data"]["NKU_Number"] = 793 -almanac[40]["GLONASS_Data"]["Cosmos_Number"] = 2396 -almanac[40]["Eccentricity"] = 1.822000e-003 -almanac[40]["Time_of_Applicability"] = 0.000000e+000 -almanac[40]["Orbital_Inclination"] = 1.129789e+000 -almanac[40]["Rate_of_Right_Ascen"] = 0.000000e+000 -almanac[40]["SQRT_A"] = 5.050500e+003 -almanac[40]["Right_Ascen_at_Week"] = 3.896863e+000 -almanac[40]["Argument_of_Perigee"] = 2.723776e+000 -almanac[40]["Mean_Anom"] = -1.193647e+000 -almanac[40]["week"] = 1390 -almanac[40]["Commit_date"] = "31.01.2003" -almanac[40]["Life_dates"] = {} - -almanac[41] = {} -almanac[41]["System"] = SAT_SYS_GLONASS -almanac[41]["Number"] = 21 -almanac[41]["Orbital"] = 3 -almanac[41]["GLONASS_Data"] = {} -almanac[41]["GLONASS_Data"]["NKU_Number"] = 792 -almanac[41]["GLONASS_Data"]["Cosmos_Number"] = 2395 -almanac[41]["Eccentricity"] = 5.290000e-004 -almanac[41]["Time_of_Applicability"] = 0.000000e+000 -almanac[41]["Orbital_Inclination"] = 1.129957e+000 -almanac[41]["Rate_of_Right_Ascen"] = 0.000000e+000 -almanac[41]["SQRT_A"] = 5.050500e+003 -almanac[41]["Right_Ascen_at_Week"] = 3.897806e+000 -almanac[41]["Argument_of_Perigee"] = -9.519367e-001 -almanac[41]["Mean_Anom"] = -4.578920e+000 -almanac[41]["week"] = 1390 -almanac[41]["Commit_date"] = "31.01.2003" -almanac[41]["Life_dates"] = {} - -almanac[42] = {} -almanac[42]["System"] = SAT_SYS_GLONASS -almanac[42]["Number"] = 22 -almanac[42]["Orbital"] = 3 -almanac[42]["GLONASS_Data"] = {} -almanac[42]["GLONASS_Data"]["NKU_Number"] = 791 -almanac[42]["GLONASS_Data"]["Cosmos_Number"] = 2394 -almanac[42]["Eccentricity"] = 9.200000e-005 -almanac[42]["Time_of_Applicability"] = 0.000000e+000 -almanac[42]["Orbital_Inclination"] = 1.129742e+000 -almanac[42]["Rate_of_Right_Ascen"] = -6.740456e-009 -almanac[42]["SQRT_A"] = 5.050500e+003 -almanac[42]["Right_Ascen_at_Week"] = 3.897404e+000 -almanac[42]["Argument_of_Perigee"] = 2.518211e+000 -almanac[42]["Mean_Anom"] = -2.530167e+000 -almanac[42]["week"] = 1390 -almanac[42]["Commit_date"] = "21.01.2003" -almanac[42]["Life_dates"] = {} - -almanac[43] = {} -almanac[43]["System"] = SAT_SYS_GLONASS -almanac[43]["Number"] = 23 -almanac[43]["Orbital"] = 3 -almanac[43]["GLONASS_Data"] = {} -almanac[43]["GLONASS_Data"]["NKU_Number"] = 714 -almanac[43]["GLONASS_Data"]["Cosmos_Number"] = 2419 -almanac[43]["Eccentricity"] = 8.730000e-004 -almanac[43]["Time_of_Applicability"] = 0.000000e+000 -almanac[43]["Orbital_Inclination"] = 1.132105e+000 -almanac[43]["Rate_of_Right_Ascen"] = 0.000000e+000 -almanac[43]["SQRT_A"] = 5.050500e+003 -almanac[43]["Right_Ascen_at_Week"] = 3.883808e+000 -almanac[43]["Argument_of_Perigee"] = -3.039139e-001 -almanac[43]["Mean_Anom"] = -5.228304e-001 -almanac[43]["week"] = 1390 -almanac[43]["Commit_date"] = "31.08.2006" -almanac[43]["Life_dates"] = {} - -almanac[44] = {} -almanac[44]["System"] = SAT_SYS_GLONASS -almanac[44]["Number"] = 24 -almanac[44]["Orbital"] = 3 -almanac[44]["GLONASS_Data"] = {} -almanac[44]["GLONASS_Data"]["NKU_Number"] = 713 -almanac[44]["GLONASS_Data"]["Cosmos_Number"] = 2418 -almanac[44]["Eccentricity"] = 2.044000e-003 -almanac[44]["Time_of_Applicability"] = 0.000000e+000 -almanac[44]["Orbital_Inclination"] = 1.132430e+000 -almanac[44]["Rate_of_Right_Ascen"] = 0.000000e+000 -almanac[44]["SQRT_A"] = 5.050500e+003 -almanac[44]["Right_Ascen_at_Week"] = 3.883983e+000 -almanac[44]["Argument_of_Perigee"] = -3.722784e-001 -almanac[44]["Mean_Anom"] = -1.240457e+000 -almanac[44]["week"] = 1390 -almanac[44]["Commit_date"] = "31.08.2006" -almanac[44]["Life_dates"] = {} - ---2 îðáèòàëüíàÿ ïëîñêîñòü, íîìåðà 9-16 -almanac[45] = {} -almanac[45]["System"] = SAT_SYS_GLONASS -almanac[45]["Number"] = 9 -almanac[45]["Orbital"] = 2 -almanac[45]["GLONASS_Data"] = {} -almanac[45]["GLONASS_Data"]["NKU_Number"] = "N/A" -almanac[45]["GLONASS_Data"]["Cosmos_Number"] = "N/A" -almanac[45]["Eccentricity"] = 1.184000e-003 -almanac[45]["Time_of_Applicability"] = 0.000000e+000 -almanac[45]["Orbital_Inclination"] = 1.126443e+000 -almanac[45]["Rate_of_Right_Ascen"] = 0.000000e+000 -almanac[45]["SQRT_A"] = 5.050500e+003 -almanac[45]["Right_Ascen_at_Week"] = 1.79067e+000 -almanac[45]["Argument_of_Perigee"] = 2.88430067 -almanac[45]["Mean_Anom"] = -5.519651e+000 -almanac[45]["week"] = 1390 -almanac[45]["Commit_date"] = "N/A" -almanac[45]["Life_dates"] = {} - -almanac[46] = {} -almanac[46]["System"] = SAT_SYS_GLONASS -almanac[46]["Number"] = 10 -almanac[46]["Orbital"] = 2 -almanac[46]["GLONASS_Data"] = {} -almanac[46]["GLONASS_Data"]["NKU_Number"] = "N/A" -almanac[46]["GLONASS_Data"]["Cosmos_Number"] = "N/A" -almanac[46]["Eccentricity"] = 1.184000e-003 -almanac[46]["Time_of_Applicability"] = 0.000000e+000 -almanac[46]["Orbital_Inclination"] = 1.126443e+000 -almanac[46]["Rate_of_Right_Ascen"] = 0.000000e+000 -almanac[46]["SQRT_A"] = 5.050500e+003 -almanac[46]["Right_Ascen_at_Week"] = 1.79067e+000 -almanac[46]["Argument_of_Perigee"] = 3.66930067 -almanac[46]["Mean_Anom"] = -5.519651e+000 -almanac[46]["week"] = 1390 -almanac[46]["Commit_date"] = "N/A" -almanac[46]["Life_dates"] = {} - -almanac[47] = {} -almanac[47]["System"] = SAT_SYS_GLONASS -almanac[47]["Number"] = 11 -almanac[47]["Orbital"] = 2 -almanac[47]["GLONASS_Data"] = {} -almanac[47]["GLONASS_Data"]["NKU_Number"] = "N/A" -almanac[47]["GLONASS_Data"]["Cosmos_Number"] = "N/A" -almanac[47]["Eccentricity"] = 1.184000e-003 -almanac[47]["Time_of_Applicability"] = 0.000000e+000 -almanac[47]["Orbital_Inclination"] = 1.126443e+000 -almanac[47]["Rate_of_Right_Ascen"] = 0.000000e+000 -almanac[47]["SQRT_A"] = 5.050500e+003 -almanac[47]["Right_Ascen_at_Week"] = 1.79067e+000 -almanac[47]["Argument_of_Perigee"] = 4.45430067 -almanac[47]["Mean_Anom"] = -5.519651e+000 -almanac[47]["week"] = 1390 -almanac[47]["Commit_date"] = "N/A" -almanac[47]["Life_dates"] = {} - -almanac[48] = {} -almanac[48]["System"] = SAT_SYS_GLONASS -almanac[48]["Number"] = 12 -almanac[48]["Orbital"] = 2 -almanac[48]["GLONASS_Data"] = {} -almanac[48]["GLONASS_Data"]["NKU_Number"] = "N/A" -almanac[48]["GLONASS_Data"]["Cosmos_Number"] = "N/A" -almanac[48]["Eccentricity"] = 1.184000e-003 -almanac[48]["Time_of_Applicability"] = 0.000000e+000 -almanac[48]["Orbital_Inclination"] = 1.126443e+000 -almanac[48]["Rate_of_Right_Ascen"] = 0.000000e+000 -almanac[48]["SQRT_A"] = 5.050500e+003 -almanac[48]["Right_Ascen_at_Week"] = 1.79067e+000 -almanac[48]["Argument_of_Perigee"] = 5.23930067 -almanac[48]["Mean_Anom"] = -5.519651e+000 -almanac[48]["week"] = 1390 -almanac[48]["Commit_date"] = "N/A" -almanac[48]["Life_dates"] = {} - -almanac[49] = {} -almanac[49]["System"] = SAT_SYS_GLONASS -almanac[49]["Number"] = 13 -almanac[49]["Orbital"] = 2 -almanac[49]["GLONASS_Data"] = {} -almanac[49]["GLONASS_Data"]["NKU_Number"] = "N/A" -almanac[49]["GLONASS_Data"]["Cosmos_Number"] = "N/A" -almanac[49]["Eccentricity"] = 1.184000e-003 -almanac[49]["Time_of_Applicability"] = 0.000000e+000 -almanac[49]["Orbital_Inclination"] = 1.126443e+000 -almanac[49]["Rate_of_Right_Ascen"] = 0.000000e+000 -almanac[49]["SQRT_A"] = 5.050500e+003 -almanac[49]["Right_Ascen_at_Week"] = 1.79067e+000 -almanac[49]["Argument_of_Perigee"] = 6.02430067 -almanac[49]["Mean_Anom"] = -5.519651e+000 -almanac[49]["week"] = 1390 -almanac[49]["Commit_date"] = "N/A" -almanac[49]["Life_dates"] = {} - -almanac[50] = {} -almanac[50]["System"] = SAT_SYS_GLONASS -almanac[50]["Number"] = 14 -almanac[50]["Orbital"] = 2 -almanac[50]["GLONASS_Data"] = {} -almanac[50]["GLONASS_Data"]["NKU_Number"] = "N/A" -almanac[50]["GLONASS_Data"]["Cosmos_Number"] = "N/A" -almanac[50]["Eccentricity"] = 1.184000e-003 -almanac[50]["Time_of_Applicability"] = 0.000000e+000 -almanac[50]["Orbital_Inclination"] = 1.126443e+000 -almanac[50]["Rate_of_Right_Ascen"] = 0.000000e+000 -almanac[50]["SQRT_A"] = 5.050500e+003 -almanac[50]["Right_Ascen_at_Week"] = 1.79067e+000 -almanac[50]["Argument_of_Perigee"] = 0.52930067 -almanac[50]["Mean_Anom"] = -5.519651e+000 -almanac[50]["week"] = 1390 -almanac[50]["Commit_date"] = "N/A" -almanac[50]["Life_dates"] = {} - -almanac[51] = {} -almanac[51]["System"] = SAT_SYS_GLONASS -almanac[51]["Number"] = 15 -almanac[51]["Orbital"] = 2 -almanac[51]["GLONASS_Data"] = {} -almanac[51]["GLONASS_Data"]["NKU_Number"] = "N/A" -almanac[51]["GLONASS_Data"]["Cosmos_Number"] = "N/A" -almanac[51]["Eccentricity"] = 1.184000e-003 -almanac[51]["Time_of_Applicability"] = 0.000000e+000 -almanac[51]["Orbital_Inclination"] = 1.126443e+000 -almanac[51]["Rate_of_Right_Ascen"] = 0.000000e+000 -almanac[51]["SQRT_A"] = 5.050500e+003 -almanac[51]["Right_Ascen_at_Week"] = 1.79067e+000 -almanac[51]["Argument_of_Perigee"] = 1.31430067 -almanac[51]["Mean_Anom"] = -5.519651e+000 -almanac[51]["week"] = 1390 -almanac[51]["Commit_date"] = "N/A" -almanac[51]["Life_dates"] = {} - -almanac[52] = {} -almanac[52]["System"] = SAT_SYS_GLONASS -almanac[52]["Number"] = 16 -almanac[52]["Orbital"] = 2 -almanac[52]["GLONASS_Data"] = {} -almanac[52]["GLONASS_Data"]["NKU_Number"] = "N/A" -almanac[52]["GLONASS_Data"]["Cosmos_Number"] = "N/A" -almanac[52]["Eccentricity"] = 1.184000e-003 -almanac[52]["Time_of_Applicability"] = 0.000000e+000 -almanac[52]["Orbital_Inclination"] = 1.126443e+000 -almanac[52]["Rate_of_Right_Ascen"] = 0.000000e+000 -almanac[52]["SQRT_A"] = 5.050500e+003 -almanac[52]["Right_Ascen_at_Week"] = 1.79067e+000 -almanac[52]["Argument_of_Perigee"] = 2.09930067 -almanac[52]["Mean_Anom"] = -5.519651e+000 -almanac[52]["week"] = 1390 -almanac[52]["Commit_date"] = "N/A" -almanac[52]["Life_dates"] = {} - -SA_mode = false -AS_mode = false diff --git a/Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC/l10n/DEFAULT/Moose.lua b/Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC/l10n/DEFAULT/Moose.lua deleted file mode 100644 index 57fc377a6..000000000 --- a/Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC/l10n/DEFAULT/Moose.lua +++ /dev/null @@ -1,28929 +0,0 @@ -env.info( '*** MOOSE STATIC INCLUDE START *** ' ) -env.info( 'Moose Generation Timestamp: 20160721_1527' ) -local base = _G - -Include = {} -Include.Files = {} -Include.File = function( IncludeFile ) -end - ---- Various routines --- @module routines --- @author Flightcontrol - -env.setErrorMessageBoxEnabled(false) - ---- Extract of MIST functions. --- @author Grimes - -routines = {} - - --- don't change these -routines.majorVersion = 3 -routines.minorVersion = 3 -routines.build = 22 - ------------------------------------------------------------------------------------------------------------------ - ----------------------------------------------------------------------------------------------- --- Utils- conversion, Lua utils, etc. -routines.utils = {} - ---from http://lua-users.org/wiki/CopyTable -routines.utils.deepCopy = function(object) - local lookup_table = {} - local function _copy(object) - if type(object) ~= "table" then - return object - elseif lookup_table[object] then - return lookup_table[object] - end - local new_table = {} - lookup_table[object] = new_table - for index, value in pairs(object) do - new_table[_copy(index)] = _copy(value) - end - return setmetatable(new_table, getmetatable(object)) - end - local objectreturn = _copy(object) - return objectreturn -end - - --- porting in Slmod's serialize_slmod2 -routines.utils.oneLineSerialize = function(tbl) -- serialization of a table all on a single line, no comments, made to replace old get_table_string function - - lookup_table = {} - - local function _Serialize( tbl ) - - if type(tbl) == 'table' then --function only works for tables! - - if lookup_table[tbl] then - return lookup_table[object] - end - - local tbl_str = {} - - lookup_table[tbl] = tbl_str - - tbl_str[#tbl_str + 1] = '{' - - for ind,val in pairs(tbl) do -- serialize its fields - local ind_str = {} - if type(ind) == "number" then - ind_str[#ind_str + 1] = '[' - ind_str[#ind_str + 1] = tostring(ind) - ind_str[#ind_str + 1] = ']=' - else --must be a string - ind_str[#ind_str + 1] = '[' - ind_str[#ind_str + 1] = routines.utils.basicSerialize(ind) - ind_str[#ind_str + 1] = ']=' - end - - local val_str = {} - if ((type(val) == 'number') or (type(val) == 'boolean')) then - val_str[#val_str + 1] = tostring(val) - val_str[#val_str + 1] = ',' - tbl_str[#tbl_str + 1] = table.concat(ind_str) - tbl_str[#tbl_str + 1] = table.concat(val_str) - elseif type(val) == 'string' then - val_str[#val_str + 1] = routines.utils.basicSerialize(val) - val_str[#val_str + 1] = ',' - tbl_str[#tbl_str + 1] = table.concat(ind_str) - tbl_str[#tbl_str + 1] = table.concat(val_str) - elseif type(val) == 'nil' then -- won't ever happen, right? - val_str[#val_str + 1] = 'nil,' - tbl_str[#tbl_str + 1] = table.concat(ind_str) - tbl_str[#tbl_str + 1] = table.concat(val_str) - elseif type(val) == 'table' then - if ind == "__index" then - -- tbl_str[#tbl_str + 1] = "__index" - -- tbl_str[#tbl_str + 1] = ',' --I think this is right, I just added it - else - - val_str[#val_str + 1] = _Serialize(val) - val_str[#val_str + 1] = ',' --I think this is right, I just added it - tbl_str[#tbl_str + 1] = table.concat(ind_str) - tbl_str[#tbl_str + 1] = table.concat(val_str) - end - elseif type(val) == 'function' then - -- tbl_str[#tbl_str + 1] = "function " .. tostring(ind) - -- tbl_str[#tbl_str + 1] = ',' --I think this is right, I just added it - else --- env.info('unable to serialize value type ' .. routines.utils.basicSerialize(type(val)) .. ' at index ' .. tostring(ind)) --- env.info( debug.traceback() ) - end - - end - tbl_str[#tbl_str + 1] = '}' - return table.concat(tbl_str) - else - return tostring(tbl) - end - end - - local objectreturn = _Serialize(tbl) - return objectreturn -end - ---porting in Slmod's "safestring" basic serialize -routines.utils.basicSerialize = function(s) - if s == nil then - return "\"\"" - else - if ((type(s) == 'number') or (type(s) == 'boolean') or (type(s) == 'function') or (type(s) == 'table') or (type(s) == 'userdata') ) then - return tostring(s) - elseif type(s) == 'string' then - s = string.format('%q', s) - return s - end - end -end - - -routines.utils.toDegree = function(angle) - return angle*180/math.pi -end - -routines.utils.toRadian = function(angle) - return angle*math.pi/180 -end - -routines.utils.metersToNM = function(meters) - return meters/1852 -end - -routines.utils.metersToFeet = function(meters) - return meters/0.3048 -end - -routines.utils.NMToMeters = function(NM) - return NM*1852 -end - -routines.utils.feetToMeters = function(feet) - return feet*0.3048 -end - -routines.utils.mpsToKnots = function(mps) - return mps*3600/1852 -end - -routines.utils.mpsToKmph = function(mps) - return mps*3.6 -end - -routines.utils.knotsToMps = function(knots) - return knots*1852/3600 -end - -routines.utils.kmphToMps = function(kmph) - return kmph/3.6 -end - -function routines.utils.makeVec2(Vec3) - if Vec3.z then - return {x = Vec3.x, y = Vec3.z} - else - return {x = Vec3.x, y = Vec3.y} -- it was actually already vec2. - end -end - -function routines.utils.makeVec3(Vec2, y) - if not Vec2.z then - if not y then - y = 0 - end - return {x = Vec2.x, y = y, z = Vec2.y} - else - return {x = Vec2.x, y = Vec2.y, z = Vec2.z} -- it was already Vec3, actually. - end -end - -function routines.utils.makeVec3GL(Vec2, offset) - local adj = offset or 0 - - if not Vec2.z then - return {x = Vec2.x, y = (land.getHeight(Vec2) + adj), z = Vec2.y} - else - return {x = Vec2.x, y = (land.getHeight({x = Vec2.x, y = Vec2.z}) + adj), z = Vec2.z} - end -end - -routines.utils.zoneToVec3 = function(zone) - local new = {} - if type(zone) == 'table' and zone.point then - new.x = zone.point.x - new.y = zone.point.y - new.z = zone.point.z - return new - elseif type(zone) == 'string' then - zone = trigger.misc.getZone(zone) - if zone then - new.x = zone.point.x - new.y = zone.point.y - new.z = zone.point.z - return new - end - end -end - --- gets heading-error corrected direction from point along vector vec. -function routines.utils.getDir(vec, point) - local dir = math.atan2(vec.z, vec.x) - dir = dir + routines.getNorthCorrection(point) - if dir < 0 then - dir = dir + 2*math.pi -- put dir in range of 0 to 2*pi - end - return dir -end - --- gets distance in meters between two points (2 dimensional) -function routines.utils.get2DDist(point1, point2) - point1 = routines.utils.makeVec3(point1) - point2 = routines.utils.makeVec3(point2) - return routines.vec.mag({x = point1.x - point2.x, y = 0, z = point1.z - point2.z}) -end - --- gets distance in meters between two points (3 dimensional) -function routines.utils.get3DDist(point1, point2) - return routines.vec.mag({x = point1.x - point2.x, y = point1.y - point2.y, z = point1.z - point2.z}) -end - - - - - ---3D Vector manipulation -routines.vec = {} - -routines.vec.add = function(vec1, vec2) - return {x = vec1.x + vec2.x, y = vec1.y + vec2.y, z = vec1.z + vec2.z} -end - -routines.vec.sub = function(vec1, vec2) - return {x = vec1.x - vec2.x, y = vec1.y - vec2.y, z = vec1.z - vec2.z} -end - -routines.vec.scalarMult = function(vec, mult) - return {x = vec.x*mult, y = vec.y*mult, z = vec.z*mult} -end - -routines.vec.scalar_mult = routines.vec.scalarMult - -routines.vec.dp = function(vec1, vec2) - return vec1.x*vec2.x + vec1.y*vec2.y + vec1.z*vec2.z -end - -routines.vec.cp = function(vec1, vec2) - return { x = vec1.y*vec2.z - vec1.z*vec2.y, y = vec1.z*vec2.x - vec1.x*vec2.z, z = vec1.x*vec2.y - vec1.y*vec2.x} -end - -routines.vec.mag = function(vec) - return (vec.x^2 + vec.y^2 + vec.z^2)^0.5 -end - -routines.vec.getUnitVec = function(vec) - local mag = routines.vec.mag(vec) - return { x = vec.x/mag, y = vec.y/mag, z = vec.z/mag } -end - -routines.vec.rotateVec2 = function(vec2, theta) - return { x = vec2.x*math.cos(theta) - vec2.y*math.sin(theta), y = vec2.x*math.sin(theta) + vec2.y*math.cos(theta)} -end ---------------------------------------------------------------------------------------------------------------------------- - - - - --- acc- the accuracy of each easting/northing. 0, 1, 2, 3, 4, or 5. -routines.tostringMGRS = function(MGRS, acc) - if acc == 0 then - return MGRS.UTMZone .. ' ' .. MGRS.MGRSDigraph - else - return MGRS.UTMZone .. ' ' .. MGRS.MGRSDigraph .. ' ' .. string.format('%0' .. acc .. 'd', routines.utils.round(MGRS.Easting/(10^(5-acc)), 0)) - .. ' ' .. string.format('%0' .. acc .. 'd', routines.utils.round(MGRS.Northing/(10^(5-acc)), 0)) - end -end - ---[[acc: -in DM: decimal point of minutes. -In DMS: decimal point of seconds. -position after the decimal of the least significant digit: -So: -42.32 - acc of 2. -]] -routines.tostringLL = function(lat, lon, acc, DMS) - - local latHemi, lonHemi - if lat > 0 then - latHemi = 'N' - else - latHemi = 'S' - end - - if lon > 0 then - lonHemi = 'E' - else - lonHemi = 'W' - end - - lat = math.abs(lat) - lon = math.abs(lon) - - local latDeg = math.floor(lat) - local latMin = (lat - latDeg)*60 - - local lonDeg = math.floor(lon) - local lonMin = (lon - lonDeg)*60 - - if DMS then -- degrees, minutes, and seconds. - local oldLatMin = latMin - latMin = math.floor(latMin) - local latSec = routines.utils.round((oldLatMin - latMin)*60, acc) - - local oldLonMin = lonMin - lonMin = math.floor(lonMin) - local lonSec = routines.utils.round((oldLonMin - lonMin)*60, acc) - - if latSec == 60 then - latSec = 0 - latMin = latMin + 1 - end - - if lonSec == 60 then - lonSec = 0 - lonMin = lonMin + 1 - end - - local secFrmtStr -- create the formatting string for the seconds place - if acc <= 0 then -- no decimal place. - secFrmtStr = '%02d' - else - local width = 3 + acc -- 01.310 - that's a width of 6, for example. - secFrmtStr = '%0' .. width .. '.' .. acc .. 'f' - end - - return string.format('%02d', latDeg) .. ' ' .. string.format('%02d', latMin) .. '\' ' .. string.format(secFrmtStr, latSec) .. '"' .. latHemi .. ' ' - .. string.format('%02d', lonDeg) .. ' ' .. string.format('%02d', lonMin) .. '\' ' .. string.format(secFrmtStr, lonSec) .. '"' .. lonHemi - - else -- degrees, decimal minutes. - latMin = routines.utils.round(latMin, acc) - lonMin = routines.utils.round(lonMin, acc) - - if latMin == 60 then - latMin = 0 - latDeg = latDeg + 1 - end - - if lonMin == 60 then - lonMin = 0 - lonDeg = lonDeg + 1 - end - - local minFrmtStr -- create the formatting string for the minutes place - if acc <= 0 then -- no decimal place. - minFrmtStr = '%02d' - else - local width = 3 + acc -- 01.310 - that's a width of 6, for example. - minFrmtStr = '%0' .. width .. '.' .. acc .. 'f' - end - - return string.format('%02d', latDeg) .. ' ' .. string.format(minFrmtStr, latMin) .. '\'' .. latHemi .. ' ' - .. string.format('%02d', lonDeg) .. ' ' .. string.format(minFrmtStr, lonMin) .. '\'' .. lonHemi - - end -end - ---[[ required: az - radian - required: dist - meters - optional: alt - meters (set to false or nil if you don't want to use it). - optional: metric - set true to get dist and alt in km and m. - precision will always be nearest degree and NM or km.]] -routines.tostringBR = function(az, dist, alt, metric) - az = routines.utils.round(routines.utils.toDegree(az), 0) - - if metric then - dist = routines.utils.round(dist/1000, 2) - else - dist = routines.utils.round(routines.utils.metersToNM(dist), 2) - end - - local s = string.format('%03d', az) .. ' for ' .. dist - - if alt then - if metric then - s = s .. ' at ' .. routines.utils.round(alt, 0) - else - s = s .. ' at ' .. routines.utils.round(routines.utils.metersToFeet(alt), 0) - end - end - return s -end - -routines.getNorthCorrection = function(point) --gets the correction needed for true north - if not point.z then --Vec2; convert to Vec3 - point.z = point.y - point.y = 0 - end - local lat, lon = coord.LOtoLL(point) - local north_posit = coord.LLtoLO(lat + 1, lon) - return math.atan2(north_posit.z - point.z, north_posit.x - point.x) -end - - -do - local idNum = 0 - - --Simplified event handler - routines.addEventHandler = function(f) --id is optional! - local handler = {} - idNum = idNum + 1 - handler.id = idNum - handler.f = f - handler.onEvent = function(self, event) - self.f(event) - end - world.addEventHandler(handler) - end - - routines.removeEventHandler = function(id) - for key, handler in pairs(world.eventHandlers) do - if handler.id and handler.id == id then - world.eventHandlers[key] = nil - return true - end - end - return false - end -end - --- need to return a Vec3 or Vec2? -function routines.getRandPointInCircle(point, radius, innerRadius) - local theta = 2*math.pi*math.random() - local rad = math.random() + math.random() - if rad > 1 then - rad = 2 - rad - end - - local radMult - if innerRadius and innerRadius <= radius then - radMult = (radius - innerRadius)*rad + innerRadius - else - radMult = radius*rad - end - - if not point.z then --might as well work with vec2/3 - point.z = point.y - end - - local rndCoord - if radius > 0 then - rndCoord = {x = math.cos(theta)*radMult + point.x, y = math.sin(theta)*radMult + point.z} - else - rndCoord = {x = point.x, y = point.z} - end - return rndCoord -end - -routines.goRoute = function(group, path) - local misTask = { - id = 'Mission', - params = { - route = { - points = routines.utils.deepCopy(path), - }, - }, - } - if type(group) == 'string' then - group = Group.getByName(group) - end - local groupCon = group:getController() - if groupCon then - groupCon:setTask(misTask) - return true - end - - Controller.setTask(groupCon, misTask) - return false -end - - --- Useful atomic functions from mist, ported. - -routines.ground = {} -routines.fixedWing = {} -routines.heli = {} - -routines.ground.buildWP = function(point, overRideForm, overRideSpeed) - - local wp = {} - wp.x = point.x - - if point.z then - wp.y = point.z - else - wp.y = point.y - end - local form, speed - - if point.speed and not overRideSpeed then - wp.speed = point.speed - elseif type(overRideSpeed) == 'number' then - wp.speed = overRideSpeed - else - wp.speed = routines.utils.kmphToMps(20) - end - - if point.form and not overRideForm then - form = point.form - else - form = overRideForm - end - - if not form then - wp.action = 'Cone' - else - form = string.lower(form) - if form == 'off_road' or form == 'off road' then - wp.action = 'Off Road' - elseif form == 'on_road' or form == 'on road' then - wp.action = 'On Road' - elseif form == 'rank' or form == 'line_abrest' or form == 'line abrest' or form == 'lineabrest'then - wp.action = 'Rank' - elseif form == 'cone' then - wp.action = 'Cone' - elseif form == 'diamond' then - wp.action = 'Diamond' - elseif form == 'vee' then - wp.action = 'Vee' - elseif form == 'echelon_left' or form == 'echelon left' or form == 'echelonl' then - wp.action = 'EchelonL' - elseif form == 'echelon_right' or form == 'echelon right' or form == 'echelonr' then - wp.action = 'EchelonR' - else - wp.action = 'Cone' -- if nothing matched - end - end - - wp.type = 'Turning Point' - - return wp - -end - -routines.fixedWing.buildWP = function(point, WPtype, speed, alt, altType) - - local wp = {} - wp.x = point.x - - if point.z then - wp.y = point.z - else - wp.y = point.y - end - - if alt and type(alt) == 'number' then - wp.alt = alt - else - wp.alt = 2000 - end - - if altType then - altType = string.lower(altType) - if altType == 'radio' or 'agl' then - wp.alt_type = 'RADIO' - elseif altType == 'baro' or 'asl' then - wp.alt_type = 'BARO' - end - else - wp.alt_type = 'RADIO' - end - - if point.speed then - speed = point.speed - end - - if point.type then - WPtype = point.type - end - - if not speed then - wp.speed = routines.utils.kmphToMps(500) - else - wp.speed = speed - end - - if not WPtype then - wp.action = 'Turning Point' - else - WPtype = string.lower(WPtype) - if WPtype == 'flyover' or WPtype == 'fly over' or WPtype == 'fly_over' then - wp.action = 'Fly Over Point' - elseif WPtype == 'turningpoint' or WPtype == 'turning point' or WPtype == 'turning_point' then - wp.action = 'Turning Point' - else - wp.action = 'Turning Point' - end - end - - wp.type = 'Turning Point' - return wp -end - -routines.heli.buildWP = function(point, WPtype, speed, alt, altType) - - local wp = {} - wp.x = point.x - - if point.z then - wp.y = point.z - else - wp.y = point.y - end - - if alt and type(alt) == 'number' then - wp.alt = alt - else - wp.alt = 500 - end - - if altType then - altType = string.lower(altType) - if altType == 'radio' or 'agl' then - wp.alt_type = 'RADIO' - elseif altType == 'baro' or 'asl' then - wp.alt_type = 'BARO' - end - else - wp.alt_type = 'RADIO' - end - - if point.speed then - speed = point.speed - end - - if point.type then - WPtype = point.type - end - - if not speed then - wp.speed = routines.utils.kmphToMps(200) - else - wp.speed = speed - end - - if not WPtype then - wp.action = 'Turning Point' - else - WPtype = string.lower(WPtype) - if WPtype == 'flyover' or WPtype == 'fly over' or WPtype == 'fly_over' then - wp.action = 'Fly Over Point' - elseif WPtype == 'turningpoint' or WPtype == 'turning point' or WPtype == 'turning_point' then - wp.action = 'Turning Point' - else - wp.action = 'Turning Point' - end - end - - wp.type = 'Turning Point' - return wp -end - -routines.groupToRandomPoint = function(vars) - local group = vars.group --Required - local point = vars.point --required - local radius = vars.radius or 0 - local innerRadius = vars.innerRadius - local form = vars.form or 'Cone' - local heading = vars.heading or math.random()*2*math.pi - local headingDegrees = vars.headingDegrees - local speed = vars.speed or routines.utils.kmphToMps(20) - - - local useRoads - if not vars.disableRoads then - useRoads = true - else - useRoads = false - end - - local path = {} - - if headingDegrees then - heading = headingDegrees*math.pi/180 - end - - if heading >= 2*math.pi then - heading = heading - 2*math.pi - end - - local rndCoord = routines.getRandPointInCircle(point, radius, innerRadius) - - local offset = {} - local posStart = routines.getLeadPos(group) - - offset.x = routines.utils.round(math.sin(heading - (math.pi/2)) * 50 + rndCoord.x, 3) - offset.z = routines.utils.round(math.cos(heading + (math.pi/2)) * 50 + rndCoord.y, 3) - path[#path + 1] = routines.ground.buildWP(posStart, form, speed) - - - if useRoads == true and ((point.x - posStart.x)^2 + (point.z - posStart.z)^2)^0.5 > radius * 1.3 then - path[#path + 1] = routines.ground.buildWP({['x'] = posStart.x + 11, ['z'] = posStart.z + 11}, 'off_road', speed) - path[#path + 1] = routines.ground.buildWP(posStart, 'on_road', speed) - path[#path + 1] = routines.ground.buildWP(offset, 'on_road', speed) - else - path[#path + 1] = routines.ground.buildWP({['x'] = posStart.x + 25, ['z'] = posStart.z + 25}, form, speed) - end - - path[#path + 1] = routines.ground.buildWP(offset, form, speed) - path[#path + 1] = routines.ground.buildWP(rndCoord, form, speed) - - routines.goRoute(group, path) - - return -end - -routines.groupRandomDistSelf = function(gpData, dist, form, heading, speed) - local pos = routines.getLeadPos(gpData) - local fakeZone = {} - fakeZone.radius = dist or math.random(300, 1000) - fakeZone.point = {x = pos.x, y, pos.y, z = pos.z} - routines.groupToRandomZone(gpData, fakeZone, form, heading, speed) - - return -end - -routines.groupToRandomZone = function(gpData, zone, form, heading, speed) - if type(gpData) == 'string' then - gpData = Group.getByName(gpData) - end - - if type(zone) == 'string' then - zone = trigger.misc.getZone(zone) - elseif type(zone) == 'table' and not zone.radius then - zone = trigger.misc.getZone(zone[math.random(1, #zone)]) - end - - if speed then - speed = routines.utils.kmphToMps(speed) - end - - local vars = {} - vars.group = gpData - vars.radius = zone.radius - vars.form = form - vars.headingDegrees = heading - vars.speed = speed - vars.point = routines.utils.zoneToVec3(zone) - - routines.groupToRandomPoint(vars) - - return -end - -routines.isTerrainValid = function(coord, terrainTypes) -- vec2/3 and enum or table of acceptable terrain types - if coord.z then - coord.y = coord.z - end - local typeConverted = {} - - if type(terrainTypes) == 'string' then -- if its a string it does this check - for constId, constData in pairs(land.SurfaceType) do - if string.lower(constId) == string.lower(terrainTypes) or string.lower(constData) == string.lower(terrainTypes) then - table.insert(typeConverted, constId) - end - end - elseif type(terrainTypes) == 'table' then -- if its a table it does this check - for typeId, typeData in pairs(terrainTypes) do - for constId, constData in pairs(land.SurfaceType) do - if string.lower(constId) == string.lower(typeData) or string.lower(constData) == string.lower(typeId) then - table.insert(typeConverted, constId) - end - end - end - end - for validIndex, validData in pairs(typeConverted) do - if land.getSurfaceType(coord) == land.SurfaceType[validData] then - return true - end - end - return false -end - -routines.groupToPoint = function(gpData, point, form, heading, speed, useRoads) - if type(point) == 'string' then - point = trigger.misc.getZone(point) - end - if speed then - speed = routines.utils.kmphToMps(speed) - end - - local vars = {} - vars.group = gpData - vars.form = form - vars.headingDegrees = heading - vars.speed = speed - vars.disableRoads = useRoads - vars.point = routines.utils.zoneToVec3(point) - routines.groupToRandomPoint(vars) - - return -end - - -routines.getLeadPos = function(group) - if type(group) == 'string' then -- group name - group = Group.getByName(group) - end - - local units = group:getUnits() - - local leader = units[1] - if not leader then -- SHOULD be good, but if there is a bug, this code future-proofs it then. - local lowestInd = math.huge - for ind, unit in pairs(units) do - if ind < lowestInd then - lowestInd = ind - leader = unit - end - end - end - if leader and Unit.isExist(leader) then -- maybe a little too paranoid now... - return leader:getPosition().p - end -end - ---[[ vars for routines.getMGRSString: -vars.units - table of unit names (NOT unitNameTable- maybe this should change). -vars.acc - integer between 0 and 5, inclusive -]] -routines.getMGRSString = function(vars) - local units = vars.units - local acc = vars.acc or 5 - local avgPos = routines.getAvgPos(units) - if avgPos then - return routines.tostringMGRS(coord.LLtoMGRS(coord.LOtoLL(avgPos)), acc) - end -end - ---[[ vars for routines.getLLString -vars.units - table of unit names (NOT unitNameTable- maybe this should change). -vars.acc - integer, number of numbers after decimal place -vars.DMS - if true, output in degrees, minutes, seconds. Otherwise, output in degrees, minutes. - - -]] -routines.getLLString = function(vars) - local units = vars.units - local acc = vars.acc or 3 - local DMS = vars.DMS - local avgPos = routines.getAvgPos(units) - if avgPos then - local lat, lon = coord.LOtoLL(avgPos) - return routines.tostringLL(lat, lon, acc, DMS) - end -end - ---[[ -vars.zone - table of a zone name. -vars.ref - vec3 ref point, maybe overload for vec2 as well? -vars.alt - boolean, if used, includes altitude in string -vars.metric - boolean, gives distance in km instead of NM. -]] -routines.getBRStringZone = function(vars) - local zone = trigger.misc.getZone( vars.zone ) - local ref = routines.utils.makeVec3(vars.ref, 0) -- turn it into Vec3 if it is not already. - local alt = vars.alt - local metric = vars.metric - if zone then - local vec = {x = zone.point.x - ref.x, y = zone.point.y - ref.y, z = zone.point.z - ref.z} - local dir = routines.utils.getDir(vec, ref) - local dist = routines.utils.get2DDist(zone.point, ref) - if alt then - alt = zone.y - end - return routines.tostringBR(dir, dist, alt, metric) - else - env.info( 'routines.getBRStringZone: error: zone is nil' ) - end -end - ---[[ -vars.units- table of unit names (NOT unitNameTable- maybe this should change). -vars.ref - vec3 ref point, maybe overload for vec2 as well? -vars.alt - boolean, if used, includes altitude in string -vars.metric - boolean, gives distance in km instead of NM. -]] -routines.getBRString = function(vars) - local units = vars.units - local ref = routines.utils.makeVec3(vars.ref, 0) -- turn it into Vec3 if it is not already. - local alt = vars.alt - local metric = vars.metric - local avgPos = routines.getAvgPos(units) - if avgPos then - local vec = {x = avgPos.x - ref.x, y = avgPos.y - ref.y, z = avgPos.z - ref.z} - local dir = routines.utils.getDir(vec, ref) - local dist = routines.utils.get2DDist(avgPos, ref) - if alt then - alt = avgPos.y - end - return routines.tostringBR(dir, dist, alt, metric) - end -end - - --- Returns the Vec3 coordinates of the average position of the concentration of units most in the heading direction. ---[[ vars for routines.getLeadingPos: -vars.units - table of unit names -vars.heading - direction -vars.radius - number -vars.headingDegrees - boolean, switches heading to degrees -]] -routines.getLeadingPos = function(vars) - local units = vars.units - local heading = vars.heading - local radius = vars.radius - if vars.headingDegrees then - heading = routines.utils.toRadian(vars.headingDegrees) - end - - local unitPosTbl = {} - for i = 1, #units do - local unit = Unit.getByName(units[i]) - if unit and unit:isExist() then - unitPosTbl[#unitPosTbl + 1] = unit:getPosition().p - end - end - if #unitPosTbl > 0 then -- one more more units found. - -- first, find the unit most in the heading direction - local maxPos = -math.huge - - local maxPosInd -- maxPos - the furthest in direction defined by heading; maxPosInd = - for i = 1, #unitPosTbl do - local rotatedVec2 = routines.vec.rotateVec2(routines.utils.makeVec2(unitPosTbl[i]), heading) - if (not maxPos) or maxPos < rotatedVec2.x then - maxPos = rotatedVec2.x - maxPosInd = i - end - end - - --now, get all the units around this unit... - local avgPos - if radius then - local maxUnitPos = unitPosTbl[maxPosInd] - local avgx, avgy, avgz, totNum = 0, 0, 0, 0 - for i = 1, #unitPosTbl do - if routines.utils.get2DDist(maxUnitPos, unitPosTbl[i]) <= radius then - avgx = avgx + unitPosTbl[i].x - avgy = avgy + unitPosTbl[i].y - avgz = avgz + unitPosTbl[i].z - totNum = totNum + 1 - end - end - avgPos = { x = avgx/totNum, y = avgy/totNum, z = avgz/totNum} - else - avgPos = unitPosTbl[maxPosInd] - end - - return avgPos - end -end - - ---[[ vars for routines.getLeadingMGRSString: -vars.units - table of unit names -vars.heading - direction -vars.radius - number -vars.headingDegrees - boolean, switches heading to degrees -vars.acc - number, 0 to 5. -]] -routines.getLeadingMGRSString = function(vars) - local pos = routines.getLeadingPos(vars) - if pos then - local acc = vars.acc or 5 - return routines.tostringMGRS(coord.LLtoMGRS(coord.LOtoLL(pos)), acc) - end -end - ---[[ vars for routines.getLeadingLLString: -vars.units - table of unit names -vars.heading - direction, number -vars.radius - number -vars.headingDegrees - boolean, switches heading to degrees -vars.acc - number of digits after decimal point (can be negative) -vars.DMS - boolean, true if you want DMS. -]] -routines.getLeadingLLString = function(vars) - local pos = routines.getLeadingPos(vars) - if pos then - local acc = vars.acc or 3 - local DMS = vars.DMS - local lat, lon = coord.LOtoLL(pos) - return routines.tostringLL(lat, lon, acc, DMS) - end -end - - - ---[[ vars for routines.getLeadingBRString: -vars.units - table of unit names -vars.heading - direction, number -vars.radius - number -vars.headingDegrees - boolean, switches heading to degrees -vars.metric - boolean, if true, use km instead of NM. -vars.alt - boolean, if true, include altitude. -vars.ref - vec3/vec2 reference point. -]] -routines.getLeadingBRString = function(vars) - local pos = routines.getLeadingPos(vars) - if pos then - local ref = vars.ref - local alt = vars.alt - local metric = vars.metric - - local vec = {x = pos.x - ref.x, y = pos.y - ref.y, z = pos.z - ref.z} - local dir = routines.utils.getDir(vec, ref) - local dist = routines.utils.get2DDist(pos, ref) - if alt then - alt = pos.y - end - return routines.tostringBR(dir, dist, alt, metric) - end -end - ---[[ vars for routines.message.add - vars.text = 'Hello World' - vars.displayTime = 20 - vars.msgFor = {coa = {'red'}, countries = {'Ukraine', 'Georgia'}, unitTypes = {'A-10C'}} - -]] - ---[[ vars for routines.msgMGRS -vars.units - table of unit names (NOT unitNameTable- maybe this should change). -vars.acc - integer between 0 and 5, inclusive -vars.text - text in the message -vars.displayTime - self explanatory -vars.msgFor - scope -]] -routines.msgMGRS = function(vars) - local units = vars.units - local acc = vars.acc - local text = vars.text - local displayTime = vars.displayTime - local msgFor = vars.msgFor - - local s = routines.getMGRSString{units = units, acc = acc} - local newText - if string.find(text, '%%s') then -- look for %s - newText = string.format(text, s) -- insert the coordinates into the message - else -- else, just append to the end. - newText = text .. s - end - - routines.message.add{ - text = newText, - displayTime = displayTime, - msgFor = msgFor - } -end - ---[[ vars for routines.msgLL -vars.units - table of unit names (NOT unitNameTable- maybe this should change) (Yes). -vars.acc - integer, number of numbers after decimal place -vars.DMS - if true, output in degrees, minutes, seconds. Otherwise, output in degrees, minutes. -vars.text - text in the message -vars.displayTime - self explanatory -vars.msgFor - scope -]] -routines.msgLL = function(vars) - local units = vars.units -- technically, I don't really need to do this, but it helps readability. - local acc = vars.acc - local DMS = vars.DMS - local text = vars.text - local displayTime = vars.displayTime - local msgFor = vars.msgFor - - local s = routines.getLLString{units = units, acc = acc, DMS = DMS} - local newText - if string.find(text, '%%s') then -- look for %s - newText = string.format(text, s) -- insert the coordinates into the message - else -- else, just append to the end. - newText = text .. s - end - - routines.message.add{ - text = newText, - displayTime = displayTime, - msgFor = msgFor - } - -end - - ---[[ -vars.units- table of unit names (NOT unitNameTable- maybe this should change). -vars.ref - vec3 ref point, maybe overload for vec2 as well? -vars.alt - boolean, if used, includes altitude in string -vars.metric - boolean, gives distance in km instead of NM. -vars.text - text of the message -vars.displayTime -vars.msgFor - scope -]] -routines.msgBR = function(vars) - local units = vars.units -- technically, I don't really need to do this, but it helps readability. - local ref = vars.ref -- vec2/vec3 will be handled in routines.getBRString - local alt = vars.alt - local metric = vars.metric - local text = vars.text - local displayTime = vars.displayTime - local msgFor = vars.msgFor - - local s = routines.getBRString{units = units, ref = ref, alt = alt, metric = metric} - local newText - if string.find(text, '%%s') then -- look for %s - newText = string.format(text, s) -- insert the coordinates into the message - else -- else, just append to the end. - newText = text .. s - end - - routines.message.add{ - text = newText, - displayTime = displayTime, - msgFor = msgFor - } - -end - - --------------------------------------------------------------------------------------------- --- basically, just sub-types of routines.msgBR... saves folks the work of getting the ref point. ---[[ -vars.units- table of unit names (NOT unitNameTable- maybe this should change). -vars.ref - string red, blue -vars.alt - boolean, if used, includes altitude in string -vars.metric - boolean, gives distance in km instead of NM. -vars.text - text of the message -vars.displayTime -vars.msgFor - scope -]] -routines.msgBullseye = function(vars) - if string.lower(vars.ref) == 'red' then - vars.ref = routines.DBs.missionData.bullseye.red - routines.msgBR(vars) - elseif string.lower(vars.ref) == 'blue' then - vars.ref = routines.DBs.missionData.bullseye.blue - routines.msgBR(vars) - end -end - ---[[ -vars.units- table of unit names (NOT unitNameTable- maybe this should change). -vars.ref - unit name of reference point -vars.alt - boolean, if used, includes altitude in string -vars.metric - boolean, gives distance in km instead of NM. -vars.text - text of the message -vars.displayTime -vars.msgFor - scope -]] - -routines.msgBRA = function(vars) - if Unit.getByName(vars.ref) then - vars.ref = Unit.getByName(vars.ref):getPosition().p - if not vars.alt then - vars.alt = true - end - routines.msgBR(vars) - end -end --------------------------------------------------------------------------------------------- - ---[[ vars for routines.msgLeadingMGRS: -vars.units - table of unit names -vars.heading - direction -vars.radius - number -vars.headingDegrees - boolean, switches heading to degrees (optional) -vars.acc - number, 0 to 5. -vars.text - text of the message -vars.displayTime -vars.msgFor - scope -]] -routines.msgLeadingMGRS = function(vars) - local units = vars.units -- technically, I don't really need to do this, but it helps readability. - local heading = vars.heading - local radius = vars.radius - local headingDegrees = vars.headingDegrees - local acc = vars.acc - local text = vars.text - local displayTime = vars.displayTime - local msgFor = vars.msgFor - - local s = routines.getLeadingMGRSString{units = units, heading = heading, radius = radius, headingDegrees = headingDegrees, acc = acc} - local newText - if string.find(text, '%%s') then -- look for %s - newText = string.format(text, s) -- insert the coordinates into the message - else -- else, just append to the end. - newText = text .. s - end - - routines.message.add{ - text = newText, - displayTime = displayTime, - msgFor = msgFor - } - - -end ---[[ vars for routines.msgLeadingLL: -vars.units - table of unit names -vars.heading - direction, number -vars.radius - number -vars.headingDegrees - boolean, switches heading to degrees (optional) -vars.acc - number of digits after decimal point (can be negative) -vars.DMS - boolean, true if you want DMS. (optional) -vars.text - text of the message -vars.displayTime -vars.msgFor - scope -]] -routines.msgLeadingLL = function(vars) - local units = vars.units -- technically, I don't really need to do this, but it helps readability. - local heading = vars.heading - local radius = vars.radius - local headingDegrees = vars.headingDegrees - local acc = vars.acc - local DMS = vars.DMS - local text = vars.text - local displayTime = vars.displayTime - local msgFor = vars.msgFor - - local s = routines.getLeadingLLString{units = units, heading = heading, radius = radius, headingDegrees = headingDegrees, acc = acc, DMS = DMS} - local newText - if string.find(text, '%%s') then -- look for %s - newText = string.format(text, s) -- insert the coordinates into the message - else -- else, just append to the end. - newText = text .. s - end - - routines.message.add{ - text = newText, - displayTime = displayTime, - msgFor = msgFor - } - -end - ---[[ -vars.units - table of unit names -vars.heading - direction, number -vars.radius - number -vars.headingDegrees - boolean, switches heading to degrees (optional) -vars.metric - boolean, if true, use km instead of NM. (optional) -vars.alt - boolean, if true, include altitude. (optional) -vars.ref - vec3/vec2 reference point. -vars.text - text of the message -vars.displayTime -vars.msgFor - scope -]] -routines.msgLeadingBR = function(vars) - local units = vars.units -- technically, I don't really need to do this, but it helps readability. - local heading = vars.heading - local radius = vars.radius - local headingDegrees = vars.headingDegrees - local metric = vars.metric - local alt = vars.alt - local ref = vars.ref -- vec2/vec3 will be handled in routines.getBRString - local text = vars.text - local displayTime = vars.displayTime - local msgFor = vars.msgFor - - local s = routines.getLeadingBRString{units = units, heading = heading, radius = radius, headingDegrees = headingDegrees, metric = metric, alt = alt, ref = ref} - local newText - if string.find(text, '%%s') then -- look for %s - newText = string.format(text, s) -- insert the coordinates into the message - else -- else, just append to the end. - newText = text .. s - end - - routines.message.add{ - text = newText, - displayTime = displayTime, - msgFor = msgFor - } -end - - -function spairs(t, order) - -- collect the keys - local keys = {} - for k in pairs(t) do keys[#keys+1] = k end - - -- if order function given, sort by it by passing the table and keys a, b, - -- otherwise just sort the keys - if order then - table.sort(keys, function(a,b) return order(t, a, b) end) - else - table.sort(keys) - end - - -- return the iterator function - local i = 0 - return function() - i = i + 1 - if keys[i] then - return keys[i], t[keys[i]] - end - end -end - - -function routines.IsPartOfGroupInZones( CargoGroup, LandingZones ) ---trace.f() - - local CurrentZoneID = nil - - if CargoGroup then - local CargoUnits = CargoGroup:getUnits() - for CargoUnitID, CargoUnit in pairs( CargoUnits ) do - if CargoUnit and CargoUnit:getLife() >= 1.0 then - CurrentZoneID = routines.IsUnitInZones( CargoUnit, LandingZones ) - if CurrentZoneID then - break - end - end - end - end - ---trace.r( "", "", { CurrentZoneID } ) - return CurrentZoneID -end - - - -function routines.IsUnitInZones( TransportUnit, LandingZones ) ---trace.f("", "routines.IsUnitInZones" ) - - local TransportZoneResult = nil - local TransportZonePos = nil - local TransportZone = nil - - -- fill-up some local variables to support further calculations to determine location of units within the zone. - if TransportUnit then - local TransportUnitPos = TransportUnit:getPosition().p - if type( LandingZones ) == "table" then - for LandingZoneID, LandingZoneName in pairs( LandingZones ) do - TransportZone = trigger.misc.getZone( LandingZoneName ) - if TransportZone then - TransportZonePos = {radius = TransportZone.radius, x = TransportZone.point.x, y = TransportZone.point.y, z = TransportZone.point.z} - if ((( TransportUnitPos.x - TransportZonePos.x)^2 + (TransportUnitPos.z - TransportZonePos.z)^2)^0.5 <= TransportZonePos.radius) then - TransportZoneResult = LandingZoneID - break - end - end - end - else - TransportZone = trigger.misc.getZone( LandingZones ) - TransportZonePos = {radius = TransportZone.radius, x = TransportZone.point.x, y = TransportZone.point.y, z = TransportZone.point.z} - if ((( TransportUnitPos.x - TransportZonePos.x)^2 + (TransportUnitPos.z - TransportZonePos.z)^2)^0.5 <= TransportZonePos.radius) then - TransportZoneResult = 1 - end - end - if TransportZoneResult then - --trace.i( "routines", "TransportZone:" .. TransportZoneResult ) - else - --trace.i( "routines", "TransportZone:nil logic" ) - end - return TransportZoneResult - else - --trace.i( "routines", "TransportZone:nil hard" ) - return nil - end -end - -function routines.IsUnitNearZonesRadius( TransportUnit, LandingZones, ZoneRadius ) ---trace.f("", "routines.IsUnitInZones" ) - - local TransportZoneResult = nil - local TransportZonePos = nil - local TransportZone = nil - - -- fill-up some local variables to support further calculations to determine location of units within the zone. - if TransportUnit then - local TransportUnitPos = TransportUnit:getPosition().p - if type( LandingZones ) == "table" then - for LandingZoneID, LandingZoneName in pairs( LandingZones ) do - TransportZone = trigger.misc.getZone( LandingZoneName ) - if TransportZone then - TransportZonePos = {radius = TransportZone.radius, x = TransportZone.point.x, y = TransportZone.point.y, z = TransportZone.point.z} - if ((( TransportUnitPos.x - TransportZonePos.x)^2 + (TransportUnitPos.z - TransportZonePos.z)^2)^0.5 <= ZoneRadius ) then - TransportZoneResult = LandingZoneID - break - end - end - end - else - TransportZone = trigger.misc.getZone( LandingZones ) - TransportZonePos = {radius = TransportZone.radius, x = TransportZone.point.x, y = TransportZone.point.y, z = TransportZone.point.z} - if ((( TransportUnitPos.x - TransportZonePos.x)^2 + (TransportUnitPos.z - TransportZonePos.z)^2)^0.5 <= ZoneRadius ) then - TransportZoneResult = 1 - end - end - if TransportZoneResult then - --trace.i( "routines", "TransportZone:" .. TransportZoneResult ) - else - --trace.i( "routines", "TransportZone:nil logic" ) - end - return TransportZoneResult - else - --trace.i( "routines", "TransportZone:nil hard" ) - return nil - end -end - - -function routines.IsStaticInZones( TransportStatic, LandingZones ) ---trace.f() - - local TransportZoneResult = nil - local TransportZonePos = nil - local TransportZone = nil - - -- fill-up some local variables to support further calculations to determine location of units within the zone. - local TransportStaticPos = TransportStatic:getPosition().p - if type( LandingZones ) == "table" then - for LandingZoneID, LandingZoneName in pairs( LandingZones ) do - TransportZone = trigger.misc.getZone( LandingZoneName ) - if TransportZone then - TransportZonePos = {radius = TransportZone.radius, x = TransportZone.point.x, y = TransportZone.point.y, z = TransportZone.point.z} - if ((( TransportStaticPos.x - TransportZonePos.x)^2 + (TransportStaticPos.z - TransportZonePos.z)^2)^0.5 <= TransportZonePos.radius) then - TransportZoneResult = LandingZoneID - break - end - end - end - else - TransportZone = trigger.misc.getZone( LandingZones ) - TransportZonePos = {radius = TransportZone.radius, x = TransportZone.point.x, y = TransportZone.point.y, z = TransportZone.point.z} - if ((( TransportStaticPos.x - TransportZonePos.x)^2 + (TransportStaticPos.z - TransportZonePos.z)^2)^0.5 <= TransportZonePos.radius) then - TransportZoneResult = 1 - end - end - ---trace.r( "", "", { TransportZoneResult } ) - return TransportZoneResult -end - - -function routines.IsUnitInRadius( CargoUnit, ReferencePosition, Radius ) ---trace.f() - - local Valid = true - - -- fill-up some local variables to support further calculations to determine location of units within the zone. - local CargoPos = CargoUnit:getPosition().p - local ReferenceP = ReferencePosition.p - - if (((CargoPos.x - ReferenceP.x)^2 + (CargoPos.z - ReferenceP.z)^2)^0.5 <= Radius) then - else - Valid = false - end - - return Valid -end - -function routines.IsPartOfGroupInRadius( CargoGroup, ReferencePosition, Radius ) ---trace.f() - - local Valid = true - - Valid = routines.ValidateGroup( CargoGroup, "CargoGroup", Valid ) - - -- fill-up some local variables to support further calculations to determine location of units within the zone - local CargoUnits = CargoGroup:getUnits() - for CargoUnitId, CargoUnit in pairs( CargoUnits ) do - local CargoUnitPos = CargoUnit:getPosition().p --- env.info( 'routines.IsPartOfGroupInRadius: CargoUnitPos.x = ' .. CargoUnitPos.x .. ' CargoUnitPos.z = ' .. CargoUnitPos.z ) - local ReferenceP = ReferencePosition.p --- env.info( 'routines.IsPartOfGroupInRadius: ReferenceGroupPos.x = ' .. ReferenceGroupPos.x .. ' ReferenceGroupPos.z = ' .. ReferenceGroupPos.z ) - - if ((( CargoUnitPos.x - ReferenceP.x)^2 + (CargoUnitPos.z - ReferenceP.z)^2)^0.5 <= Radius) then - else - Valid = false - break - end - end - - return Valid -end - - -function routines.ValidateString( Variable, VariableName, Valid ) ---trace.f() - - if type( Variable ) == "string" then - if Variable == "" then - error( "routines.ValidateString: error: " .. VariableName .. " must be filled out!" ) - Valid = false - end - else - error( "routines.ValidateString: error: " .. VariableName .. " is not a string." ) - Valid = false - end - ---trace.r( "", "", { Valid } ) - return Valid -end - -function routines.ValidateNumber( Variable, VariableName, Valid ) ---trace.f() - - if type( Variable ) == "number" then - else - error( "routines.ValidateNumber: error: " .. VariableName .. " is not a number." ) - Valid = false - end - ---trace.r( "", "", { Valid } ) - return Valid - -end - -function routines.ValidateGroup( Variable, VariableName, Valid ) ---trace.f() - - if Variable == nil then - error( "routines.ValidateGroup: error: " .. VariableName .. " is a nil value!" ) - Valid = false - end - ---trace.r( "", "", { Valid } ) - return Valid -end - -function routines.ValidateZone( LandingZones, VariableName, Valid ) ---trace.f() - - if LandingZones == nil then - error( "routines.ValidateGroup: error: " .. VariableName .. " is a nil value!" ) - Valid = false - end - - if type( LandingZones ) == "table" then - for LandingZoneID, LandingZoneName in pairs( LandingZones ) do - if trigger.misc.getZone( LandingZoneName ) == nil then - error( "routines.ValidateGroup: error: Zone " .. LandingZoneName .. " does not exist!" ) - Valid = false - break - end - end - else - if trigger.misc.getZone( LandingZones ) == nil then - error( "routines.ValidateGroup: error: Zone " .. LandingZones .. " does not exist!" ) - Valid = false - end - end - ---trace.r( "", "", { Valid } ) - return Valid -end - -function routines.ValidateEnumeration( Variable, VariableName, Enum, Valid ) ---trace.f() - - local ValidVariable = false - - for EnumId, EnumData in pairs( Enum ) do - if Variable == EnumData then - ValidVariable = true - break - end - end - - if ValidVariable then - else - error( 'TransportValidateEnum: " .. VariableName .. " is not a valid type.' .. Variable ) - Valid = false - end - ---trace.r( "", "", { Valid } ) - return Valid -end - -function routines.getGroupRoute(groupIdent, task) -- same as getGroupPoints but returns speed and formation type along with vec2 of point} - -- refactor to search by groupId and allow groupId and groupName as inputs - local gpId = groupIdent - if type(groupIdent) == 'string' and not tonumber(groupIdent) then - gpId = _DATABASE.Templates.Groups[groupIdent].groupId - end - - for coa_name, coa_data in pairs(env.mission.coalition) do - if (coa_name == 'red' or coa_name == 'blue') and type(coa_data) == 'table' then - if coa_data.country then --there is a country table - for cntry_id, cntry_data in pairs(coa_data.country) do - for obj_type_name, obj_type_data in pairs(cntry_data) do - if obj_type_name == "helicopter" or obj_type_name == "ship" or obj_type_name == "plane" or obj_type_name == "vehicle" then -- only these types have points - if ((type(obj_type_data) == 'table') and obj_type_data.group and (type(obj_type_data.group) == 'table') and (#obj_type_data.group > 0)) then --there's a group! - for group_num, group_data in pairs(obj_type_data.group) do - if group_data and group_data.groupId == gpId then -- this is the group we are looking for - if group_data.route and group_data.route.points and #group_data.route.points > 0 then - local points = {} - - for point_num, point in pairs(group_data.route.points) do - local routeData = {} - if not point.point then - routeData.x = point.x - routeData.y = point.y - else - routeData.point = point.point --it's possible that the ME could move to the point = Vec2 notation. - end - routeData.form = point.action - routeData.speed = point.speed - routeData.alt = point.alt - routeData.alt_type = point.alt_type - routeData.airdromeId = point.airdromeId - routeData.helipadId = point.helipadId - routeData.type = point.type - routeData.action = point.action - if task then - routeData.task = point.task - end - points[point_num] = routeData - end - - return points - end - return - end --if group_data and group_data.name and group_data.name == 'groupname' - end --for group_num, group_data in pairs(obj_type_data.group) do - end --if ((type(obj_type_data) == 'table') and obj_type_data.group and (type(obj_type_data.group) == 'table') and (#obj_type_data.group > 0)) then - end --if obj_type_name == "helicopter" or obj_type_name == "ship" or obj_type_name == "plane" or obj_type_name == "vehicle" or obj_type_name == "static" then - end --for obj_type_name, obj_type_data in pairs(cntry_data) do - end --for cntry_id, cntry_data in pairs(coa_data.country) do - end --if coa_data.country then --there is a country table - end --if coa_name == 'red' or coa_name == 'blue' and type(coa_data) == 'table' then - end --for coa_name, coa_data in pairs(mission.coalition) do -end - -routines.ground.patrolRoute = function(vars) - - - local tempRoute = {} - local useRoute = {} - local gpData = vars.gpData - if type(gpData) == 'string' then - gpData = Group.getByName(gpData) - end - - local useGroupRoute - if not vars.useGroupRoute then - useGroupRoute = vars.gpData - else - useGroupRoute = vars.useGroupRoute - end - local routeProvided = false - if not vars.route then - if useGroupRoute then - tempRoute = routines.getGroupRoute(useGroupRoute) - end - else - useRoute = vars.route - local posStart = routines.getLeadPos(gpData) - useRoute[1] = routines.ground.buildWP(posStart, useRoute[1].action, useRoute[1].speed) - routeProvided = true - end - - - local overRideSpeed = vars.speed or 'default' - local pType = vars.pType - local offRoadForm = vars.offRoadForm or 'default' - local onRoadForm = vars.onRoadForm or 'default' - - if routeProvided == false and #tempRoute > 0 then - local posStart = routines.getLeadPos(gpData) - - - useRoute[#useRoute + 1] = routines.ground.buildWP(posStart, offRoadForm, overRideSpeed) - for i = 1, #tempRoute do - local tempForm = tempRoute[i].action - local tempSpeed = tempRoute[i].speed - - if offRoadForm == 'default' then - tempForm = tempRoute[i].action - end - if onRoadForm == 'default' then - onRoadForm = 'On Road' - end - if (string.lower(tempRoute[i].action) == 'on road' or string.lower(tempRoute[i].action) == 'onroad' or string.lower(tempRoute[i].action) == 'on_road') then - tempForm = onRoadForm - else - tempForm = offRoadForm - end - - if type(overRideSpeed) == 'number' then - tempSpeed = overRideSpeed - end - - - useRoute[#useRoute + 1] = routines.ground.buildWP(tempRoute[i], tempForm, tempSpeed) - end - - if pType and string.lower(pType) == 'doubleback' then - local curRoute = routines.utils.deepCopy(useRoute) - for i = #curRoute, 2, -1 do - useRoute[#useRoute + 1] = routines.ground.buildWP(curRoute[i], curRoute[i].action, curRoute[i].speed) - end - end - - useRoute[1].action = useRoute[#useRoute].action -- make it so the first WP matches the last WP - end - - local cTask3 = {} - local newPatrol = {} - newPatrol.route = useRoute - newPatrol.gpData = gpData:getName() - cTask3[#cTask3 + 1] = 'routines.ground.patrolRoute(' - cTask3[#cTask3 + 1] = routines.utils.oneLineSerialize(newPatrol) - cTask3[#cTask3 + 1] = ')' - cTask3 = table.concat(cTask3) - local tempTask = { - id = 'WrappedAction', - params = { - action = { - id = 'Script', - params = { - command = cTask3, - - }, - }, - }, - } - - - useRoute[#useRoute].task = tempTask - routines.goRoute(gpData, useRoute) - - return -end - -routines.ground.patrol = function(gpData, pType, form, speed) - local vars = {} - - if type(gpData) == 'table' and gpData:getName() then - gpData = gpData:getName() - end - - vars.useGroupRoute = gpData - vars.gpData = gpData - vars.pType = pType - vars.offRoadForm = form - vars.speed = speed - - routines.ground.patrolRoute(vars) - - return -end - -function routines.GetUnitHeight( CheckUnit ) ---trace.f( "routines" ) - - local UnitPoint = CheckUnit:getPoint() - local UnitPosition = { x = UnitPoint.x, y = UnitPoint.z } - local UnitHeight = UnitPoint.y - - local LandHeight = land.getHeight( UnitPosition ) - - --env.info(( 'CarrierHeight: LandHeight = ' .. LandHeight .. ' CarrierHeight = ' .. CarrierHeight )) - - --trace.f( "routines", "Unit Height = " .. UnitHeight - LandHeight ) - - return UnitHeight - LandHeight - -end - - - -Su34Status = { status = {} } -boardMsgRed = { statusMsg = "" } -boardMsgAll = { timeMsg = "" } -SpawnSettings = {} -Su34MenuPath = {} -Su34Menus = 0 - - -function Su34AttackCarlVinson(groupName) ---trace.menu("", "Su34AttackCarlVinson") - local groupSu34 = Group.getByName( groupName ) - local controllerSu34 = groupSu34.getController(groupSu34) - local groupCarlVinson = Group.getByName("US Carl Vinson #001") - controllerSu34.setOption( controllerSu34, AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.OPEN_FIRE ) - controllerSu34.setOption( controllerSu34, AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.EVADE_FIRE ) - if groupCarlVinson ~= nil then - controllerSu34.pushTask(controllerSu34,{id = 'AttackGroup', params = { groupId = groupCarlVinson:getID(), expend = AI.Task.WeaponExpend.ALL, attackQtyLimit = true}}) - end - Su34Status.status[groupName] = 1 - MessageToRed( string.format('%s: ',groupName) .. 'Attacking carrier Carl Vinson. ', 10, 'RedStatus' .. groupName ) -end - -function Su34AttackWest(groupName) ---trace.f("","Su34AttackWest") - local groupSu34 = Group.getByName( groupName ) - local controllerSu34 = groupSu34.getController(groupSu34) - local groupShipWest1 = Group.getByName("US Ship West #001") - local groupShipWest2 = Group.getByName("US Ship West #002") - controllerSu34.setOption( controllerSu34, AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.OPEN_FIRE ) - controllerSu34.setOption( controllerSu34, AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.EVADE_FIRE ) - if groupShipWest1 ~= nil then - controllerSu34.pushTask(controllerSu34,{id = 'AttackGroup', params = { groupId = groupShipWest1:getID(), expend = AI.Task.WeaponExpend.ALL, attackQtyLimit = true}}) - end - if groupShipWest2 ~= nil then - controllerSu34.pushTask(controllerSu34,{id = 'AttackGroup', params = { groupId = groupShipWest2:getID(), expend = AI.Task.WeaponExpend.ALL, attackQtyLimit = true}}) - end - Su34Status.status[groupName] = 2 - MessageToRed( string.format('%s: ',groupName) .. 'Attacking invading ships in the west. ', 10, 'RedStatus' .. groupName ) -end - -function Su34AttackNorth(groupName) ---trace.menu("","Su34AttackNorth") - local groupSu34 = Group.getByName( groupName ) - local controllerSu34 = groupSu34.getController(groupSu34) - local groupShipNorth1 = Group.getByName("US Ship North #001") - local groupShipNorth2 = Group.getByName("US Ship North #002") - local groupShipNorth3 = Group.getByName("US Ship North #003") - controllerSu34.setOption( controllerSu34, AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.OPEN_FIRE ) - controllerSu34.setOption( controllerSu34, AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.EVADE_FIRE ) - if groupShipNorth1 ~= nil then - controllerSu34.pushTask(controllerSu34,{id = 'AttackGroup', params = { groupId = groupShipNorth1:getID(), expend = AI.Task.WeaponExpend.ALL, attackQtyLimit = false}}) - end - if groupShipNorth2 ~= nil then - controllerSu34.pushTask(controllerSu34,{id = 'AttackGroup', params = { groupId = groupShipNorth2:getID(), expend = AI.Task.WeaponExpend.ALL, attackQtyLimit = false}}) - end - if groupShipNorth3 ~= nil then - controllerSu34.pushTask(controllerSu34,{id = 'AttackGroup', params = { groupId = groupShipNorth3:getID(), expend = AI.Task.WeaponExpend.ALL, attackQtyLimit = false}}) - end - Su34Status.status[groupName] = 3 - MessageToRed( string.format('%s: ',groupName) .. 'Attacking invading ships in the north. ', 10, 'RedStatus' .. groupName ) -end - -function Su34Orbit(groupName) ---trace.menu("","Su34Orbit") - local groupSu34 = Group.getByName( groupName ) - local controllerSu34 = groupSu34:getController() - controllerSu34.setOption( controllerSu34, AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.WEAPON_HOLD ) - controllerSu34.setOption( controllerSu34, AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.EVADE_FIRE ) - controllerSu34:pushTask( {id = 'ControlledTask', params = { task = { id = 'Orbit', params = { pattern = AI.Task.OrbitPattern.RACE_TRACK } }, stopCondition = { duration = 600 } } } ) - Su34Status.status[groupName] = 4 - MessageToRed( string.format('%s: ',groupName) .. 'In orbit and awaiting further instructions. ', 10, 'RedStatus' .. groupName ) -end - -function Su34TakeOff(groupName) ---trace.menu("","Su34TakeOff") - local groupSu34 = Group.getByName( groupName ) - local controllerSu34 = groupSu34:getController() - controllerSu34.setOption( controllerSu34, AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.WEAPON_HOLD ) - controllerSu34.setOption( controllerSu34, AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.BYPASS_AND_ESCAPE ) - Su34Status.status[groupName] = 8 - MessageToRed( string.format('%s: ',groupName) .. 'Take-Off. ', 10, 'RedStatus' .. groupName ) -end - -function Su34Hold(groupName) ---trace.menu("","Su34Hold") - local groupSu34 = Group.getByName( groupName ) - local controllerSu34 = groupSu34:getController() - controllerSu34.setOption( controllerSu34, AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.WEAPON_HOLD ) - controllerSu34.setOption( controllerSu34, AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.BYPASS_AND_ESCAPE ) - Su34Status.status[groupName] = 5 - MessageToRed( string.format('%s: ',groupName) .. 'Holding Weapons. ', 10, 'RedStatus' .. groupName ) -end - -function Su34RTB(groupName) ---trace.menu("","Su34RTB") - Su34Status.status[groupName] = 6 - MessageToRed( string.format('%s: ',groupName) .. 'Return to Krasnodar. ', 10, 'RedStatus' .. groupName ) -end - -function Su34Destroyed(groupName) ---trace.menu("","Su34Destroyed") - Su34Status.status[groupName] = 7 - MessageToRed( string.format('%s: ',groupName) .. 'Destroyed. ', 30, 'RedStatus' .. groupName ) -end - -function GroupAlive( groupName ) ---trace.menu("","GroupAlive") - local groupTest = Group.getByName( groupName ) - - local groupExists = false - - if groupTest then - groupExists = groupTest:isExist() - end - - --trace.r( "", "", { groupExists } ) - return groupExists -end - -function Su34IsDead() ---trace.f() - -end - -function Su34OverviewStatus() ---trace.menu("","Su34OverviewStatus") - local msg = "" - local currentStatus = 0 - local Exists = false - - for groupName, currentStatus in pairs(Su34Status.status) do - - env.info(('Su34 Overview Status: GroupName = ' .. groupName )) - Alive = GroupAlive( groupName ) - - if Alive then - if currentStatus == 1 then - msg = msg .. string.format("%s: ",groupName) - msg = msg .. "Attacking carrier Carl Vinson. " - elseif currentStatus == 2 then - msg = msg .. string.format("%s: ",groupName) - msg = msg .. "Attacking supporting ships in the west. " - elseif currentStatus == 3 then - msg = msg .. string.format("%s: ",groupName) - msg = msg .. "Attacking invading ships in the north. " - elseif currentStatus == 4 then - msg = msg .. string.format("%s: ",groupName) - msg = msg .. "In orbit and awaiting further instructions. " - elseif currentStatus == 5 then - msg = msg .. string.format("%s: ",groupName) - msg = msg .. "Holding Weapons. " - elseif currentStatus == 6 then - msg = msg .. string.format("%s: ",groupName) - msg = msg .. "Return to Krasnodar. " - elseif currentStatus == 7 then - msg = msg .. string.format("%s: ",groupName) - msg = msg .. "Destroyed. " - elseif currentStatus == 8 then - msg = msg .. string.format("%s: ",groupName) - msg = msg .. "Take-Off. " - end - else - if currentStatus == 7 then - msg = msg .. string.format("%s: ",groupName) - msg = msg .. "Destroyed. " - else - Su34Destroyed(groupName) - end - end - end - - boardMsgRed.statusMsg = msg -end - - -function UpdateBoardMsg() ---trace.f() - Su34OverviewStatus() - MessageToRed( boardMsgRed.statusMsg, 15, 'RedStatus' ) -end - -function MusicReset( flg ) ---trace.f() - trigger.action.setUserFlag(95,flg) -end - -function PlaneActivate(groupNameFormat, flg) ---trace.f() - local groupName = groupNameFormat .. string.format("#%03d", trigger.misc.getUserFlag(flg)) - --trigger.action.outText(groupName,10) - trigger.action.activateGroup(Group.getByName(groupName)) -end - -function Su34Menu(groupName) ---trace.f() - - --env.info(( 'Su34Menu(' .. groupName .. ')' )) - local groupSu34 = Group.getByName( groupName ) - - if Su34Status.status[groupName] == 1 or - Su34Status.status[groupName] == 2 or - Su34Status.status[groupName] == 3 or - Su34Status.status[groupName] == 4 or - Su34Status.status[groupName] == 5 then - if Su34MenuPath[groupName] == nil then - if planeMenuPath == nil then - planeMenuPath = missionCommands.addSubMenuForCoalition( - coalition.side.RED, - "SU-34 anti-ship flights", - nil - ) - end - Su34MenuPath[groupName] = missionCommands.addSubMenuForCoalition( - coalition.side.RED, - "Flight " .. groupName, - planeMenuPath - ) - - missionCommands.addCommandForCoalition( - coalition.side.RED, - "Attack carrier Carl Vinson", - Su34MenuPath[groupName], - Su34AttackCarlVinson, - groupName - ) - - missionCommands.addCommandForCoalition( - coalition.side.RED, - "Attack ships in the west", - Su34MenuPath[groupName], - Su34AttackWest, - groupName - ) - - missionCommands.addCommandForCoalition( - coalition.side.RED, - "Attack ships in the north", - Su34MenuPath[groupName], - Su34AttackNorth, - groupName - ) - - missionCommands.addCommandForCoalition( - coalition.side.RED, - "Hold position and await instructions", - Su34MenuPath[groupName], - Su34Orbit, - groupName - ) - - missionCommands.addCommandForCoalition( - coalition.side.RED, - "Report status", - Su34MenuPath[groupName], - Su34OverviewStatus - ) - end - else - if Su34MenuPath[groupName] then - missionCommands.removeItemForCoalition(coalition.side.RED, Su34MenuPath[groupName]) - end - end -end - ---- Obsolete function, but kept to rework in framework. - -function ChooseInfantry ( TeleportPrefixTable, TeleportMax ) ---trace.f("Spawn") - --env.info(( 'ChooseInfantry: ' )) - - TeleportPrefixTableCount = #TeleportPrefixTable - TeleportPrefixTableIndex = math.random( 1, TeleportPrefixTableCount ) - - --env.info(( 'ChooseInfantry: TeleportPrefixTableIndex = ' .. TeleportPrefixTableIndex .. ' TeleportPrefixTableCount = ' .. TeleportPrefixTableCount .. ' TeleportMax = ' .. TeleportMax )) - - local TeleportFound = false - local TeleportLoop = true - local Index = TeleportPrefixTableIndex - local TeleportPrefix = '' - - while TeleportLoop do - TeleportPrefix = TeleportPrefixTable[Index] - if SpawnSettings[TeleportPrefix] then - if SpawnSettings[TeleportPrefix]['SpawnCount'] - 1 < TeleportMax then - SpawnSettings[TeleportPrefix]['SpawnCount'] = SpawnSettings[TeleportPrefix]['SpawnCount'] + 1 - TeleportFound = true - else - TeleportFound = false - end - else - SpawnSettings[TeleportPrefix] = {} - SpawnSettings[TeleportPrefix]['SpawnCount'] = 0 - TeleportFound = true - end - if TeleportFound then - TeleportLoop = false - else - if Index < TeleportPrefixTableCount then - Index = Index + 1 - else - TeleportLoop = false - end - end - --env.info(( 'ChooseInfantry: Loop 1 - TeleportPrefix = ' .. TeleportPrefix .. ' Index = ' .. Index )) - end - - if TeleportFound == false then - TeleportLoop = true - Index = 1 - while TeleportLoop do - TeleportPrefix = TeleportPrefixTable[Index] - if SpawnSettings[TeleportPrefix] then - if SpawnSettings[TeleportPrefix]['SpawnCount'] - 1 < TeleportMax then - SpawnSettings[TeleportPrefix]['SpawnCount'] = SpawnSettings[TeleportPrefix]['SpawnCount'] + 1 - TeleportFound = true - else - TeleportFound = false - end - else - SpawnSettings[TeleportPrefix] = {} - SpawnSettings[TeleportPrefix]['SpawnCount'] = 0 - TeleportFound = true - end - if TeleportFound then - TeleportLoop = false - else - if Index < TeleportPrefixTableIndex then - Index = Index + 1 - else - TeleportLoop = false - end - end - --env.info(( 'ChooseInfantry: Loop 2 - TeleportPrefix = ' .. TeleportPrefix .. ' Index = ' .. Index )) - end - end - - local TeleportGroupName = '' - if TeleportFound == true then - TeleportGroupName = TeleportPrefix .. string.format("#%03d", SpawnSettings[TeleportPrefix]['SpawnCount'] ) - else - TeleportGroupName = '' - end - - --env.info(('ChooseInfantry: TeleportGroupName = ' .. TeleportGroupName )) - --env.info(('ChooseInfantry: return')) - - return TeleportGroupName -end - -SpawnedInfantry = 0 - -function LandCarrier ( CarrierGroup, LandingZonePrefix ) ---trace.f() - --env.info(( 'LandCarrier: ' )) - --env.info(( 'LandCarrier: CarrierGroup = ' .. CarrierGroup:getName() )) - --env.info(( 'LandCarrier: LandingZone = ' .. LandingZonePrefix )) - - local controllerGroup = CarrierGroup:getController() - - local LandingZone = trigger.misc.getZone(LandingZonePrefix) - local LandingZonePos = {} - LandingZonePos.x = LandingZone.point.x + math.random(LandingZone.radius * -1, LandingZone.radius) - LandingZonePos.y = LandingZone.point.z + math.random(LandingZone.radius * -1, LandingZone.radius) - - controllerGroup:pushTask( { id = 'Land', params = { point = LandingZonePos, durationFlag = true, duration = 10 } } ) - - --env.info(( 'LandCarrier: end' )) -end - -EscortCount = 0 -function EscortCarrier ( CarrierGroup, EscortPrefix, EscortLastWayPoint, EscortEngagementDistanceMax, EscortTargetTypes ) ---trace.f() - --env.info(( 'EscortCarrier: ' )) - --env.info(( 'EscortCarrier: CarrierGroup = ' .. CarrierGroup:getName() )) - --env.info(( 'EscortCarrier: EscortPrefix = ' .. EscortPrefix )) - - local CarrierName = CarrierGroup:getName() - - local EscortMission = {} - local CarrierMission = {} - - local EscortMission = SpawnMissionGroup( EscortPrefix ) - local CarrierMission = SpawnMissionGroup( CarrierGroup:getName() ) - - if EscortMission ~= nil and CarrierMission ~= nil then - - EscortCount = EscortCount + 1 - EscortMissionName = string.format( EscortPrefix .. '#Escort %s', CarrierName ) - EscortMission.name = EscortMissionName - EscortMission.groupId = nil - EscortMission.lateActivation = false - EscortMission.taskSelected = false - - local EscortUnits = #EscortMission.units - for u = 1, EscortUnits do - EscortMission.units[u].name = string.format( EscortPrefix .. '#Escort %s %02d', CarrierName, u ) - EscortMission.units[u].unitId = nil - end - - - EscortMission.route.points[1].task = { id = "ComboTask", - params = - { - tasks = - { - [1] = - { - enabled = true, - auto = false, - id = "Escort", - number = 1, - params = - { - lastWptIndexFlagChangedManually = false, - groupId = CarrierGroup:getID(), - lastWptIndex = nil, - lastWptIndexFlag = false, - engagementDistMax = EscortEngagementDistanceMax, - targetTypes = EscortTargetTypes, - pos = - { - y = 20, - x = 20, - z = 0, - } -- end of ["pos"] - } -- end of ["params"] - } -- end of [1] - } -- end of ["tasks"] - } -- end of ["params"] - } -- end of ["task"] - - SpawnGroupAdd( EscortPrefix, EscortMission ) - - end -end - -function SendMessageToCarrier( CarrierGroup, CarrierMessage ) ---trace.f() - - if CarrierGroup ~= nil then - MessageToGroup( CarrierGroup, CarrierMessage, 30, 'Carrier/' .. CarrierGroup:getName() ) - end - -end - -function MessageToGroup( MsgGroup, MsgText, MsgTime, MsgName ) ---trace.f() - - if type(MsgGroup) == 'string' then - --env.info( 'MessageToGroup: Converted MsgGroup string "' .. MsgGroup .. '" into a Group structure.' ) - MsgGroup = Group.getByName( MsgGroup ) - end - - if MsgGroup ~= nil then - local MsgTable = {} - MsgTable.text = MsgText - MsgTable.displayTime = MsgTime - MsgTable.msgFor = { units = { MsgGroup:getUnits()[1]:getName() } } - MsgTable.name = MsgName - --routines.message.add( MsgTable ) - --env.info(('MessageToGroup: Message sent to ' .. MsgGroup:getUnits()[1]:getName() .. ' -> ' .. MsgText )) - end -end - -function MessageToUnit( UnitName, MsgText, MsgTime, MsgName ) ---trace.f() - - if UnitName ~= nil then - local MsgTable = {} - MsgTable.text = MsgText - MsgTable.displayTime = MsgTime - MsgTable.msgFor = { units = { UnitName } } - MsgTable.name = MsgName - --routines.message.add( MsgTable ) - end -end - -function MessageToAll( MsgText, MsgTime, MsgName ) ---trace.f() - - MESSAGE:New( MsgText, MsgTime, "Message" ):ToCoalition( coalition.side.RED ):ToCoalition( coalition.side.BLUE ) -end - -function MessageToRed( MsgText, MsgTime, MsgName ) ---trace.f() - - MESSAGE:New( MsgText, MsgTime, "To Red Coalition" ):ToCoalition( coalition.side.RED ) -end - -function MessageToBlue( MsgText, MsgTime, MsgName ) ---trace.f() - - MESSAGE:New( MsgText, MsgTime, "To Blue Coalition" ):ToCoalition( coalition.side.RED ) -end - -function getCarrierHeight( CarrierGroup ) ---trace.f() - - if CarrierGroup ~= nil then - if table.getn(CarrierGroup:getUnits()) == 1 then - local CarrierUnit = CarrierGroup:getUnits()[1] - local CurrentPoint = CarrierUnit:getPoint() - - local CurrentPosition = { x = CurrentPoint.x, y = CurrentPoint.z } - local CarrierHeight = CurrentPoint.y - - local LandHeight = land.getHeight( CurrentPosition ) - - --env.info(( 'CarrierHeight: LandHeight = ' .. LandHeight .. ' CarrierHeight = ' .. CarrierHeight )) - - return CarrierHeight - LandHeight - else - return 999999 - end - else - return 999999 - end - -end - -function GetUnitHeight( CheckUnit ) ---trace.f() - - local UnitPoint = CheckUnit:getPoint() - local UnitPosition = { x = CurrentPoint.x, y = CurrentPoint.z } - local UnitHeight = CurrentPoint.y - - local LandHeight = land.getHeight( CurrentPosition ) - - --env.info(( 'CarrierHeight: LandHeight = ' .. LandHeight .. ' CarrierHeight = ' .. CarrierHeight )) - - return UnitHeight - LandHeight - -end - - -_MusicTable = {} -_MusicTable.Files = {} -_MusicTable.Queue = {} -_MusicTable.FileCnt = 0 - - -function MusicRegister( SndRef, SndFile, SndTime ) ---trace.f() - - env.info(( 'MusicRegister: SndRef = ' .. SndRef )) - env.info(( 'MusicRegister: SndFile = ' .. SndFile )) - env.info(( 'MusicRegister: SndTime = ' .. SndTime )) - - - _MusicTable.FileCnt = _MusicTable.FileCnt + 1 - - _MusicTable.Files[_MusicTable.FileCnt] = {} - _MusicTable.Files[_MusicTable.FileCnt].Ref = SndRef - _MusicTable.Files[_MusicTable.FileCnt].File = SndFile - _MusicTable.Files[_MusicTable.FileCnt].Time = SndTime - - if not _MusicTable.Function then - _MusicTable.Function = routines.scheduleFunction( MusicScheduler, { }, timer.getTime() + 10, 10) - end - -end - -function MusicToPlayer( SndRef, PlayerName, SndContinue ) ---trace.f() - - --env.info(( 'MusicToPlayer: SndRef = ' .. SndRef )) - - local PlayerUnits = AlivePlayerUnits() - for PlayerUnitIdx, PlayerUnit in pairs(PlayerUnits) do - local PlayerUnitName = PlayerUnit:getPlayerName() - --env.info(( 'MusicToPlayer: PlayerUnitName = ' .. PlayerUnitName )) - if PlayerName == PlayerUnitName then - PlayerGroup = PlayerUnit:getGroup() - if PlayerGroup then - --env.info(( 'MusicToPlayer: PlayerGroup = ' .. PlayerGroup:getName() )) - MusicToGroup( SndRef, PlayerGroup, SndContinue ) - end - break - end - end - - --env.info(( 'MusicToPlayer: end' )) - -end - -function MusicToGroup( SndRef, SndGroup, SndContinue ) ---trace.f() - - --env.info(( 'MusicToGroup: SndRef = ' .. SndRef )) - - if SndGroup ~= nil then - if _MusicTable and _MusicTable.FileCnt > 0 then - if SndGroup:isExist() then - if MusicCanStart(SndGroup:getUnit(1):getPlayerName()) then - --env.info(( 'MusicToGroup: OK for Sound.' )) - local SndIdx = 0 - if SndRef == '' then - --env.info(( 'MusicToGroup: SndRef as empty. Queueing at random.' )) - SndIdx = math.random( 1, _MusicTable.FileCnt ) - else - for SndIdx = 1, _MusicTable.FileCnt do - if _MusicTable.Files[SndIdx].Ref == SndRef then - break - end - end - end - --env.info(( 'MusicToGroup: SndIdx = ' .. SndIdx )) - --env.info(( 'MusicToGroup: Queueing Music ' .. _MusicTable.Files[SndIdx].File .. ' for Group ' .. SndGroup:getID() )) - trigger.action.outSoundForGroup( SndGroup:getID(), _MusicTable.Files[SndIdx].File ) - MessageToGroup( SndGroup, 'Playing ' .. _MusicTable.Files[SndIdx].File, 15, 'Music-' .. SndGroup:getUnit(1):getPlayerName() ) - - local SndQueueRef = SndGroup:getUnit(1):getPlayerName() - if _MusicTable.Queue[SndQueueRef] == nil then - _MusicTable.Queue[SndQueueRef] = {} - end - _MusicTable.Queue[SndQueueRef].Start = timer.getTime() - _MusicTable.Queue[SndQueueRef].PlayerName = SndGroup:getUnit(1):getPlayerName() - _MusicTable.Queue[SndQueueRef].Group = SndGroup - _MusicTable.Queue[SndQueueRef].ID = SndGroup:getID() - _MusicTable.Queue[SndQueueRef].Ref = SndIdx - _MusicTable.Queue[SndQueueRef].Continue = SndContinue - _MusicTable.Queue[SndQueueRef].Type = Group - end - end - end - end -end - -function MusicCanStart(PlayerName) ---trace.f() - - --env.info(( 'MusicCanStart:' )) - - local MusicOut = false - - if _MusicTable['Queue'] ~= nil and _MusicTable.FileCnt > 0 then - --env.info(( 'MusicCanStart: PlayerName = ' .. PlayerName )) - local PlayerFound = false - local MusicStart = 0 - local MusicTime = 0 - for SndQueueIdx, SndQueue in pairs( _MusicTable.Queue ) do - if SndQueue.PlayerName == PlayerName then - PlayerFound = true - MusicStart = SndQueue.Start - MusicTime = _MusicTable.Files[SndQueue.Ref].Time - break - end - end - if PlayerFound then - --env.info(( 'MusicCanStart: MusicStart = ' .. MusicStart )) - --env.info(( 'MusicCanStart: MusicTime = ' .. MusicTime )) - --env.info(( 'MusicCanStart: timer.getTime() = ' .. timer.getTime() )) - - if MusicStart + MusicTime <= timer.getTime() then - MusicOut = true - end - else - MusicOut = true - end - end - - if MusicOut then - --env.info(( 'MusicCanStart: true' )) - else - --env.info(( 'MusicCanStart: false' )) - end - - return MusicOut -end - -function MusicScheduler() ---trace.scheduled("", "MusicScheduler") - - --env.info(( 'MusicScheduler:' )) - if _MusicTable['Queue'] ~= nil and _MusicTable.FileCnt > 0 then - --env.info(( 'MusicScheduler: Walking Sound Queue.')) - for SndQueueIdx, SndQueue in pairs( _MusicTable.Queue ) do - if SndQueue.Continue then - if MusicCanStart(SndQueue.PlayerName) then - --env.info(('MusicScheduler: MusicToGroup')) - MusicToPlayer( '', SndQueue.PlayerName, true ) - end - end - end - end - -end - - -env.info(( 'Init: Scripts Loaded v1.1' )) - - ---- @type SMOKECOLOR --- @field Green --- @field Red --- @field White --- @field Orange --- @field Blue - -SMOKECOLOR = trigger.smokeColor -- #SMOKECOLOR - ---- @type FLARECOLOR --- @field Green --- @field Red --- @field White --- @field Yellow - -FLARECOLOR = trigger.flareColor -- #FLARECOLOR - ---- Utilities static class. --- @type UTILS -UTILS = {} - - ---from http://lua-users.org/wiki/CopyTable -UTILS.DeepCopy = function(object) - local lookup_table = {} - local function _copy(object) - if type(object) ~= "table" then - return object - elseif lookup_table[object] then - return lookup_table[object] - end - local new_table = {} - lookup_table[object] = new_table - for index, value in pairs(object) do - new_table[_copy(index)] = _copy(value) - end - return setmetatable(new_table, getmetatable(object)) - end - local objectreturn = _copy(object) - return objectreturn -end - - --- porting in Slmod's serialize_slmod2 -UTILS.OneLineSerialize = function( tbl ) -- serialization of a table all on a single line, no comments, made to replace old get_table_string function - - lookup_table = {} - - local function _Serialize( tbl ) - - if type(tbl) == 'table' then --function only works for tables! - - if lookup_table[tbl] then - return lookup_table[object] - end - - local tbl_str = {} - - lookup_table[tbl] = tbl_str - - tbl_str[#tbl_str + 1] = '{' - - for ind,val in pairs(tbl) do -- serialize its fields - local ind_str = {} - if type(ind) == "number" then - ind_str[#ind_str + 1] = '[' - ind_str[#ind_str + 1] = tostring(ind) - ind_str[#ind_str + 1] = ']=' - else --must be a string - ind_str[#ind_str + 1] = '[' - ind_str[#ind_str + 1] = routines.utils.basicSerialize(ind) - ind_str[#ind_str + 1] = ']=' - end - - local val_str = {} - if ((type(val) == 'number') or (type(val) == 'boolean')) then - val_str[#val_str + 1] = tostring(val) - val_str[#val_str + 1] = ',' - tbl_str[#tbl_str + 1] = table.concat(ind_str) - tbl_str[#tbl_str + 1] = table.concat(val_str) - elseif type(val) == 'string' then - val_str[#val_str + 1] = routines.utils.basicSerialize(val) - val_str[#val_str + 1] = ',' - tbl_str[#tbl_str + 1] = table.concat(ind_str) - tbl_str[#tbl_str + 1] = table.concat(val_str) - elseif type(val) == 'nil' then -- won't ever happen, right? - val_str[#val_str + 1] = 'nil,' - tbl_str[#tbl_str + 1] = table.concat(ind_str) - tbl_str[#tbl_str + 1] = table.concat(val_str) - elseif type(val) == 'table' then - if ind == "__index" then - -- tbl_str[#tbl_str + 1] = "__index" - -- tbl_str[#tbl_str + 1] = ',' --I think this is right, I just added it - else - - val_str[#val_str + 1] = _Serialize(val) - val_str[#val_str + 1] = ',' --I think this is right, I just added it - tbl_str[#tbl_str + 1] = table.concat(ind_str) - tbl_str[#tbl_str + 1] = table.concat(val_str) - end - elseif type(val) == 'function' then - -- tbl_str[#tbl_str + 1] = "function " .. tostring(ind) - -- tbl_str[#tbl_str + 1] = ',' --I think this is right, I just added it - else --- env.info('unable to serialize value type ' .. routines.utils.basicSerialize(type(val)) .. ' at index ' .. tostring(ind)) --- env.info( debug.traceback() ) - end - - end - tbl_str[#tbl_str + 1] = '}' - return table.concat(tbl_str) - else - return tostring(tbl) - end - end - - local objectreturn = _Serialize(tbl) - return objectreturn -end - ---porting in Slmod's "safestring" basic serialize -UTILS.BasicSerialize = function(s) - if s == nil then - return "\"\"" - else - if ((type(s) == 'number') or (type(s) == 'boolean') or (type(s) == 'function') or (type(s) == 'table') or (type(s) == 'userdata') ) then - return tostring(s) - elseif type(s) == 'string' then - s = string.format('%q', s) - return s - end - end -end - - -UTILS.ToDegree = function(angle) - return angle*180/math.pi -end - -UTILS.ToRadian = function(angle) - return angle*math.pi/180 -end - -UTILS.MetersToNM = function(meters) - return meters/1852 -end - -UTILS.MetersToFeet = function(meters) - return meters/0.3048 -end - -UTILS.NMToMeters = function(NM) - return NM*1852 -end - -UTILS.FeetToMeters = function(feet) - return feet*0.3048 -end - -UTILS.MpsToKnots = function(mps) - return mps*3600/1852 -end - -UTILS.MpsToKmph = function(mps) - return mps*3.6 -end - -UTILS.KnotsToMps = function(knots) - return knots*1852/3600 -end - -UTILS.KmphToMps = function(kmph) - return kmph/3.6 -end - ---[[acc: -in DM: decimal point of minutes. -In DMS: decimal point of seconds. -position after the decimal of the least significant digit: -So: -42.32 - acc of 2. -]] -UTILS.tostringLL = function( lat, lon, acc, DMS) - - local latHemi, lonHemi - if lat > 0 then - latHemi = 'N' - else - latHemi = 'S' - end - - if lon > 0 then - lonHemi = 'E' - else - lonHemi = 'W' - end - - lat = math.abs(lat) - lon = math.abs(lon) - - local latDeg = math.floor(lat) - local latMin = (lat - latDeg)*60 - - local lonDeg = math.floor(lon) - local lonMin = (lon - lonDeg)*60 - - if DMS then -- degrees, minutes, and seconds. - local oldLatMin = latMin - latMin = math.floor(latMin) - local latSec = UTILS.Round((oldLatMin - latMin)*60, acc) - - local oldLonMin = lonMin - lonMin = math.floor(lonMin) - local lonSec = UTILS.Round((oldLonMin - lonMin)*60, acc) - - if latSec == 60 then - latSec = 0 - latMin = latMin + 1 - end - - if lonSec == 60 then - lonSec = 0 - lonMin = lonMin + 1 - end - - local secFrmtStr -- create the formatting string for the seconds place - if acc <= 0 then -- no decimal place. - secFrmtStr = '%02d' - else - local width = 3 + acc -- 01.310 - that's a width of 6, for example. - secFrmtStr = '%0' .. width .. '.' .. acc .. 'f' - end - - return string.format('%02d', latDeg) .. ' ' .. string.format('%02d', latMin) .. '\' ' .. string.format(secFrmtStr, latSec) .. '"' .. latHemi .. ' ' - .. string.format('%02d', lonDeg) .. ' ' .. string.format('%02d', lonMin) .. '\' ' .. string.format(secFrmtStr, lonSec) .. '"' .. lonHemi - - else -- degrees, decimal minutes. - latMin = UTILS.Round(latMin, acc) - lonMin = UTILS.Round(lonMin, acc) - - if latMin == 60 then - latMin = 0 - latDeg = latDeg + 1 - end - - if lonMin == 60 then - lonMin = 0 - lonDeg = lonDeg + 1 - end - - local minFrmtStr -- create the formatting string for the minutes place - if acc <= 0 then -- no decimal place. - minFrmtStr = '%02d' - else - local width = 3 + acc -- 01.310 - that's a width of 6, for example. - minFrmtStr = '%0' .. width .. '.' .. acc .. 'f' - end - - return string.format('%02d', latDeg) .. ' ' .. string.format(minFrmtStr, latMin) .. '\'' .. latHemi .. ' ' - .. string.format('%02d', lonDeg) .. ' ' .. string.format(minFrmtStr, lonMin) .. '\'' .. lonHemi - - end -end - - ---- From http://lua-users.org/wiki/SimpleRound --- use negative idp for rounding ahead of decimal place, positive for rounding after decimal place -function UTILS.Round( num, idp ) - local mult = 10 ^ ( idp or 0 ) - return math.floor( num * mult + 0.5 ) / mult -end - --- porting in Slmod's dostring -function UTILS.DoString( s ) - local f, err = loadstring( s ) - if f then - return true, f() - else - return false, err - end -end ---- This module contains the BASE class. --- --- 1) @{#BASE} class --- ================= --- The @{#BASE} class is the super class for all the classes defined within MOOSE. --- --- It handles: --- --- * The construction and inheritance of child classes. --- * The tracing of objects during mission execution within the **DCS.log** file, under the **"Saved Games\DCS\Logs"** folder. --- --- Note: Normally you would not use the BASE class unless you are extending the MOOSE framework with new classes. --- --- 1.1) BASE constructor --- --------------------- --- Any class derived from BASE, must use the @{Base#BASE.New) constructor within the @{Base#BASE.Inherit) method. --- See an example at the @{Base#BASE.New} method how this is done. --- --- 1.2) BASE Trace functionality --- ----------------------------- --- The BASE class contains trace methods to trace progress within a mission execution of a certain object. --- Note that these trace methods are inherited by each MOOSE class interiting BASE. --- As such, each object created from derived class from BASE can use the tracing functions to trace its execution. --- --- 1.2.1) Tracing functions --- ------------------------ --- There are basically 3 types of tracing methods available within BASE: --- --- * @{#BASE.F}: Trace the beginning of a function and its given parameters. An F is indicated at column 44 in the DCS.log file. --- * @{#BASE.T}: Trace further logic within a function giving optional variables or parameters. A T is indicated at column 44 in the DCS.log file. --- * @{#BASE.E}: Trace an exception within a function giving optional variables or parameters. An E is indicated at column 44 in the DCS.log file. An exception will always be traced. --- --- 1.2.2) Tracing levels --- --------------------- --- There are 3 tracing levels within MOOSE. --- These tracing levels were defined to avoid bulks of tracing to be generated by lots of objects. --- --- As such, the F and T methods have additional variants to trace level 2 and 3 respectively: --- --- * @{#BASE.F2}: Trace the beginning of a function and its given parameters with tracing level 2. --- * @{#BASE.F3}: Trace the beginning of a function and its given parameters with tracing level 3. --- * @{#BASE.T2}: Trace further logic within a function giving optional variables or parameters with tracing level 2. --- * @{#BASE.T3}: Trace further logic within a function giving optional variables or parameters with tracing level 3. --- --- 1.3) BASE Inheritance support --- =========================== --- The following methods are available to support inheritance: --- --- * @{#BASE.Inherit}: Inherits from a class. --- * @{#BASE.Inherited}: Returns the parent class from the class. --- --- Future --- ====== --- Further methods may be added to BASE whenever there is a need to make "overall" functions available within MOOSE. --- --- ==== --- --- ### Author: FlightControl --- --- @module Base - - - -local _TraceOnOff = true -local _TraceLevel = 1 -local _TraceAll = false -local _TraceClass = {} -local _TraceClassMethod = {} - -local _ClassID = 0 - ---- The BASE Class --- @type BASE --- @field ClassName The name of the class. --- @field ClassID The ID number of the class. --- @field ClassNameAndID The name of the class concatenated with the ID number of the class. -BASE = { - ClassName = "BASE", - ClassID = 0, - Events = {}, - States = {} -} - ---- The Formation Class --- @type FORMATION --- @field Cone A cone formation. -FORMATION = { - Cone = "Cone" -} - - - ---- The base constructor. This is the top top class of all classed defined within the MOOSE. --- Any new class needs to be derived from this class for proper inheritance. --- @param #BASE self --- @return #BASE The new instance of the BASE class. --- @usage --- -- This declares the constructor of the class TASK, inheriting from BASE. --- --- TASK constructor --- -- @param #TASK self --- -- @param Parameter The parameter of the New constructor. --- -- @return #TASK self --- function TASK:New( Parameter ) --- --- local self = BASE:Inherit( self, BASE:New() ) --- --- self.Variable = Parameter --- --- return self --- end --- @todo need to investigate if the deepCopy is really needed... Don't think so. -function BASE:New() - local self = routines.utils.deepCopy( self ) -- Create a new self instance - local MetaTable = {} - setmetatable( self, MetaTable ) - self.__index = self - _ClassID = _ClassID + 1 - self.ClassID = _ClassID - return self -end - ---- This is the worker method to inherit from a parent class. --- @param #BASE self --- @param Child is the Child class that inherits. --- @param #BASE Parent is the Parent class that the Child inherits from. --- @return #BASE Child -function BASE:Inherit( Child, Parent ) - local Child = routines.utils.deepCopy( Child ) - --local Parent = routines.utils.deepCopy( Parent ) - --local Parent = Parent - if Child ~= nil then - setmetatable( Child, Parent ) - Child.__index = Child - end - --self:T( 'Inherited from ' .. Parent.ClassName ) - return Child -end - ---- This is the worker method to retrieve the Parent class. --- @param #BASE self --- @param #BASE Child is the Child class from which the Parent class needs to be retrieved. --- @return #BASE -function BASE:GetParent( Child ) - local Parent = getmetatable( Child ) --- env.info('Inherited class of ' .. Child.ClassName .. ' is ' .. Parent.ClassName ) - return Parent -end - ---- Get the ClassName + ClassID of the class instance. --- The ClassName + ClassID is formatted as '%s#%09d'. --- @param #BASE self --- @return #string The ClassName + ClassID of the class instance. -function BASE:GetClassNameAndID() - return string.format( '%s#%09d', self.ClassName, self.ClassID ) -end - ---- Get the ClassName of the class instance. --- @param #BASE self --- @return #string The ClassName of the class instance. -function BASE:GetClassName() - return self.ClassName -end - ---- Get the ClassID of the class instance. --- @param #BASE self --- @return #string The ClassID of the class instance. -function BASE:GetClassID() - return self.ClassID -end - ---- Set a new listener for the class. --- @param self --- @param DCSTypes#Event Event --- @param #function EventFunction --- @return #BASE -function BASE:AddEvent( Event, EventFunction ) - self:F( Event ) - - self.Events[#self.Events+1] = {} - self.Events[#self.Events].Event = Event - self.Events[#self.Events].EventFunction = EventFunction - self.Events[#self.Events].EventEnabled = false - - return self -end - ---- Returns the event dispatcher --- @param #BASE self --- @return Event#EVENT -function BASE:Event() - - return _EVENTDISPATCHER -end - - - - - ---- Enable the event listeners for the class. --- @param #BASE self --- @return #BASE -function BASE:EnableEvents() - self:F( #self.Events ) - - for EventID, Event in pairs( self.Events ) do - Event.Self = self - Event.EventEnabled = true - end - self.Events.Handler = world.addEventHandler( self ) - - return self -end - - ---- Disable the event listeners for the class. --- @param #BASE self --- @return #BASE -function BASE:DisableEvents() - self:F() - - world.removeEventHandler( self ) - for EventID, Event in pairs( self.Events ) do - Event.Self = nil - Event.EventEnabled = false - end - - return self -end - - -local BaseEventCodes = { - "S_EVENT_SHOT", - "S_EVENT_HIT", - "S_EVENT_TAKEOFF", - "S_EVENT_LAND", - "S_EVENT_CRASH", - "S_EVENT_EJECTION", - "S_EVENT_REFUELING", - "S_EVENT_DEAD", - "S_EVENT_PILOT_DEAD", - "S_EVENT_BASE_CAPTURED", - "S_EVENT_MISSION_START", - "S_EVENT_MISSION_END", - "S_EVENT_TOOK_CONTROL", - "S_EVENT_REFUELING_STOP", - "S_EVENT_BIRTH", - "S_EVENT_HUMAN_FAILURE", - "S_EVENT_ENGINE_STARTUP", - "S_EVENT_ENGINE_SHUTDOWN", - "S_EVENT_PLAYER_ENTER_UNIT", - "S_EVENT_PLAYER_LEAVE_UNIT", - "S_EVENT_PLAYER_COMMENT", - "S_EVENT_SHOOTING_START", - "S_EVENT_SHOOTING_END", - "S_EVENT_MAX", -} - ---onEvent( {[1]="S_EVENT_BIRTH",[2]={["subPlace"]=5,["time"]=0,["initiator"]={["id_"]=16884480,},["place"]={["id_"]=5000040,},["id"]=15,["IniUnitName"]="US F-15C@RAMP-Air Support Mountains#001-01",},} --- Event = { --- id = enum world.event, --- time = Time, --- initiator = Unit, --- target = Unit, --- place = Unit, --- subPlace = enum world.BirthPlace, --- weapon = Weapon --- } - ---- Creation of a Birth Event. --- @param #BASE self --- @param DCSTypes#Time EventTime The time stamp of the event. --- @param DCSObject#Object Initiator The initiating object of the event. --- @param #string IniUnitName The initiating unit name. --- @param place --- @param subplace -function BASE:CreateEventBirth( EventTime, Initiator, IniUnitName, place, subplace ) - self:F( { EventTime, Initiator, IniUnitName, place, subplace } ) - - local Event = { - id = world.event.S_EVENT_BIRTH, - time = EventTime, - initiator = Initiator, - IniUnitName = IniUnitName, - place = place, - subplace = subplace - } - - world.onEvent( Event ) -end - ---- Creation of a Crash Event. --- @param #BASE self --- @param DCSTypes#Time EventTime The time stamp of the event. --- @param DCSObject#Object Initiator The initiating object of the event. -function BASE:CreateEventCrash( EventTime, Initiator ) - self:F( { EventTime, Initiator } ) - - local Event = { - id = world.event.S_EVENT_CRASH, - time = EventTime, - initiator = Initiator, - } - - world.onEvent( Event ) -end - --- TODO: Complete DCSTypes#Event structure. ---- The main event handling function... This function captures all events generated for the class. --- @param #BASE self --- @param DCSTypes#Event event -function BASE:onEvent(event) - --self:F( { BaseEventCodes[event.id], event } ) - - if self then - for EventID, EventObject in pairs( self.Events ) do - if EventObject.EventEnabled then - --env.info( 'onEvent Table EventObject.Self = ' .. tostring(EventObject.Self) ) - --env.info( 'onEvent event.id = ' .. tostring(event.id) ) - --env.info( 'onEvent EventObject.Event = ' .. tostring(EventObject.Event) ) - if event.id == EventObject.Event then - if self == EventObject.Self then - if event.initiator and event.initiator:isExist() then - event.IniUnitName = event.initiator:getName() - end - if event.target and event.target:isExist() then - event.TgtUnitName = event.target:getName() - end - --self:T( { BaseEventCodes[event.id], event } ) - --EventObject.EventFunction( self, event ) - end - end - end - end - end -end - -function BASE:SetState( Object, StateName, State ) - - local ClassNameAndID = Object:GetClassNameAndID() - - self.States[ClassNameAndID] = self.States[ClassNameAndID] or {} - self.States[ClassNameAndID][StateName] = State - self:T2( { ClassNameAndID, StateName, State } ) - - return self.States[ClassNameAndID][StateName] -end - -function BASE:GetState( Object, StateName ) - - local ClassNameAndID = Object:GetClassNameAndID() - - if self.States[ClassNameAndID] then - local State = self.States[ClassNameAndID][StateName] - self:T2( { ClassNameAndID, StateName, State } ) - return State - end - - return nil -end - -function BASE:ClearState( Object, StateName ) - - local ClassNameAndID = Object:GetClassNameAndID() - if self.States[ClassNameAndID] then - self.States[ClassNameAndID][StateName] = nil - end -end - --- Trace section - --- Log a trace (only shown when trace is on) --- TODO: Make trace function using variable parameters. - ---- Set trace on or off --- Note that when trace is off, no debug statement is performed, increasing performance! --- When Moose is loaded statically, (as one file), tracing is switched off by default. --- So tracing must be switched on manually in your mission if you are using Moose statically. --- When moose is loading dynamically (for moose class development), tracing is switched on by default. --- @param #BASE self --- @param #boolean TraceOnOff Switch the tracing on or off. --- @usage --- -- Switch the tracing On --- BASE:TraceOn( true ) --- --- -- Switch the tracing Off --- BASE:TraceOn( false ) -function BASE:TraceOnOff( TraceOnOff ) - _TraceOnOff = TraceOnOff -end - - ---- Enquires if tracing is on (for the class). --- @param #BASE self --- @return #boolean -function BASE:IsTrace() - - if debug and ( _TraceAll == true ) or ( _TraceClass[self.ClassName] or _TraceClassMethod[self.ClassName] ) then - return true - else - return false - end -end - ---- Set trace level --- @param #BASE self --- @param #number Level -function BASE:TraceLevel( Level ) - _TraceLevel = Level - self:E( "Tracing level " .. Level ) -end - ---- Trace all methods in MOOSE --- @param #BASE self --- @param #boolean TraceAll true = trace all methods in MOOSE. -function BASE:TraceAll( TraceAll ) - - _TraceAll = TraceAll - - if _TraceAll then - self:E( "Tracing all methods in MOOSE " ) - else - self:E( "Switched off tracing all methods in MOOSE" ) - end -end - ---- Set tracing for a class --- @param #BASE self --- @param #string Class -function BASE:TraceClass( Class ) - _TraceClass[Class] = true - _TraceClassMethod[Class] = {} - self:E( "Tracing class " .. Class ) -end - ---- Set tracing for a specific method of class --- @param #BASE self --- @param #string Class --- @param #string Method -function BASE:TraceClassMethod( Class, Method ) - if not _TraceClassMethod[Class] then - _TraceClassMethod[Class] = {} - _TraceClassMethod[Class].Method = {} - end - _TraceClassMethod[Class].Method[Method] = true - self:E( "Tracing method " .. Method .. " of class " .. Class ) -end - ---- Trace a function call. This function is private. --- @param #BASE self --- @param Arguments A #table or any field. -function BASE:_F( Arguments, DebugInfoCurrentParam, DebugInfoFromParam ) - - if debug and ( _TraceAll == true ) or ( _TraceClass[self.ClassName] or _TraceClassMethod[self.ClassName] ) then - - local DebugInfoCurrent = DebugInfoCurrentParam and DebugInfoCurrentParam or debug.getinfo( 2, "nl" ) - local DebugInfoFrom = DebugInfoFromParam and DebugInfoFromParam or debug.getinfo( 3, "l" ) - - local Function = "function" - if DebugInfoCurrent.name then - Function = DebugInfoCurrent.name - end - - if _TraceAll == true or _TraceClass[self.ClassName] or _TraceClassMethod[self.ClassName].Method[Function] then - local LineCurrent = 0 - if DebugInfoCurrent.currentline then - LineCurrent = DebugInfoCurrent.currentline - end - local LineFrom = 0 - if DebugInfoFrom then - LineFrom = DebugInfoFrom.currentline - end - env.info( string.format( "%6d(%6d)/%1s:%20s%05d.%s(%s)" , LineCurrent, LineFrom, "F", self.ClassName, self.ClassID, Function, routines.utils.oneLineSerialize( Arguments ) ) ) - end - end -end - ---- Trace a function call. Must be at the beginning of the function logic. --- @param #BASE self --- @param Arguments A #table or any field. -function BASE:F( Arguments ) - - if debug and _TraceOnOff then - local DebugInfoCurrent = debug.getinfo( 2, "nl" ) - local DebugInfoFrom = debug.getinfo( 3, "l" ) - - if _TraceLevel >= 1 then - self:_F( Arguments, DebugInfoCurrent, DebugInfoFrom ) - end - end -end - - ---- Trace a function call level 2. Must be at the beginning of the function logic. --- @param #BASE self --- @param Arguments A #table or any field. -function BASE:F2( Arguments ) - - if debug and _TraceOnOff then - local DebugInfoCurrent = debug.getinfo( 2, "nl" ) - local DebugInfoFrom = debug.getinfo( 3, "l" ) - - if _TraceLevel >= 2 then - self:_F( Arguments, DebugInfoCurrent, DebugInfoFrom ) - end - end -end - ---- Trace a function call level 3. Must be at the beginning of the function logic. --- @param #BASE self --- @param Arguments A #table or any field. -function BASE:F3( Arguments ) - - if debug and _TraceOnOff then - local DebugInfoCurrent = debug.getinfo( 2, "nl" ) - local DebugInfoFrom = debug.getinfo( 3, "l" ) - - if _TraceLevel >= 3 then - self:_F( Arguments, DebugInfoCurrent, DebugInfoFrom ) - end - end -end - ---- Trace a function logic. --- @param #BASE self --- @param Arguments A #table or any field. -function BASE:_T( Arguments, DebugInfoCurrentParam, DebugInfoFromParam ) - - if debug and ( _TraceAll == true ) or ( _TraceClass[self.ClassName] or _TraceClassMethod[self.ClassName] ) then - - local DebugInfoCurrent = DebugInfoCurrentParam and DebugInfoCurrentParam or debug.getinfo( 2, "nl" ) - local DebugInfoFrom = DebugInfoFromParam and DebugInfoFromParam or debug.getinfo( 3, "l" ) - - local Function = "function" - if DebugInfoCurrent.name then - Function = DebugInfoCurrent.name - end - - if _TraceAll == true or _TraceClass[self.ClassName] or _TraceClassMethod[self.ClassName].Method[Function] then - local LineCurrent = 0 - if DebugInfoCurrent.currentline then - LineCurrent = DebugInfoCurrent.currentline - end - local LineFrom = 0 - if DebugInfoFrom then - LineFrom = DebugInfoFrom.currentline - end - env.info( string.format( "%6d(%6d)/%1s:%20s%05d.%s" , LineCurrent, LineFrom, "T", self.ClassName, self.ClassID, routines.utils.oneLineSerialize( Arguments ) ) ) - end - end -end - ---- Trace a function logic level 1. Can be anywhere within the function logic. --- @param #BASE self --- @param Arguments A #table or any field. -function BASE:T( Arguments ) - - if debug and _TraceOnOff then - local DebugInfoCurrent = debug.getinfo( 2, "nl" ) - local DebugInfoFrom = debug.getinfo( 3, "l" ) - - if _TraceLevel >= 1 then - self:_T( Arguments, DebugInfoCurrent, DebugInfoFrom ) - end - end -end - - ---- Trace a function logic level 2. Can be anywhere within the function logic. --- @param #BASE self --- @param Arguments A #table or any field. -function BASE:T2( Arguments ) - - if debug and _TraceOnOff then - local DebugInfoCurrent = debug.getinfo( 2, "nl" ) - local DebugInfoFrom = debug.getinfo( 3, "l" ) - - if _TraceLevel >= 2 then - self:_T( Arguments, DebugInfoCurrent, DebugInfoFrom ) - end - end -end - ---- Trace a function logic level 3. Can be anywhere within the function logic. --- @param #BASE self --- @param Arguments A #table or any field. -function BASE:T3( Arguments ) - - if debug and _TraceOnOff then - local DebugInfoCurrent = debug.getinfo( 2, "nl" ) - local DebugInfoFrom = debug.getinfo( 3, "l" ) - - if _TraceLevel >= 3 then - self:_T( Arguments, DebugInfoCurrent, DebugInfoFrom ) - end - end -end - ---- Log an exception which will be traced always. Can be anywhere within the function logic. --- @param #BASE self --- @param Arguments A #table or any field. -function BASE:E( Arguments ) - - if debug then - local DebugInfoCurrent = debug.getinfo( 2, "nl" ) - local DebugInfoFrom = debug.getinfo( 3, "l" ) - - local Function = "function" - if DebugInfoCurrent.name then - Function = DebugInfoCurrent.name - end - - local LineCurrent = DebugInfoCurrent.currentline - local LineFrom = -1 - if DebugInfoFrom then - LineFrom = DebugInfoFrom.currentline - end - - env.info( string.format( "%6d(%6d)/%1s:%20s%05d.%s(%s)" , LineCurrent, LineFrom, "E", self.ClassName, self.ClassID, Function, routines.utils.oneLineSerialize( Arguments ) ) ) - end - -end - - - ---- This module contains the OBJECT class. --- --- 1) @{Object#OBJECT} class, extends @{Base#BASE} --- =========================================================== --- The @{Object#OBJECT} class is a wrapper class to handle the DCS Object objects: --- --- * Support all DCS Object APIs. --- * Enhance with Object specific APIs not in the DCS Object API set. --- * Manage the "state" of the DCS Object. --- --- 1.1) OBJECT constructor: --- ------------------------------ --- The OBJECT class provides the following functions to construct a OBJECT instance: --- --- * @{Object#OBJECT.New}(): Create a OBJECT instance. --- --- 1.2) OBJECT methods: --- -------------------------- --- The following methods can be used to identify an Object object: --- --- * @{Object#OBJECT.GetID}(): Returns the ID of the Object object. --- --- === --- --- @module Object --- @author FlightControl - ---- The OBJECT class --- @type OBJECT --- @extends Base#BASE --- @field #string ObjectName The name of the Object. -OBJECT = { - ClassName = "OBJECT", - ObjectName = "", -} - - ---- A DCSObject --- @type DCSObject --- @field id_ The ID of the controllable in DCS - ---- Create a new OBJECT from a DCSObject --- @param #OBJECT self --- @param DCSObject#Object ObjectName The Object name --- @return #OBJECT self -function OBJECT:New( ObjectName ) - local self = BASE:Inherit( self, BASE:New() ) - self:F2( ObjectName ) - self.ObjectName = ObjectName - return self -end - - ---- Returns the unit's unique identifier. --- @param Object#OBJECT self --- @return DCSObject#Object.ID ObjectID --- @return #nil The DCS Object is not existing or alive. -function OBJECT:GetID() - self:F2( self.ObjectName ) - - local DCSObject = self:GetDCSObject() - - if DCSObject then - local ObjectID = DCSObject:getID() - return ObjectID - end - - return nil -end - - - ---- This module contains the IDENTIFIABLE class. --- --- 1) @{Identifiable#IDENTIFIABLE} class, extends @{Object#OBJECT} --- =============================================================== --- The @{Identifiable#IDENTIFIABLE} class is a wrapper class to handle the DCS Identifiable objects: --- --- * Support all DCS Identifiable APIs. --- * Enhance with Identifiable specific APIs not in the DCS Identifiable API set. --- * Manage the "state" of the DCS Identifiable. --- --- 1.1) IDENTIFIABLE constructor: --- ------------------------------ --- The IDENTIFIABLE class provides the following functions to construct a IDENTIFIABLE instance: --- --- * @{Identifiable#IDENTIFIABLE.New}(): Create a IDENTIFIABLE instance. --- --- 1.2) IDENTIFIABLE methods: --- -------------------------- --- The following methods can be used to identify an identifiable object: --- --- * @{Identifiable#IDENTIFIABLE.GetName}(): Returns the name of the Identifiable. --- * @{Identifiable#IDENTIFIABLE.IsAlive}(): Returns if the Identifiable is alive. --- * @{Identifiable#IDENTIFIABLE.GetTypeName}(): Returns the type name of the Identifiable. --- * @{Identifiable#IDENTIFIABLE.GetCoalition}(): Returns the coalition of the Identifiable. --- * @{Identifiable#IDENTIFIABLE.GetCountry}(): Returns the country of the Identifiable. --- * @{Identifiable#IDENTIFIABLE.GetDesc}(): Returns the descriptor structure of the Identifiable. --- --- --- === --- --- @module Identifiable --- @author FlightControl - ---- The IDENTIFIABLE class --- @type IDENTIFIABLE --- @extends Object#OBJECT --- @field #string IdentifiableName The name of the identifiable. -IDENTIFIABLE = { - ClassName = "IDENTIFIABLE", - IdentifiableName = "", -} - -local _CategoryName = { - [Unit.Category.AIRPLANE] = "Airplane", - [Unit.Category.HELICOPTER] = "Helicoper", - [Unit.Category.GROUND_UNIT] = "Ground Identifiable", - [Unit.Category.SHIP] = "Ship", - [Unit.Category.STRUCTURE] = "Structure", - } - ---- Create a new IDENTIFIABLE from a DCSIdentifiable --- @param #IDENTIFIABLE self --- @param DCSIdentifiable#Identifiable IdentifiableName The DCS Identifiable name --- @return #IDENTIFIABLE self -function IDENTIFIABLE:New( IdentifiableName ) - local self = BASE:Inherit( self, OBJECT:New( IdentifiableName ) ) - self:F2( IdentifiableName ) - self.IdentifiableName = IdentifiableName - return self -end - ---- Returns if the Identifiable is alive. --- @param Identifiable#IDENTIFIABLE self --- @return #boolean true if Identifiable is alive. --- @return #nil The DCS Identifiable is not existing or alive. -function IDENTIFIABLE:IsAlive() - self:F2( self.IdentifiableName ) - - local DCSIdentifiable = self:GetDCSObject() - - if DCSIdentifiable then - local IdentifiableIsAlive = DCSIdentifiable:isExist() - return IdentifiableIsAlive - end - - return false -end - - - - ---- Returns DCS Identifiable object name. --- The function provides access to non-activated objects too. --- @param Identifiable#IDENTIFIABLE self --- @return #string The name of the DCS Identifiable. --- @return #nil The DCS Identifiable is not existing or alive. -function IDENTIFIABLE:GetName() - self:F2( self.IdentifiableName ) - - local DCSIdentifiable = self:GetDCSObject() - - if DCSIdentifiable then - local IdentifiableName = self.IdentifiableName - return IdentifiableName - end - - self:E( self.ClassName .. " " .. self.IdentifiableName .. " not found!" ) - return nil -end - - ---- Returns the type name of the DCS Identifiable. --- @param Identifiable#IDENTIFIABLE self --- @return #string The type name of the DCS Identifiable. --- @return #nil The DCS Identifiable is not existing or alive. -function IDENTIFIABLE:GetTypeName() - self:F2( self.IdentifiableName ) - - local DCSIdentifiable = self:GetDCSObject() - - if DCSIdentifiable then - local IdentifiableTypeName = DCSIdentifiable:getTypeName() - self:T3( IdentifiableTypeName ) - return IdentifiableTypeName - end - - self:E( self.ClassName .. " " .. self.IdentifiableName .. " not found!" ) - return nil -end - - ---- Returns category of the DCS Identifiable. --- @param #IDENTIFIABLE self --- @return DCSObject#Object.Category The category ID -function IDENTIFIABLE:GetCategory() - self:F2( self.ObjectName ) - - local DCSObject = self:GetDCSObject() - if DCSObject then - local ObjectCategory = DCSObject:getCategory() - self:T3( ObjectCategory ) - return ObjectCategory - end - - return nil -end - - ---- Returns the DCS Identifiable category name as defined within the DCS Identifiable Descriptor. --- @param Identifiable#IDENTIFIABLE self --- @return #string The DCS Identifiable Category Name -function IDENTIFIABLE:GetCategoryName() - local DCSIdentifiable = self:GetDCSObject() - - if DCSIdentifiable then - local IdentifiableCategoryName = _CategoryName[ self:GetDesc().category ] - return IdentifiableCategoryName - end - - self:E( self.ClassName .. " " .. self.IdentifiableName .. " not found!" ) - return nil -end - ---- Returns coalition of the Identifiable. --- @param Identifiable#IDENTIFIABLE self --- @return DCSCoalitionObject#coalition.side The side of the coalition. --- @return #nil The DCS Identifiable is not existing or alive. -function IDENTIFIABLE:GetCoalition() - self:F2( self.IdentifiableName ) - - local DCSIdentifiable = self:GetDCSObject() - - if DCSIdentifiable then - local IdentifiableCoalition = DCSIdentifiable:getCoalition() - self:T3( IdentifiableCoalition ) - return IdentifiableCoalition - end - - self:E( self.ClassName .. " " .. self.IdentifiableName .. " not found!" ) - return nil -end - ---- Returns country of the Identifiable. --- @param Identifiable#IDENTIFIABLE self --- @return DCScountry#country.id The country identifier. --- @return #nil The DCS Identifiable is not existing or alive. -function IDENTIFIABLE:GetCountry() - self:F2( self.IdentifiableName ) - - local DCSIdentifiable = self:GetDCSObject() - - if DCSIdentifiable then - local IdentifiableCountry = DCSIdentifiable:getCountry() - self:T3( IdentifiableCountry ) - return IdentifiableCountry - end - - self:E( self.ClassName .. " " .. self.IdentifiableName .. " not found!" ) - return nil -end - - - ---- Returns Identifiable descriptor. Descriptor type depends on Identifiable category. --- @param Identifiable#IDENTIFIABLE self --- @return DCSIdentifiable#Identifiable.Desc The Identifiable descriptor. --- @return #nil The DCS Identifiable is not existing or alive. -function IDENTIFIABLE:GetDesc() - self:F2( self.IdentifiableName ) - - local DCSIdentifiable = self:GetDCSObject() - - if DCSIdentifiable then - local IdentifiableDesc = DCSIdentifiable:getDesc() - self:T2( IdentifiableDesc ) - return IdentifiableDesc - end - - self:E( self.ClassName .. " " .. self.IdentifiableName .. " not found!" ) - return nil -end - - - - - - - - - ---- This module contains the POSITIONABLE class. --- --- 1) @{Positionable#POSITIONABLE} class, extends @{Identifiable#IDENTIFIABLE} --- =========================================================== --- The @{Positionable#POSITIONABLE} class is a wrapper class to handle the DCS Positionable objects: --- --- * Support all DCS Positionable APIs. --- * Enhance with Positionable specific APIs not in the DCS Positionable API set. --- * Manage the "state" of the DCS Positionable. --- --- 1.1) POSITIONABLE constructor: --- ------------------------------ --- The POSITIONABLE class provides the following functions to construct a POSITIONABLE instance: --- --- * @{Positionable#POSITIONABLE.New}(): Create a POSITIONABLE instance. --- --- 1.2) POSITIONABLE methods: --- -------------------------- --- The following methods can be used to identify an measurable object: --- --- * @{Positionable#POSITIONABLE.GetID}(): Returns the ID of the measurable object. --- * @{Positionable#POSITIONABLE.GetName}(): Returns the name of the measurable object. --- --- === --- --- @module Positionable --- @author FlightControl - ---- The POSITIONABLE class --- @type POSITIONABLE --- @extends Identifiable#IDENTIFIABLE --- @field #string PositionableName The name of the measurable. -POSITIONABLE = { - ClassName = "POSITIONABLE", - PositionableName = "", -} - ---- A DCSPositionable --- @type DCSPositionable --- @field id_ The ID of the controllable in DCS - ---- Create a new POSITIONABLE from a DCSPositionable --- @param #POSITIONABLE self --- @param DCSPositionable#Positionable PositionableName The DCS Positionable name --- @return #POSITIONABLE self -function POSITIONABLE:New( PositionableName ) - local self = BASE:Inherit( self, IDENTIFIABLE:New( PositionableName ) ) - - return self -end - ---- Returns the @{DCSTypes#Position3} position vectors indicating the point and direction vectors in 3D of the DCS Positionable within the mission. --- @param Positionable#POSITIONABLE self --- @return DCSTypes#Position The 3D position vectors of the DCS Positionable. --- @return #nil The DCS Positionable is not existing or alive. -function POSITIONABLE:GetPositionVec3() - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local PositionablePosition = DCSPositionable:getPosition() - self:T3( PositionablePosition ) - return PositionablePosition - end - - return nil -end - ---- Returns the @{DCSTypes#Vec2} vector indicating the point in 2D of the DCS Positionable within the mission. --- @param Positionable#POSITIONABLE self --- @return DCSTypes#Vec2 The 2D point vector of the DCS Positionable. --- @return #nil The DCS Positionable is not existing or alive. -function POSITIONABLE:GetVec2() - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local PositionablePointVec3 = DCSPositionable:getPosition().p - - local PositionablePointVec2 = {} - PositionablePointVec2.x = PositionablePointVec3.x - PositionablePointVec2.y = PositionablePointVec3.z - - self:T2( PositionablePointVec2 ) - return PositionablePointVec2 - end - - return nil -end - - ---- Returns a random @{DCSTypes#Vec3} vector within a range, indicating the point in 3D of the DCS Positionable within the mission. --- @param Positionable#POSITIONABLE self --- @return DCSTypes#Vec3 The 3D point vector of the DCS Positionable. --- @return #nil The DCS Positionable is not existing or alive. -function POSITIONABLE:GetRandomPointVec3( Radius ) - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local PositionablePointVec3 = DCSPositionable:getPosition().p - local PositionableRandomPointVec3 = {} - local angle = math.random() * math.pi*2; - PositionableRandomPointVec3.x = PositionablePointVec3.x + math.cos( angle ) * math.random() * Radius; - PositionableRandomPointVec3.y = PositionablePointVec3.y - PositionableRandomPointVec3.z = PositionablePointVec3.z + math.sin( angle ) * math.random() * Radius; - - self:T3( PositionableRandomPointVec3 ) - return PositionableRandomPointVec3 - end - - return nil -end - ---- Returns the @{DCSTypes#Vec3} vector indicating the point in 3D of the DCS Positionable within the mission. --- @param Positionable#POSITIONABLE self --- @return DCSTypes#Vec3 The 3D point vector of the DCS Positionable. --- @return #nil The DCS Positionable is not existing or alive. -function POSITIONABLE:GetPointVec3() - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local PositionablePointVec3 = DCSPositionable:getPosition().p - self:T3( PositionablePointVec3 ) - return PositionablePointVec3 - end - - return nil -end - ---- Returns the altitude of the DCS Positionable. --- @param Positionable#POSITIONABLE self --- @return DCSTypes#Distance The altitude of the DCS Positionable. --- @return #nil The DCS Positionable is not existing or alive. -function POSITIONABLE:GetAltitude() - self:F2() - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local PositionablePointVec3 = DCSPositionable:getPoint() --DCSTypes#Vec3 - return PositionablePointVec3.y - end - - return nil -end - ---- Returns if the Positionable is located above a runway. --- @param Positionable#POSITIONABLE self --- @return #boolean true if Positionable is above a runway. --- @return #nil The DCS Positionable is not existing or alive. -function POSITIONABLE:IsAboveRunway() - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - - local PointVec2 = self:GetVec2() - local SurfaceType = land.getSurfaceType( PointVec2 ) - local IsAboveRunway = SurfaceType == land.SurfaceType.RUNWAY - - self:T2( IsAboveRunway ) - return IsAboveRunway - end - - return nil -end - - - ---- Returns the DCS Positionable heading. --- @param Positionable#POSITIONABLE self --- @return #number The DCS Positionable heading -function POSITIONABLE:GetHeading() - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - - local PositionablePosition = DCSPositionable:getPosition() - if PositionablePosition then - local PositionableHeading = math.atan2( PositionablePosition.x.z, PositionablePosition.x.x ) - if PositionableHeading < 0 then - PositionableHeading = PositionableHeading + 2 * math.pi - end - self:T2( PositionableHeading ) - return PositionableHeading - end - end - - return nil -end - - ---- Returns true if the DCS Positionable is in the air. --- @param Positionable#POSITIONABLE self --- @return #boolean true if in the air. --- @return #nil The DCS Positionable is not existing or alive. -function POSITIONABLE:InAir() - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local PositionableInAir = DCSPositionable:inAir() - self:T3( PositionableInAir ) - return PositionableInAir - end - - return nil -end - ---- Returns the DCS Positionable velocity vector. --- @param Positionable#POSITIONABLE self --- @return DCSTypes#Vec3 The velocity vector --- @return #nil The DCS Positionable is not existing or alive. -function POSITIONABLE:GetVelocity() - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local PositionableVelocityVec3 = DCSPositionable:getVelocity() - self:T3( PositionableVelocityVec3 ) - return PositionableVelocityVec3 - end - - return nil -end - ---- Returns the @{Unit#UNIT} velocity in km/h. --- @param Positionable#POSITIONABLE self --- @return #number The velocity in km/h --- @return #nil The DCS Positionable is not existing or alive. -function POSITIONABLE:GetVelocityKMH() - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local VelocityVec3 = self:GetVelocity() - local Velocity = ( VelocityVec3.x ^ 2 + VelocityVec3.y ^ 2 + VelocityVec3.z ^ 2 ) ^ 0.5 -- in meters / sec - local Velocity = Velocity * 3.6 -- now it is in km/h. - self:T3( Velocity ) - return Velocity - end - - return nil -end - - - - ---- This module contains the CONTROLLABLE class. --- --- 1) @{Controllable#CONTROLLABLE} class, extends @{Positionable#POSITIONABLE} --- =========================================================== --- The @{Controllable#CONTROLLABLE} class is a wrapper class to handle the DCS Controllable objects: --- --- * Support all DCS Controllable APIs. --- * Enhance with Controllable specific APIs not in the DCS Controllable API set. --- * Handle local Controllable Controller. --- * Manage the "state" of the DCS Controllable. --- --- 1.1) CONTROLLABLE constructor --- ----------------------------- --- The CONTROLLABLE class provides the following functions to construct a CONTROLLABLE instance: --- --- * @{#CONTROLLABLE.New}(): Create a CONTROLLABLE instance. --- --- 1.2) CONTROLLABLE task methods --- ------------------------------ --- Several controllable task methods are available that help you to prepare tasks. --- These methods return a string consisting of the task description, which can then be given to either a @{Controllable#CONTROLLABLE.PushTask} or @{Controllable#SetTask} method to assign the task to the CONTROLLABLE. --- Tasks are specific for the category of the CONTROLLABLE, more specific, for AIR, GROUND or AIR and GROUND. --- Each task description where applicable indicates for which controllable category the task is valid. --- There are 2 main subdivisions of tasks: Assigned tasks and EnRoute tasks. --- --- ### 1.2.1) Assigned task methods --- --- Assigned task methods make the controllable execute the task where the location of the (possible) targets of the task are known before being detected. --- This is different from the EnRoute tasks, where the targets of the task need to be detected before the task can be executed. --- --- Find below a list of the **assigned task** methods: --- --- * @{#CONTROLLABLE.TaskAttackControllable}: (AIR) Attack a Controllable. --- * @{#CONTROLLABLE.TaskAttackMapObject}: (AIR) Attacking the map object (building, structure, e.t.c). --- * @{#CONTROLLABLE.TaskAttackUnit}: (AIR) Attack the Unit. --- * @{#CONTROLLABLE.TaskBombing}: (AIR) Delivering weapon at the point on the ground. --- * @{#CONTROLLABLE.TaskBombingRunway}: (AIR) Delivering weapon on the runway. --- * @{#CONTROLLABLE.TaskEmbarking}: (AIR) Move the controllable to a Vec2 Point, wait for a defined duration and embark a controllable. --- * @{#CONTROLLABLE.TaskEmbarkToTransport}: (GROUND) Embark to a Transport landed at a location. --- * @{#CONTROLLABLE.TaskEscort}: (AIR) Escort another airborne controllable. --- * @{#CONTROLLABLE.TaskFAC_AttackControllable}: (AIR + GROUND) The task makes the controllable/unit a FAC and orders the FAC to control the target (enemy ground controllable) destruction. --- * @{#CONTROLLABLE.TaskFireAtPoint}: (GROUND) Fire at a VEC2 point until ammunition is finished. --- * @{#CONTROLLABLE.TaskFollow}: (AIR) Following another airborne controllable. --- * @{#CONTROLLABLE.TaskHold}: (GROUND) Hold ground controllable from moving. --- * @{#CONTROLLABLE.TaskHoldPosition}: (AIR) Hold position at the current position of the first unit of the controllable. --- * @{#CONTROLLABLE.TaskLand}: (AIR HELICOPTER) Landing at the ground. For helicopters only. --- * @{#CONTROLLABLE.TaskLandAtZone}: (AIR) Land the controllable at a @{Zone#ZONE_RADIUS). --- * @{#CONTROLLABLE.TaskOrbitCircle}: (AIR) Orbit at the current position of the first unit of the controllable at a specified alititude. --- * @{#CONTROLLABLE.TaskOrbitCircleAtVec2}: (AIR) Orbit at a specified position at a specified alititude during a specified duration with a specified speed. --- * @{#CONTROLLABLE.TaskRefueling}: (AIR) Refueling from the nearest tanker. No parameters. --- * @{#CONTROLLABLE.TaskRoute}: (AIR + GROUND) Return a Misson task to follow a given route defined by Points. --- * @{#CONTROLLABLE.TaskRouteToVec2}: (AIR + GROUND) Make the Controllable move to a given point. --- * @{#CONTROLLABLE.TaskRouteToVec3}: (AIR + GROUND) Make the Controllable move to a given point. --- * @{#CONTROLLABLE.TaskRouteToZone}: (AIR + GROUND) Route the controllable to a given zone. --- * @{#CONTROLLABLE.TaskReturnToBase}: (AIR) Route the controllable to an airbase. --- --- ### 1.2.2) EnRoute task methods --- --- EnRoute tasks require the targets of the task need to be detected by the controllable (using its sensors) before the task can be executed: --- --- * @{#CONTROLLABLE.EnRouteTaskAWACS}: (AIR) Aircraft will act as an AWACS for friendly units (will provide them with information about contacts). No parameters. --- * @{#CONTROLLABLE.EnRouteTaskEngageControllable}: (AIR) Engaging a controllable. The task does not assign the target controllable to the unit/controllable to attack now; it just allows the unit/controllable to engage the target controllable as well as other assigned targets. --- * @{#CONTROLLABLE.EnRouteTaskEngageTargets}: (AIR) Engaging targets of defined types. --- * @{#CONTROLLABLE.EnRouteTaskEWR}: (AIR) Attack the Unit. --- * @{#CONTROLLABLE.EnRouteTaskFAC}: (AIR + GROUND) The task makes the controllable/unit a FAC and lets the FAC to choose a targets (enemy ground controllable) around as well as other assigned targets. --- * @{#CONTROLLABLE.EnRouteTaskFAC_EngageControllable}: (AIR + GROUND) The task makes the controllable/unit a FAC and lets the FAC to choose the target (enemy ground controllable) as well as other assigned targets. --- * @{#CONTROLLABLE.EnRouteTaskTanker}: (AIR) Aircraft will act as a tanker for friendly units. No parameters. --- --- ### 1.2.3) Preparation task methods --- --- There are certain task methods that allow to tailor the task behaviour: --- --- * @{#CONTROLLABLE.TaskWrappedAction}: Return a WrappedAction Task taking a Command. --- * @{#CONTROLLABLE.TaskCombo}: Return a Combo Task taking an array of Tasks. --- * @{#CONTROLLABLE.TaskCondition}: Return a condition section for a controlled task. --- * @{#CONTROLLABLE.TaskControlled}: Return a Controlled Task taking a Task and a TaskCondition. --- --- ### 1.2.4) Obtain the mission from controllable templates --- --- Controllable templates contain complete mission descriptions. Sometimes you want to copy a complete mission from a controllable and assign it to another: --- --- * @{#CONTROLLABLE.TaskMission}: (AIR + GROUND) Return a mission task from a mission template. --- --- 1.3) CONTROLLABLE Command methods --- -------------------------- --- Controllable **command methods** prepare the execution of commands using the @{#CONTROLLABLE.SetCommand} method: --- --- * @{#CONTROLLABLE.CommandDoScript}: Do Script command. --- * @{#CONTROLLABLE.CommandSwitchWayPoint}: Perform a switch waypoint command. --- --- 1.4) CONTROLLABLE Option methods --- ------------------------- --- Controllable **Option methods** change the behaviour of the Controllable while being alive. --- --- ### 1.4.1) Rule of Engagement: --- --- * @{#CONTROLLABLE.OptionROEWeaponFree} --- * @{#CONTROLLABLE.OptionROEOpenFire} --- * @{#CONTROLLABLE.OptionROEReturnFire} --- * @{#CONTROLLABLE.OptionROEEvadeFire} --- --- To check whether an ROE option is valid for a specific controllable, use: --- --- * @{#CONTROLLABLE.OptionROEWeaponFreePossible} --- * @{#CONTROLLABLE.OptionROEOpenFirePossible} --- * @{#CONTROLLABLE.OptionROEReturnFirePossible} --- * @{#CONTROLLABLE.OptionROEEvadeFirePossible} --- --- ### 1.4.2) Rule on thread: --- --- * @{#CONTROLLABLE.OptionROTNoReaction} --- * @{#CONTROLLABLE.OptionROTPassiveDefense} --- * @{#CONTROLLABLE.OptionROTEvadeFire} --- * @{#CONTROLLABLE.OptionROTVertical} --- --- To test whether an ROT option is valid for a specific controllable, use: --- --- * @{#CONTROLLABLE.OptionROTNoReactionPossible} --- * @{#CONTROLLABLE.OptionROTPassiveDefensePossible} --- * @{#CONTROLLABLE.OptionROTEvadeFirePossible} --- * @{#CONTROLLABLE.OptionROTVerticalPossible} --- --- === --- --- @module Controllable --- @author FlightControl - ---- The CONTROLLABLE class --- @type CONTROLLABLE --- @extends Positionable#POSITIONABLE --- @field DCSControllable#Controllable DCSControllable The DCS controllable class. --- @field #string ControllableName The name of the controllable. -CONTROLLABLE = { - ClassName = "CONTROLLABLE", - ControllableName = "", - WayPointFunctions = {}, -} - ---- Create a new CONTROLLABLE from a DCSControllable --- @param #CONTROLLABLE self --- @param DCSControllable#Controllable ControllableName The DCS Controllable name --- @return #CONTROLLABLE self -function CONTROLLABLE:New( ControllableName ) - local self = BASE:Inherit( self, POSITIONABLE:New( ControllableName ) ) - self:F2( ControllableName ) - self.ControllableName = ControllableName - return self -end - --- DCS Controllable methods support. - ---- Get the controller for the CONTROLLABLE. --- @param #CONTROLLABLE self --- @return DCSController#Controller -function CONTROLLABLE:_GetController() - self:F2( { self.ControllableName } ) - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - local ControllableController = DCSControllable:getController() - self:T3( ControllableController ) - return ControllableController - end - - return nil -end - - - --- Tasks - ---- Popping current Task from the controllable. --- @param #CONTROLLABLE self --- @return Controllable#CONTROLLABLE self -function CONTROLLABLE:PopCurrentTask() - self:F2() - - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - local Controller = self:_GetController() - Controller:popTask() - return self - end - - return nil -end - ---- Pushing Task on the queue from the controllable. --- @param #CONTROLLABLE self --- @return Controllable#CONTROLLABLE self -function CONTROLLABLE:PushTask( DCSTask, WaitTime ) - self:F2() - - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - local Controller = self:_GetController() - - -- When a controllable SPAWNs, it takes about a second to get the controllable in the simulator. Setting tasks to unspawned controllables provides unexpected results. - -- Therefore we schedule the functions to set the mission and options for the Controllable. - -- Controller:pushTask( DCSTask ) - - if WaitTime then - SCHEDULER:New( Controller, Controller.pushTask, { DCSTask }, WaitTime ) - else - Controller:pushTask( DCSTask ) - end - - return self - end - - return nil -end - ---- Clearing the Task Queue and Setting the Task on the queue from the controllable. --- @param #CONTROLLABLE self --- @return Controllable#CONTROLLABLE self -function CONTROLLABLE:SetTask( DCSTask, WaitTime ) - self:F2( { DCSTask } ) - - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - - local Controller = self:_GetController() - - -- When a controllable SPAWNs, it takes about a second to get the controllable in the simulator. Setting tasks to unspawned controllables provides unexpected results. - -- Therefore we schedule the functions to set the mission and options for the Controllable. - -- Controller.setTask( Controller, DCSTask ) - - if not WaitTime then - WaitTime = 1 - end - SCHEDULER:New( Controller, Controller.setTask, { DCSTask }, WaitTime ) - - return self - end - - return nil -end - - ---- Return a condition section for a controlled task. --- @param #CONTROLLABLE self --- @param DCSTime#Time time --- @param #string userFlag --- @param #boolean userFlagValue --- @param #string condition --- @param DCSTime#Time duration --- @param #number lastWayPoint --- return DCSTask#Task -function CONTROLLABLE:TaskCondition( time, userFlag, userFlagValue, condition, duration, lastWayPoint ) - self:F2( { time, userFlag, userFlagValue, condition, duration, lastWayPoint } ) - - local DCSStopCondition = {} - DCSStopCondition.time = time - DCSStopCondition.userFlag = userFlag - DCSStopCondition.userFlagValue = userFlagValue - DCSStopCondition.condition = condition - DCSStopCondition.duration = duration - DCSStopCondition.lastWayPoint = lastWayPoint - - self:T3( { DCSStopCondition } ) - return DCSStopCondition -end - ---- Return a Controlled Task taking a Task and a TaskCondition. --- @param #CONTROLLABLE self --- @param DCSTask#Task DCSTask --- @param #DCSStopCondition DCSStopCondition --- @return DCSTask#Task -function CONTROLLABLE:TaskControlled( DCSTask, DCSStopCondition ) - self:F2( { DCSTask, DCSStopCondition } ) - - local DCSTaskControlled - - DCSTaskControlled = { - id = 'ControlledTask', - params = { - task = DCSTask, - stopCondition = DCSStopCondition - } - } - - self:T3( { DCSTaskControlled } ) - return DCSTaskControlled -end - ---- Return a Combo Task taking an array of Tasks. --- @param #CONTROLLABLE self --- @param DCSTask#TaskArray DCSTasks Array of @{DCSTask#Task} --- @return DCSTask#Task -function CONTROLLABLE:TaskCombo( DCSTasks ) - self:F2( { DCSTasks } ) - - local DCSTaskCombo - - DCSTaskCombo = { - id = 'ComboTask', - params = { - tasks = DCSTasks - } - } - - self:T3( { DCSTaskCombo } ) - return DCSTaskCombo -end - ---- Return a WrappedAction Task taking a Command. --- @param #CONTROLLABLE self --- @param DCSCommand#Command DCSCommand --- @return DCSTask#Task -function CONTROLLABLE:TaskWrappedAction( DCSCommand, Index ) - self:F2( { DCSCommand } ) - - local DCSTaskWrappedAction - - DCSTaskWrappedAction = { - id = "WrappedAction", - enabled = true, - number = Index, - auto = false, - params = { - action = DCSCommand, - }, - } - - self:T3( { DCSTaskWrappedAction } ) - return DCSTaskWrappedAction -end - ---- Executes a command action --- @param #CONTROLLABLE self --- @param DCSCommand#Command DCSCommand --- @return #CONTROLLABLE self -function CONTROLLABLE:SetCommand( DCSCommand ) - self:F2( DCSCommand ) - - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - local Controller = self:_GetController() - Controller:setCommand( DCSCommand ) - return self - end - - return nil -end - ---- Perform a switch waypoint command --- @param #CONTROLLABLE self --- @param #number FromWayPoint --- @param #number ToWayPoint --- @return DCSTask#Task --- @usage --- --- This test demonstrates the use(s) of the SwitchWayPoint method of the GROUP class. --- HeliGroup = GROUP:FindByName( "Helicopter" ) --- --- --- Route the helicopter back to the FARP after 60 seconds. --- -- We use the SCHEDULER class to do this. --- SCHEDULER:New( nil, --- function( HeliGroup ) --- local CommandRTB = HeliGroup:CommandSwitchWayPoint( 2, 8 ) --- HeliGroup:SetCommand( CommandRTB ) --- end, { HeliGroup }, 90 --- ) -function CONTROLLABLE:CommandSwitchWayPoint( FromWayPoint, ToWayPoint ) - self:F2( { FromWayPoint, ToWayPoint } ) - - local CommandSwitchWayPoint = { - id = 'SwitchWaypoint', - params = { - fromWaypointIndex = FromWayPoint, - goToWaypointIndex = ToWayPoint, - }, - } - - self:T3( { CommandSwitchWayPoint } ) - return CommandSwitchWayPoint -end - ---- Perform stop route command --- @param #CONTROLLABLE self --- @param #boolean StopRoute --- @return DCSTask#Task -function CONTROLLABLE:CommandStopRoute( StopRoute, Index ) - self:F2( { StopRoute, Index } ) - - local CommandStopRoute = { - id = 'StopRoute', - params = { - value = StopRoute, - }, - } - - self:T3( { CommandStopRoute } ) - return CommandStopRoute -end - - --- TASKS FOR AIR CONTROLLABLES - - ---- (AIR) Attack a Controllable. --- @param #CONTROLLABLE self --- @param Controllable#CONTROLLABLE AttackGroup The Controllable to be attacked. --- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. --- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. --- @param DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. --- @param DCSTypes#Distance Altitude (optional) Desired attack start altitude. Controllable/aircraft will make its attacks from the altitude. If the altitude is too low or too high to use weapon aircraft/controllable will choose closest altitude to the desired attack start altitude. If the desired altitude is defined controllable/aircraft will not attack from safe altitude. --- @param #boolean AttackQtyLimit (optional) The flag determines how to interpret attackQty parameter. If the flag is true then attackQty is a limit on maximal attack quantity for "AttackControllable" and "AttackUnit" tasks. If the flag is false then attackQty is a desired attack quantity for "Bombing" and "BombingRunway" tasks. --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:TaskAttackGroup( AttackGroup, WeaponType, WeaponExpend, AttackQty, Direction, Altitude, AttackQtyLimit ) - self:F2( { self.ControllableName, AttackGroup, WeaponType, WeaponExpend, AttackQty, Direction, Altitude, AttackQtyLimit } ) - - -- AttackControllable = { - -- id = 'AttackControllable', - -- params = { - -- controllableId = Controllable.ID, - -- weaponType = number, - -- expend = enum AI.Task.WeaponExpend, - -- attackQty = number, - -- directionEnabled = boolean, - -- direction = Azimuth, - -- altitudeEnabled = boolean, - -- altitude = Distance, - -- attackQtyLimit = boolean, - -- } - -- } - - local DirectionEnabled = nil - if Direction then - DirectionEnabled = true - end - - local AltitudeEnabled = nil - if Altitude then - AltitudeEnabled = true - end - - local DCSTask - DCSTask = { id = 'AttackControllable', - params = { - controllableId = AttackGroup:GetID(), - weaponType = WeaponType, - expend = WeaponExpend, - attackQty = AttackQty, - directionEnabled = DirectionEnabled, - direction = Direction, - altitudeEnabled = AltitudeEnabled, - altitude = Altitude, - attackQtyLimit = AttackQtyLimit, - }, - }, - - self:T3( { DCSTask } ) - return DCSTask -end - - ---- (AIR) Attack the Unit. --- @param #CONTROLLABLE self --- @param Unit#UNIT AttackUnit The unit. --- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. --- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. --- @param DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. --- @param #boolean AttackQtyLimit (optional) The flag determines how to interpret attackQty parameter. If the flag is true then attackQty is a limit on maximal attack quantity for "AttackControllable" and "AttackUnit" tasks. If the flag is false then attackQty is a desired attack quantity for "Bombing" and "BombingRunway" tasks. --- @param #boolean ControllableAttack (optional) Flag indicates that the target must be engaged by all aircrafts of the controllable. Has effect only if the task is assigned to a controllable, not to a single aircraft. --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:TaskAttackUnit( AttackUnit, WeaponType, WeaponExpend, AttackQty, Direction, AttackQtyLimit, ControllableAttack ) - self:F2( { self.ControllableName, AttackUnit, WeaponType, WeaponExpend, AttackQty, Direction, AttackQtyLimit, ControllableAttack } ) - - -- AttackUnit = { - -- id = 'AttackUnit', - -- params = { - -- unitId = Unit.ID, - -- weaponType = number, - -- expend = enum AI.Task.WeaponExpend - -- attackQty = number, - -- direction = Azimuth, - -- attackQtyLimit = boolean, - -- controllableAttack = boolean, - -- } - -- } - - local DCSTask - DCSTask = { id = 'AttackUnit', - params = { - unitId = AttackUnit:GetID(), - weaponType = WeaponType, - expend = WeaponExpend, - attackQty = AttackQty, - direction = Direction, - attackQtyLimit = AttackQtyLimit, - controllableAttack = ControllableAttack, - }, - }, - - self:T3( { DCSTask } ) - return DCSTask -end - - ---- (AIR) Delivering weapon at the point on the ground. --- @param #CONTROLLABLE self --- @param DCSTypes#Vec2 PointVec2 2D-coordinates of the point to deliver weapon at. --- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. --- @param #number AttackQty (optional) Desired quantity of passes. The parameter is not the same in AttackControllable and AttackUnit tasks. --- @param DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. --- @param #boolean ControllableAttack (optional) Flag indicates that the target must be engaged by all aircrafts of the controllable. Has effect only if the task is assigned to a controllable, not to a single aircraft. --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:TaskBombing( PointVec2, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack ) - self:F2( { self.ControllableName, PointVec2, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack } ) - --- Bombing = { --- id = 'Bombing', --- params = { --- point = Vec2, --- weaponType = number, --- expend = enum AI.Task.WeaponExpend, --- attackQty = number, --- direction = Azimuth, --- controllableAttack = boolean, --- } --- } - - local DCSTask - DCSTask = { id = 'Bombing', - params = { - point = PointVec2, - weaponType = WeaponType, - expend = WeaponExpend, - attackQty = AttackQty, - direction = Direction, - controllableAttack = ControllableAttack, - }, - }, - - self:T3( { DCSTask } ) - return DCSTask -end - ---- (AIR) Orbit at a specified position at a specified alititude during a specified duration with a specified speed. --- @param #CONTROLLABLE self --- @param DCSTypes#Vec2 Point The point to hold the position. --- @param #number Altitude The altitude to hold the position. --- @param #number Speed The speed flying when holding the position. --- @return #CONTROLLABLE self -function CONTROLLABLE:TaskOrbitCircleAtVec2( Point, Altitude, Speed ) - self:F2( { self.ControllableName, Point, Altitude, Speed } ) - - -- pattern = enum AI.Task.OribtPattern, - -- point = Vec2, - -- point2 = Vec2, - -- speed = Distance, - -- altitude = Distance - - local LandHeight = land.getHeight( Point ) - - self:T3( { LandHeight } ) - - local DCSTask = { id = 'Orbit', - params = { pattern = AI.Task.OrbitPattern.CIRCLE, - point = Point, - speed = Speed, - altitude = Altitude + LandHeight - } - } - - - -- local AITask = { id = 'ControlledTask', - -- params = { task = { id = 'Orbit', - -- params = { pattern = AI.Task.OrbitPattern.CIRCLE, - -- point = Point, - -- speed = Speed, - -- altitude = Altitude + LandHeight - -- } - -- }, - -- stopCondition = { duration = Duration - -- } - -- } - -- } - -- ) - - return DCSTask -end - ---- (AIR) Orbit at the current position of the first unit of the controllable at a specified alititude. --- @param #CONTROLLABLE self --- @param #number Altitude The altitude to hold the position. --- @param #number Speed The speed flying when holding the position. --- @return #CONTROLLABLE self -function CONTROLLABLE:TaskOrbitCircle( Altitude, Speed ) - self:F2( { self.ControllableName, Altitude, Speed } ) - - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - local ControllablePoint = self:GetVec2() - return self:TaskOrbitCircleAtVec2( ControllablePoint, Altitude, Speed ) - end - - return nil -end - - - ---- (AIR) Hold position at the current position of the first unit of the controllable. --- @param #CONTROLLABLE self --- @param #number Duration The maximum duration in seconds to hold the position. --- @return #CONTROLLABLE self -function CONTROLLABLE:TaskHoldPosition() - self:F2( { self.ControllableName } ) - - return self:TaskOrbitCircle( 30, 10 ) -end - - - - ---- (AIR) Attacking the map object (building, structure, e.t.c). --- @param #CONTROLLABLE self --- @param DCSTypes#Vec2 PointVec2 2D-coordinates of the point the map object is closest to. The distance between the point and the map object must not be greater than 2000 meters. Object id is not used here because Mission Editor doesn't support map object identificators. --- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. --- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. --- @param DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. --- @param #boolean ControllableAttack (optional) Flag indicates that the target must be engaged by all aircrafts of the controllable. Has effect only if the task is assigned to a controllable, not to a single aircraft. --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:TaskAttackMapObject( PointVec2, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack ) - self:F2( { self.ControllableName, PointVec2, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack } ) - --- AttackMapObject = { --- id = 'AttackMapObject', --- params = { --- point = Vec2, --- weaponType = number, --- expend = enum AI.Task.WeaponExpend, --- attackQty = number, --- direction = Azimuth, --- controllableAttack = boolean, --- } --- } - - local DCSTask - DCSTask = { id = 'AttackMapObject', - params = { - point = PointVec2, - weaponType = WeaponType, - expend = WeaponExpend, - attackQty = AttackQty, - direction = Direction, - controllableAttack = ControllableAttack, - }, - }, - - self:T3( { DCSTask } ) - return DCSTask -end - - ---- (AIR) Delivering weapon on the runway. --- @param #CONTROLLABLE self --- @param Airbase#AIRBASE Airbase Airbase to attack. --- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. --- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. --- @param DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. --- @param #boolean ControllableAttack (optional) Flag indicates that the target must be engaged by all aircrafts of the controllable. Has effect only if the task is assigned to a controllable, not to a single aircraft. --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:TaskBombingRunway( Airbase, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack ) - self:F2( { self.ControllableName, Airbase, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack } ) - --- BombingRunway = { --- id = 'BombingRunway', --- params = { --- runwayId = AirdromeId, --- weaponType = number, --- expend = enum AI.Task.WeaponExpend, --- attackQty = number, --- direction = Azimuth, --- controllableAttack = boolean, --- } --- } - - local DCSTask - DCSTask = { id = 'BombingRunway', - params = { - point = Airbase:GetID(), - weaponType = WeaponType, - expend = WeaponExpend, - attackQty = AttackQty, - direction = Direction, - controllableAttack = ControllableAttack, - }, - }, - - self:T3( { DCSTask } ) - return DCSTask -end - - ---- (AIR) Refueling from the nearest tanker. No parameters. --- @param #CONTROLLABLE self --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:TaskRefueling() - self:F2( { self.ControllableName } ) - --- Refueling = { --- id = 'Refueling', --- params = {} --- } - - local DCSTask - DCSTask = { id = 'Refueling', - params = { - }, - }, - - self:T3( { DCSTask } ) - return DCSTask -end - - ---- (AIR HELICOPTER) Landing at the ground. For helicopters only. --- @param #CONTROLLABLE self --- @param DCSTypes#Vec2 Point The point where to land. --- @param #number Duration The duration in seconds to stay on the ground. --- @return #CONTROLLABLE self -function CONTROLLABLE:TaskLandAtVec2( Point, Duration ) - self:F2( { self.ControllableName, Point, Duration } ) - --- Land = { --- id= 'Land', --- params = { --- point = Vec2, --- durationFlag = boolean, --- duration = Time --- } --- } - - local DCSTask - if Duration and Duration > 0 then - DCSTask = { id = 'Land', - params = { - point = Point, - durationFlag = true, - duration = Duration, - }, - } - else - DCSTask = { id = 'Land', - params = { - point = Point, - durationFlag = false, - }, - } - end - - self:T3( DCSTask ) - return DCSTask -end - ---- (AIR) Land the controllable at a @{Zone#ZONE_RADIUS). --- @param #CONTROLLABLE self --- @param Zone#ZONE Zone The zone where to land. --- @param #number Duration The duration in seconds to stay on the ground. --- @return #CONTROLLABLE self -function CONTROLLABLE:TaskLandAtZone( Zone, Duration, RandomPoint ) - self:F2( { self.ControllableName, Zone, Duration, RandomPoint } ) - - local Point - if RandomPoint then - Point = Zone:GetRandomVec2() - else - Point = Zone:GetVec2() - end - - local DCSTask = self:TaskLandAtVec2( Point, Duration ) - - self:T3( DCSTask ) - return DCSTask -end - - - ---- (AIR) Following another airborne controllable. --- The unit / controllable will follow lead unit of another controllable, wingmens of both controllables will continue following their leaders. --- If another controllable is on land the unit / controllable will orbit around. --- @param #CONTROLLABLE self --- @param Controllable#CONTROLLABLE FollowControllable The controllable to be followed. --- @param DCSTypes#Vec3 PointVec3 Position of the unit / lead unit of the controllable relative lead unit of another controllable in frame reference oriented by course of lead unit of another controllable. If another controllable is on land the unit / controllable will orbit around. --- @param #number LastWaypointIndex Detach waypoint of another controllable. Once reached the unit / controllable Follow task is finished. --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:TaskFollow( FollowControllable, PointVec3, LastWaypointIndex ) - self:F2( { self.ControllableName, FollowControllable, PointVec3, LastWaypointIndex } ) - --- Follow = { --- id = 'Follow', --- params = { --- controllableId = Controllable.ID, --- pos = Vec3, --- lastWptIndexFlag = boolean, --- lastWptIndex = number --- } --- } - - local LastWaypointIndexFlag = nil - if LastWaypointIndex then - LastWaypointIndexFlag = true - end - - local DCSTask - DCSTask = { id = 'Follow', - params = { - controllableId = FollowControllable:GetID(), - pos = PointVec3, - lastWptIndexFlag = LastWaypointIndexFlag, - lastWptIndex = LastWaypointIndex, - }, - }, - - self:T3( { DCSTask } ) - return DCSTask -end - - ---- (AIR) Escort another airborne controllable. --- The unit / controllable will follow lead unit of another controllable, wingmens of both controllables will continue following their leaders. --- The unit / controllable will also protect that controllable from threats of specified types. --- @param #CONTROLLABLE self --- @param Controllable#CONTROLLABLE EscortControllable The controllable to be escorted. --- @param DCSTypes#Vec3 PointVec3 Position of the unit / lead unit of the controllable relative lead unit of another controllable in frame reference oriented by course of lead unit of another controllable. If another controllable is on land the unit / controllable will orbit around. --- @param #number LastWaypointIndex Detach waypoint of another controllable. Once reached the unit / controllable Follow task is finished. --- @param #number EngagementDistanceMax Maximal distance from escorted controllable to threat. If the threat is already engaged by escort escort will disengage if the distance becomes greater than 1.5 * engagementDistMax. --- @param DCSTypes#AttributeNameArray TargetTypes Array of AttributeName that is contains threat categories allowed to engage. --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:TaskEscort( FollowControllable, PointVec3, LastWaypointIndex, EngagementDistance, TargetTypes ) - self:F2( { self.ControllableName, FollowControllable, PointVec3, LastWaypointIndex, EngagementDistance, TargetTypes } ) - --- Escort = { --- id = 'Escort', --- params = { --- controllableId = Controllable.ID, --- pos = Vec3, --- lastWptIndexFlag = boolean, --- lastWptIndex = number, --- engagementDistMax = Distance, --- targetTypes = array of AttributeName, --- } --- } - - local LastWaypointIndexFlag = nil - if LastWaypointIndex then - LastWaypointIndexFlag = true - end - - local DCSTask - DCSTask = { id = 'Follow', - params = { - controllableId = FollowControllable:GetID(), - pos = PointVec3, - lastWptIndexFlag = LastWaypointIndexFlag, - lastWptIndex = LastWaypointIndex, - engagementDistMax = EngagementDistance, - targetTypes = TargetTypes, - }, - }, - - self:T3( { DCSTask } ) - return DCSTask -end - - --- GROUND TASKS - ---- (GROUND) Fire at a VEC2 point until ammunition is finished. --- @param #CONTROLLABLE self --- @param DCSTypes#Vec2 PointVec2 The point to fire at. --- @param DCSTypes#Distance Radius The radius of the zone to deploy the fire at. --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:TaskFireAtPoint( PointVec2, Radius ) - self:F2( { self.ControllableName, PointVec2, Radius } ) - - -- FireAtPoint = { - -- id = 'FireAtPoint', - -- params = { - -- point = Vec2, - -- radius = Distance, - -- } - -- } - - local DCSTask - DCSTask = { id = 'FireAtPoint', - params = { - point = PointVec2, - radius = Radius, - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - ---- (GROUND) Hold ground controllable from moving. --- @param #CONTROLLABLE self --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:TaskHold() - self:F2( { self.ControllableName } ) - --- Hold = { --- id = 'Hold', --- params = { --- } --- } - - local DCSTask - DCSTask = { id = 'Hold', - params = { - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - - --- TASKS FOR AIRBORNE AND GROUND UNITS/CONTROLLABLES - ---- (AIR + GROUND) The task makes the controllable/unit a FAC and orders the FAC to control the target (enemy ground controllable) destruction. --- The killer is player-controlled allied CAS-aircraft that is in contact with the FAC. --- If the task is assigned to the controllable lead unit will be a FAC. --- @param #CONTROLLABLE self --- @param Controllable#CONTROLLABLE AttackGroup Target CONTROLLABLE. --- @param #number WeaponType Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param DCSTypes#AI.Task.Designation Designation (optional) Designation type. --- @param #boolean Datalink (optional) Allows to use datalink to send the target information to attack aircraft. Enabled by default. --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:TaskFAC_AttackGroup( AttackGroup, WeaponType, Designation, Datalink ) - self:F2( { self.ControllableName, AttackGroup, WeaponType, Designation, Datalink } ) - --- FAC_AttackControllable = { --- id = 'FAC_AttackControllable', --- params = { --- controllableId = Controllable.ID, --- weaponType = number, --- designation = enum AI.Task.Designation, --- datalink = boolean --- } --- } - - local DCSTask - DCSTask = { id = 'FAC_AttackControllable', - params = { - controllableId = AttackGroup:GetID(), - weaponType = WeaponType, - designation = Designation, - datalink = Datalink, - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - --- EN-ROUTE TASKS FOR AIRBORNE CONTROLLABLES - ---- (AIR) Engaging targets of defined types. --- @param #CONTROLLABLE self --- @param DCSTypes#Distance Distance Maximal distance from the target to a route leg. If the target is on a greater distance it will be ignored. --- @param DCSTypes#AttributeNameArray TargetTypes Array of target categories allowed to engage. --- @param #number Priority All enroute tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:EnRouteTaskEngageTargets( Distance, TargetTypes, Priority ) - self:F2( { self.ControllableName, Distance, TargetTypes, Priority } ) - --- EngageTargets ={ --- id = 'EngageTargets', --- params = { --- maxDist = Distance, --- targetTypes = array of AttributeName, --- priority = number --- } --- } - - local DCSTask - DCSTask = { id = 'EngageTargets', - params = { - maxDist = Distance, - targetTypes = TargetTypes, - priority = Priority - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - - - ---- (AIR) Engaging a targets of defined types at circle-shaped zone. --- @param #CONTROLLABLE self --- @param DCSTypes#Vec2 PointVec2 2D-coordinates of the zone. --- @param DCSTypes#Distance Radius Radius of the zone. --- @param DCSTypes#AttributeNameArray TargetTypes Array of target categories allowed to engage. --- @param #number Priority All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:EnRouteTaskEngageTargets( PointVec2, Radius, TargetTypes, Priority ) - self:F2( { self.ControllableName, PointVec2, Radius, TargetTypes, Priority } ) - --- EngageTargetsInZone = { --- id = 'EngageTargetsInZone', --- params = { --- point = Vec2, --- zoneRadius = Distance, --- targetTypes = array of AttributeName, --- priority = number --- } --- } - - local DCSTask - DCSTask = { id = 'EngageTargetsInZone', - params = { - point = PointVec2, - zoneRadius = Radius, - targetTypes = TargetTypes, - priority = Priority - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - - ---- (AIR) Engaging a controllable. The task does not assign the target controllable to the unit/controllable to attack now; it just allows the unit/controllable to engage the target controllable as well as other assigned targets. --- @param #CONTROLLABLE self --- @param Controllable#CONTROLLABLE AttackGroup The Controllable to be attacked. --- @param #number Priority All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. --- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. --- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. --- @param DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. --- @param DCSTypes#Distance Altitude (optional) Desired attack start altitude. Controllable/aircraft will make its attacks from the altitude. If the altitude is too low or too high to use weapon aircraft/controllable will choose closest altitude to the desired attack start altitude. If the desired altitude is defined controllable/aircraft will not attack from safe altitude. --- @param #boolean AttackQtyLimit (optional) The flag determines how to interpret attackQty parameter. If the flag is true then attackQty is a limit on maximal attack quantity for "AttackControllable" and "AttackUnit" tasks. If the flag is false then attackQty is a desired attack quantity for "Bombing" and "BombingRunway" tasks. --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:EnRouteTaskEngageGroup( AttackGroup, Priority, WeaponType, WeaponExpend, AttackQty, Direction, Altitude, AttackQtyLimit ) - self:F2( { self.ControllableName, AttackGroup, Priority, WeaponType, WeaponExpend, AttackQty, Direction, Altitude, AttackQtyLimit } ) - - -- EngageControllable = { - -- id = 'EngageControllable ', - -- params = { - -- controllableId = Controllable.ID, - -- weaponType = number, - -- expend = enum AI.Task.WeaponExpend, - -- attackQty = number, - -- directionEnabled = boolean, - -- direction = Azimuth, - -- altitudeEnabled = boolean, - -- altitude = Distance, - -- attackQtyLimit = boolean, - -- priority = number, - -- } - -- } - - local DirectionEnabled = nil - if Direction then - DirectionEnabled = true - end - - local AltitudeEnabled = nil - if Altitude then - AltitudeEnabled = true - end - - local DCSTask - DCSTask = { id = 'EngageControllable', - params = { - controllableId = AttackGroup:GetID(), - weaponType = WeaponType, - expend = WeaponExpend, - attackQty = AttackQty, - directionEnabled = DirectionEnabled, - direction = Direction, - altitudeEnabled = AltitudeEnabled, - altitude = Altitude, - attackQtyLimit = AttackQtyLimit, - priority = Priority, - }, - }, - - self:T3( { DCSTask } ) - return DCSTask -end - - ---- (AIR) Attack the Unit. --- @param #CONTROLLABLE self --- @param Unit#UNIT AttackUnit The UNIT. --- @param #number Priority All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. --- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param DCSTypes#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. --- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. --- @param DCSTypes#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. --- @param #boolean AttackQtyLimit (optional) The flag determines how to interpret attackQty parameter. If the flag is true then attackQty is a limit on maximal attack quantity for "AttackControllable" and "AttackUnit" tasks. If the flag is false then attackQty is a desired attack quantity for "Bombing" and "BombingRunway" tasks. --- @param #boolean ControllableAttack (optional) Flag indicates that the target must be engaged by all aircrafts of the controllable. Has effect only if the task is assigned to a controllable, not to a single aircraft. --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:EnRouteTaskEngageUnit( AttackUnit, Priority, WeaponType, WeaponExpend, AttackQty, Direction, AttackQtyLimit, ControllableAttack ) - self:F2( { self.ControllableName, AttackUnit, Priority, WeaponType, WeaponExpend, AttackQty, Direction, AttackQtyLimit, ControllableAttack } ) - - -- EngageUnit = { - -- id = 'EngageUnit', - -- params = { - -- unitId = Unit.ID, - -- weaponType = number, - -- expend = enum AI.Task.WeaponExpend - -- attackQty = number, - -- direction = Azimuth, - -- attackQtyLimit = boolean, - -- controllableAttack = boolean, - -- priority = number, - -- } - -- } - - local DCSTask - DCSTask = { id = 'EngageUnit', - params = { - unitId = AttackUnit:GetID(), - weaponType = WeaponType, - expend = WeaponExpend, - attackQty = AttackQty, - direction = Direction, - attackQtyLimit = AttackQtyLimit, - controllableAttack = ControllableAttack, - priority = Priority, - }, - }, - - self:T3( { DCSTask } ) - return DCSTask -end - - - ---- (AIR) Aircraft will act as an AWACS for friendly units (will provide them with information about contacts). No parameters. --- @param #CONTROLLABLE self --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:EnRouteTaskAWACS( ) - self:F2( { self.ControllableName } ) - --- AWACS = { --- id = 'AWACS', --- params = { --- } --- } - - local DCSTask - DCSTask = { id = 'AWACS', - params = { - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - - ---- (AIR) Aircraft will act as a tanker for friendly units. No parameters. --- @param #CONTROLLABLE self --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:EnRouteTaskTanker( ) - self:F2( { self.ControllableName } ) - --- Tanker = { --- id = 'Tanker', --- params = { --- } --- } - - local DCSTask - DCSTask = { id = 'Tanker', - params = { - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - - --- En-route tasks for ground units/controllables - ---- (GROUND) Ground unit (EW-radar) will act as an EWR for friendly units (will provide them with information about contacts). No parameters. --- @param #CONTROLLABLE self --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:EnRouteTaskEWR( ) - self:F2( { self.ControllableName } ) - --- EWR = { --- id = 'EWR', --- params = { --- } --- } - - local DCSTask - DCSTask = { id = 'EWR', - params = { - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - - --- En-route tasks for airborne and ground units/controllables - ---- (AIR + GROUND) The task makes the controllable/unit a FAC and lets the FAC to choose the target (enemy ground controllable) as well as other assigned targets. --- The killer is player-controlled allied CAS-aircraft that is in contact with the FAC. --- If the task is assigned to the controllable lead unit will be a FAC. --- @param #CONTROLLABLE self --- @param Controllable#CONTROLLABLE AttackGroup Target CONTROLLABLE. --- @param #number Priority All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. --- @param #number WeaponType Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. --- @param DCSTypes#AI.Task.Designation Designation (optional) Designation type. --- @param #boolean Datalink (optional) Allows to use datalink to send the target information to attack aircraft. Enabled by default. --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:EnRouteTaskFAC_EngageGroup( AttackGroup, Priority, WeaponType, Designation, Datalink ) - self:F2( { self.ControllableName, AttackGroup, WeaponType, Priority, Designation, Datalink } ) - --- FAC_EngageControllable = { --- id = 'FAC_EngageControllable', --- params = { --- controllableId = Controllable.ID, --- weaponType = number, --- designation = enum AI.Task.Designation, --- datalink = boolean, --- priority = number, --- } --- } - - local DCSTask - DCSTask = { id = 'FAC_EngageControllable', - params = { - controllableId = AttackGroup:GetID(), - weaponType = WeaponType, - designation = Designation, - datalink = Datalink, - priority = Priority, - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - - ---- (AIR + GROUND) The task makes the controllable/unit a FAC and lets the FAC to choose a targets (enemy ground controllable) around as well as other assigned targets. --- The killer is player-controlled allied CAS-aircraft that is in contact with the FAC. --- If the task is assigned to the controllable lead unit will be a FAC. --- @param #CONTROLLABLE self --- @param DCSTypes#Distance Radius The maximal distance from the FAC to a target. --- @param #number Priority All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:EnRouteTaskFAC( Radius, Priority ) - self:F2( { self.ControllableName, Radius, Priority } ) - --- FAC = { --- id = 'FAC', --- params = { --- radius = Distance, --- priority = number --- } --- } - - local DCSTask - DCSTask = { id = 'FAC', - params = { - radius = Radius, - priority = Priority - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - - - - ---- (AIR) Move the controllable to a Vec2 Point, wait for a defined duration and embark a controllable. --- @param #CONTROLLABLE self --- @param DCSTypes#Vec2 Point The point where to wait. --- @param #number Duration The duration in seconds to wait. --- @param #CONTROLLABLE EmbarkingControllable The controllable to be embarked. --- @return DCSTask#Task The DCS task structure -function CONTROLLABLE:TaskEmbarking( Point, Duration, EmbarkingControllable ) - self:F2( { self.ControllableName, Point, Duration, EmbarkingControllable.DCSControllable } ) - - local DCSTask - DCSTask = { id = 'Embarking', - params = { x = Point.x, - y = Point.y, - duration = Duration, - controllablesForEmbarking = { EmbarkingControllable.ControllableID }, - durationFlag = true, - distributionFlag = false, - distribution = {}, - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - ---- (GROUND) Embark to a Transport landed at a location. - ---- Move to a defined Vec2 Point, and embark to a controllable when arrived within a defined Radius. --- @param #CONTROLLABLE self --- @param DCSTypes#Vec2 Point The point where to wait. --- @param #number Radius The radius of the embarking zone around the Point. --- @return DCSTask#Task The DCS task structure. -function CONTROLLABLE:TaskEmbarkToTransport( Point, Radius ) - self:F2( { self.ControllableName, Point, Radius } ) - - local DCSTask --DCSTask#Task - DCSTask = { id = 'EmbarkToTransport', - params = { x = Point.x, - y = Point.y, - zoneRadius = Radius, - } - } - - self:T3( { DCSTask } ) - return DCSTask -end - - - ---- (AIR + GROUND) Return a mission task from a mission template. --- @param #CONTROLLABLE self --- @param #table TaskMission A table containing the mission task. --- @return DCSTask#Task -function CONTROLLABLE:TaskMission( TaskMission ) - self:F2( Points ) - - local DCSTask - DCSTask = { id = 'Mission', params = { TaskMission, }, } - - self:T3( { DCSTask } ) - return DCSTask -end - ---- Return a Misson task to follow a given route defined by Points. --- @param #CONTROLLABLE self --- @param #table Points A table of route points. --- @return DCSTask#Task -function CONTROLLABLE:TaskRoute( Points ) - self:F2( Points ) - - local DCSTask - DCSTask = { id = 'Mission', params = { route = { points = Points, }, }, } - - self:T3( { DCSTask } ) - return DCSTask -end - ---- (AIR + GROUND) Make the Controllable move to fly to a given point. --- @param #CONTROLLABLE self --- @param DCSTypes#Vec3 Point The destination point in Vec3 format. --- @param #number Speed The speed to travel. --- @return #CONTROLLABLE self -function CONTROLLABLE:TaskRouteToVec2( Point, Speed ) - self:F2( { Point, Speed } ) - - local ControllablePoint = self:GetUnit( 1 ):GetVec2() - - local PointFrom = {} - PointFrom.x = ControllablePoint.x - PointFrom.y = ControllablePoint.y - PointFrom.type = "Turning Point" - PointFrom.action = "Turning Point" - PointFrom.speed = Speed - PointFrom.speed_locked = true - PointFrom.properties = { - ["vnav"] = 1, - ["scale"] = 0, - ["angle"] = 0, - ["vangle"] = 0, - ["steer"] = 2, - } - - - local PointTo = {} - PointTo.x = Point.x - PointTo.y = Point.y - PointTo.type = "Turning Point" - PointTo.action = "Fly Over Point" - PointTo.speed = Speed - PointTo.speed_locked = true - PointTo.properties = { - ["vnav"] = 1, - ["scale"] = 0, - ["angle"] = 0, - ["vangle"] = 0, - ["steer"] = 2, - } - - - local Points = { PointFrom, PointTo } - - self:T3( Points ) - - self:Route( Points ) - - return self -end - ---- (AIR + GROUND) Make the Controllable move to a given point. --- @param #CONTROLLABLE self --- @param DCSTypes#Vec3 Point The destination point in Vec3 format. --- @param #number Speed The speed to travel. --- @return #CONTROLLABLE self -function CONTROLLABLE:TaskRouteToVec3( Point, Speed ) - self:F2( { Point, Speed } ) - - local ControllablePoint = self:GetUnit( 1 ):GetPointVec3() - - local PointFrom = {} - PointFrom.x = ControllablePoint.x - PointFrom.y = ControllablePoint.z - PointFrom.alt = ControllablePoint.y - PointFrom.alt_type = "BARO" - PointFrom.type = "Turning Point" - PointFrom.action = "Turning Point" - PointFrom.speed = Speed - PointFrom.speed_locked = true - PointFrom.properties = { - ["vnav"] = 1, - ["scale"] = 0, - ["angle"] = 0, - ["vangle"] = 0, - ["steer"] = 2, - } - - - local PointTo = {} - PointTo.x = Point.x - PointTo.y = Point.z - PointTo.alt = Point.y - PointTo.alt_type = "BARO" - PointTo.type = "Turning Point" - PointTo.action = "Fly Over Point" - PointTo.speed = Speed - PointTo.speed_locked = true - PointTo.properties = { - ["vnav"] = 1, - ["scale"] = 0, - ["angle"] = 0, - ["vangle"] = 0, - ["steer"] = 2, - } - - - local Points = { PointFrom, PointTo } - - self:T3( Points ) - - self:Route( Points ) - - return self -end - - - ---- Make the controllable to follow a given route. --- @param #CONTROLLABLE self --- @param #table GoPoints A table of Route Points. --- @return #CONTROLLABLE self -function CONTROLLABLE:Route( GoPoints ) - self:F2( GoPoints ) - - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - local Points = routines.utils.deepCopy( GoPoints ) - local MissionTask = { id = 'Mission', params = { route = { points = Points, }, }, } - local Controller = self:_GetController() - --Controller.setTask( Controller, MissionTask ) - SCHEDULER:New( Controller, Controller.setTask, { MissionTask }, 1 ) - return self - end - - return nil -end - - - ---- (AIR + GROUND) Route the controllable to a given zone. --- The controllable final destination point can be randomized. --- A speed can be given in km/h. --- A given formation can be given. --- @param #CONTROLLABLE self --- @param Zone#ZONE Zone The zone where to route to. --- @param #boolean Randomize Defines whether to target point gets randomized within the Zone. --- @param #number Speed The speed. --- @param Base#FORMATION Formation The formation string. -function CONTROLLABLE:TaskRouteToZone( Zone, Randomize, Speed, Formation ) - self:F2( Zone ) - - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - - local ControllablePoint = self:GetVec2() - - local PointFrom = {} - PointFrom.x = ControllablePoint.x - PointFrom.y = ControllablePoint.y - PointFrom.type = "Turning Point" - PointFrom.action = "Cone" - PointFrom.speed = 20 / 1.6 - - - local PointTo = {} - local ZonePoint - - if Randomize then - ZonePoint = Zone:GetRandomVec2() - else - ZonePoint = Zone:GetVec2() - end - - PointTo.x = ZonePoint.x - PointTo.y = ZonePoint.y - PointTo.type = "Turning Point" - - if Formation then - PointTo.action = Formation - else - PointTo.action = "Cone" - end - - if Speed then - PointTo.speed = Speed - else - PointTo.speed = 20 / 1.6 - end - - local Points = { PointFrom, PointTo } - - self:T3( Points ) - - self:Route( Points ) - - return self - end - - return nil -end - ---- (AIR) Return the Controllable to an @{Airbase#AIRBASE} --- A speed can be given in km/h. --- A given formation can be given. --- @param #CONTROLLABLE self --- @param Airbase#AIRBASE ReturnAirbase The @{Airbase#AIRBASE} to return to. --- @param #number Speed (optional) The speed. --- @return #string The route -function CONTROLLABLE:RouteReturnToAirbase( ReturnAirbase, Speed ) - self:F2( { ReturnAirbase, Speed } ) - --- Example --- [4] = --- { --- ["alt"] = 45, --- ["type"] = "Land", --- ["action"] = "Landing", --- ["alt_type"] = "BARO", --- ["formation_template"] = "", --- ["properties"] = --- { --- ["vnav"] = 1, --- ["scale"] = 0, --- ["angle"] = 0, --- ["vangle"] = 0, --- ["steer"] = 2, --- }, -- end of ["properties"] --- ["ETA"] = 527.81058817743, --- ["airdromeId"] = 12, --- ["y"] = 243127.2973737, --- ["x"] = -5406.2803440839, --- ["name"] = "DictKey_WptName_53", --- ["speed"] = 138.88888888889, --- ["ETA_locked"] = false, --- ["task"] = --- { --- ["id"] = "ComboTask", --- ["params"] = --- { --- ["tasks"] = --- { --- }, -- end of ["tasks"] --- }, -- end of ["params"] --- }, -- end of ["task"] --- ["speed_locked"] = true, --- }, -- end of [4] - - - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - - local ControllablePoint = self:GetVec2() - local ControllableVelocity = self:GetMaxVelocity() - - local PointFrom = {} - PointFrom.x = ControllablePoint.x - PointFrom.y = ControllablePoint.y - PointFrom.type = "Turning Point" - PointFrom.action = "Turning Point" - PointFrom.speed = ControllableVelocity - - - local PointTo = {} - local AirbasePoint = ReturnAirbase:GetVec2() - - PointTo.x = AirbasePoint.x - PointTo.y = AirbasePoint.y - PointTo.type = "Land" - PointTo.action = "Landing" - PointTo.airdromeId = ReturnAirbase:GetID()-- Airdrome ID - self:T(PointTo.airdromeId) - --PointTo.alt = 0 - - local Points = { PointFrom, PointTo } - - self:T3( Points ) - - local Route = { points = Points, } - - return Route - end - - return nil -end - --- Commands - ---- Do Script command --- @param #CONTROLLABLE self --- @param #string DoScript --- @return #DCSCommand -function CONTROLLABLE:CommandDoScript( DoScript ) - - local DCSDoScript = { - id = "Script", - params = { - command = DoScript, - }, - } - - self:T3( DCSDoScript ) - return DCSDoScript -end - - ---- Return the mission template of the controllable. --- @param #CONTROLLABLE self --- @return #table The MissionTemplate --- TODO: Rework the method how to retrieve a template ... -function CONTROLLABLE:GetTaskMission() - self:F2( self.ControllableName ) - - return routines.utils.deepCopy( _DATABASE.Templates.Controllables[self.ControllableName].Template ) -end - ---- Return the mission route of the controllable. --- @param #CONTROLLABLE self --- @return #table The mission route defined by points. -function CONTROLLABLE:GetTaskRoute() - self:F2( self.ControllableName ) - - return routines.utils.deepCopy( _DATABASE.Templates.Controllables[self.ControllableName].Template.route.points ) -end - ---- Return the route of a controllable by using the @{Database#DATABASE} class. --- @param #CONTROLLABLE self --- @param #number Begin The route point from where the copy will start. The base route point is 0. --- @param #number End The route point where the copy will end. The End point is the last point - the End point. The last point has base 0. --- @param #boolean Randomize Randomization of the route, when true. --- @param #number Radius When randomization is on, the randomization is within the radius. -function CONTROLLABLE:CopyRoute( Begin, End, Randomize, Radius ) - self:F2( { Begin, End } ) - - local Points = {} - - -- Could be a Spawned Controllable - local ControllableName = string.match( self:GetName(), ".*#" ) - if ControllableName then - ControllableName = ControllableName:sub( 1, -2 ) - else - ControllableName = self:GetName() - end - - self:T3( { ControllableName } ) - - local Template = _DATABASE.Templates.Controllables[ControllableName].Template - - if Template then - if not Begin then - Begin = 0 - end - if not End then - End = 0 - end - - for TPointID = Begin + 1, #Template.route.points - End do - if Template.route.points[TPointID] then - Points[#Points+1] = routines.utils.deepCopy( Template.route.points[TPointID] ) - if Randomize then - if not Radius then - Radius = 500 - end - Points[#Points].x = Points[#Points].x + math.random( Radius * -1, Radius ) - Points[#Points].y = Points[#Points].y + math.random( Radius * -1, Radius ) - end - end - end - return Points - else - error( "Template not found for Controllable : " .. ControllableName ) - end - - return nil -end - - ---- Return the detected targets of the controllable. --- The optional parametes specify the detection methods that can be applied. --- If no detection method is given, the detection will use all the available methods by default. --- @param Controllable#CONTROLLABLE self --- @param #boolean DetectVisual (optional) --- @param #boolean DetectOptical (optional) --- @param #boolean DetectRadar (optional) --- @param #boolean DetectIRST (optional) --- @param #boolean DetectRWR (optional) --- @param #boolean DetectDLINK (optional) --- @return #table DetectedTargets -function CONTROLLABLE:GetDetectedTargets( DetectVisual, DetectOptical, DetectRadar, DetectIRST, DetectRWR, DetectDLINK ) - self:F2( self.ControllableName ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - local DetectionVisual = ( DetectVisual and DetectVisual == true ) and Controller.Detection.VISUAL or nil - local DetectionOptical = ( DetectOptical and DetectOptical == true ) and Controller.Detection.OPTICAL or nil - local DetectionRadar = ( DetectRadar and DetectRadar == true ) and Controller.Detection.RADAR or nil - local DetectionIRST = ( DetectIRST and DetectIRST == true ) and Controller.Detection.IRST or nil - local DetectionRWR = ( DetectRWR and DetectRWR == true ) and Controller.Detection.RWR or nil - local DetectionDLINK = ( DetectDLINK and DetectDLINK == true ) and Controller.Detection.DLINK or nil - - - return self:_GetController():getDetectedTargets( DetectionVisual, DetectionOptical, DetectionRadar, DetectionIRST, DetectionRWR, DetectionDLINK ) - end - - return nil -end - -function CONTROLLABLE:IsTargetDetected( DCSObject ) - self:F2( self.ControllableName ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - - local TargetIsDetected, TargetIsVisible, TargetLastTime, TargetKnowType, TargetKnowDistance, TargetLastPos, TargetLastVelocity - = self:_GetController().isTargetDetected( self:_GetController(), DCSObject, - Controller.Detection.VISUAL, - Controller.Detection.OPTIC, - Controller.Detection.RADAR, - Controller.Detection.IRST, - Controller.Detection.RWR, - Controller.Detection.DLINK - ) - return TargetIsDetected, TargetIsVisible, TargetLastTime, TargetKnowType, TargetKnowDistance, TargetLastPos, TargetLastVelocity - end - - return nil -end - --- Options - ---- Can the CONTROLLABLE hold their weapons? --- @param #CONTROLLABLE self --- @return #boolean -function CONTROLLABLE:OptionROEHoldFirePossible() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - if self:IsAir() or self:IsGround() or self:IsShip() then - return true - end - - return false - end - - return nil -end - ---- Holding weapons. --- @param Controllable#CONTROLLABLE self --- @return Controllable#CONTROLLABLE self -function CONTROLLABLE:OptionROEHoldFire() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - local Controller = self:_GetController() - - if self:IsAir() then - Controller:setOption( AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.WEAPON_HOLD ) - elseif self:IsGround() then - Controller:setOption( AI.Option.Ground.id.ROE, AI.Option.Ground.val.ROE.WEAPON_HOLD ) - elseif self:IsShip() then - Controller:setOption( AI.Option.Naval.id.ROE, AI.Option.Naval.val.ROE.WEAPON_HOLD ) - end - - return self - end - - return nil -end - ---- Can the CONTROLLABLE attack returning on enemy fire? --- @param #CONTROLLABLE self --- @return #boolean -function CONTROLLABLE:OptionROEReturnFirePossible() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - if self:IsAir() or self:IsGround() or self:IsShip() then - return true - end - - return false - end - - return nil -end - ---- Return fire. --- @param #CONTROLLABLE self --- @return #CONTROLLABLE self -function CONTROLLABLE:OptionROEReturnFire() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - local Controller = self:_GetController() - - if self:IsAir() then - Controller:setOption( AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.RETURN_FIRE ) - elseif self:IsGround() then - Controller:setOption( AI.Option.Ground.id.ROE, AI.Option.Ground.val.ROE.RETURN_FIRE ) - elseif self:IsShip() then - Controller:setOption( AI.Option.Naval.id.ROE, AI.Option.Naval.val.ROE.RETURN_FIRE ) - end - - return self - end - - return nil -end - ---- Can the CONTROLLABLE attack designated targets? --- @param #CONTROLLABLE self --- @return #boolean -function CONTROLLABLE:OptionROEOpenFirePossible() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - if self:IsAir() or self:IsGround() or self:IsShip() then - return true - end - - return false - end - - return nil -end - ---- Openfire. --- @param #CONTROLLABLE self --- @return #CONTROLLABLE self -function CONTROLLABLE:OptionROEOpenFire() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - local Controller = self:_GetController() - - if self:IsAir() then - Controller:setOption( AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.OPEN_FIRE ) - elseif self:IsGround() then - Controller:setOption( AI.Option.Ground.id.ROE, AI.Option.Ground.val.ROE.OPEN_FIRE ) - elseif self:IsShip() then - Controller:setOption( AI.Option.Naval.id.ROE, AI.Option.Naval.val.ROE.OPEN_FIRE ) - end - - return self - end - - return nil -end - ---- Can the CONTROLLABLE attack targets of opportunity? --- @param #CONTROLLABLE self --- @return #boolean -function CONTROLLABLE:OptionROEWeaponFreePossible() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - if self:IsAir() then - return true - end - - return false - end - - return nil -end - ---- Weapon free. --- @param #CONTROLLABLE self --- @return #CONTROLLABLE self -function CONTROLLABLE:OptionROEWeaponFree() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - local Controller = self:_GetController() - - if self:IsAir() then - Controller:setOption( AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.WEAPON_FREE ) - end - - return self - end - - return nil -end - ---- Can the CONTROLLABLE ignore enemy fire? --- @param #CONTROLLABLE self --- @return #boolean -function CONTROLLABLE:OptionROTNoReactionPossible() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - if self:IsAir() then - return true - end - - return false - end - - return nil -end - - ---- No evasion on enemy threats. --- @param #CONTROLLABLE self --- @return #CONTROLLABLE self -function CONTROLLABLE:OptionROTNoReaction() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - local Controller = self:_GetController() - - if self:IsAir() then - Controller:setOption( AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.NO_REACTION ) - end - - return self - end - - return nil -end - ---- Can the CONTROLLABLE evade using passive defenses? --- @param #CONTROLLABLE self --- @return #boolean -function CONTROLLABLE:OptionROTPassiveDefensePossible() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - if self:IsAir() then - return true - end - - return false - end - - return nil -end - ---- Evasion passive defense. --- @param #CONTROLLABLE self --- @return #CONTROLLABLE self -function CONTROLLABLE:OptionROTPassiveDefense() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - local Controller = self:_GetController() - - if self:IsAir() then - Controller:setOption( AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.PASSIVE_DEFENCE ) - end - - return self - end - - return nil -end - ---- Can the CONTROLLABLE evade on enemy fire? --- @param #CONTROLLABLE self --- @return #boolean -function CONTROLLABLE:OptionROTEvadeFirePossible() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - if self:IsAir() then - return true - end - - return false - end - - return nil -end - - ---- Evade on fire. --- @param #CONTROLLABLE self --- @return #CONTROLLABLE self -function CONTROLLABLE:OptionROTEvadeFire() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - local Controller = self:_GetController() - - if self:IsAir() then - Controller:setOption( AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.EVADE_FIRE ) - end - - return self - end - - return nil -end - ---- Can the CONTROLLABLE evade on fire using vertical manoeuvres? --- @param #CONTROLLABLE self --- @return #boolean -function CONTROLLABLE:OptionROTVerticalPossible() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - if self:IsAir() then - return true - end - - return false - end - - return nil -end - - ---- Evade on fire using vertical manoeuvres. --- @param #CONTROLLABLE self --- @return #CONTROLLABLE self -function CONTROLLABLE:OptionROTVertical() - self:F2( { self.ControllableName } ) - - local DCSControllable = self:GetDCSObject() - if DCSControllable then - local Controller = self:_GetController() - - if self:IsAir() then - Controller:setOption( AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.BYPASS_AND_ESCAPE ) - end - - return self - end - - return nil -end - ---- Retrieve the controllable mission and allow to place function hooks within the mission waypoint plan. --- Use the method @{Controllable#CONTROLLABLE:WayPointFunction} to define the hook functions for specific waypoints. --- Use the method @{Controllable@CONTROLLABLE:WayPointExecute) to start the execution of the new mission plan. --- Note that when WayPointInitialize is called, the Mission of the controllable is RESTARTED! --- @param #CONTROLLABLE self --- @param #table WayPoints If WayPoints is given, then use the route. --- @return #CONTROLLABLE -function CONTROLLABLE:WayPointInitialize( WayPoints ) - self:F( { WayPoint, WayPointIndex, WayPointFunction } ) - - if WayPoints then - self.WayPoints = WayPoints - else - self.WayPoints = self:GetTaskRoute() - end - - return self -end - - ---- Registers a waypoint function that will be executed when the controllable moves over the WayPoint. --- @param #CONTROLLABLE self --- @param #number WayPoint The waypoint number. Note that the start waypoint on the route is WayPoint 1! --- @param #number WayPointIndex When defining multiple WayPoint functions for one WayPoint, use WayPointIndex to set the sequence of actions. --- @param #function WayPointFunction The waypoint function to be called when the controllable moves over the waypoint. The waypoint function takes variable parameters. --- @return #CONTROLLABLE -function CONTROLLABLE:WayPointFunction( WayPoint, WayPointIndex, WayPointFunction, ... ) - self:F2( { WayPoint, WayPointIndex, WayPointFunction } ) - - table.insert( self.WayPoints[WayPoint].task.params.tasks, WayPointIndex ) - self.WayPoints[WayPoint].task.params.tasks[WayPointIndex] = self:TaskFunction( WayPoint, WayPointIndex, WayPointFunction, arg ) - return self -end - - -function CONTROLLABLE:TaskFunction( WayPoint, WayPointIndex, FunctionString, FunctionArguments ) - self:F2( { WayPoint, WayPointIndex, FunctionString, FunctionArguments } ) - - local DCSTask - - local DCSScript = {} - DCSScript[#DCSScript+1] = "local MissionControllable = CONTROLLABLE:Find( ... ) " - - if FunctionArguments and #FunctionArguments > 0 then - DCSScript[#DCSScript+1] = FunctionString .. "( MissionControllable, " .. table.concat( FunctionArguments, "," ) .. ")" - else - DCSScript[#DCSScript+1] = FunctionString .. "( MissionControllable )" - end - - DCSTask = self:TaskWrappedAction( - self:CommandDoScript( - table.concat( DCSScript ) - ), WayPointIndex - ) - - self:T3( DCSTask ) - - return DCSTask - -end - ---- Executes the WayPoint plan. --- The function gets a WayPoint parameter, that you can use to restart the mission at a specific WayPoint. --- Note that when the WayPoint parameter is used, the new start mission waypoint of the controllable will be 1! --- @param #CONTROLLABLE self --- @param #number WayPoint The WayPoint from where to execute the mission. --- @param #number WaitTime The amount seconds to wait before initiating the mission. --- @return #CONTROLLABLE -function CONTROLLABLE:WayPointExecute( WayPoint, WaitTime ) - self:F( { WayPoint, WaitTime } ) - - if not WayPoint then - WayPoint = 1 - end - - -- When starting the mission from a certain point, the TaskPoints need to be deleted before the given WayPoint. - for TaskPointID = 1, WayPoint - 1 do - table.remove( self.WayPoints, 1 ) - end - - self:T3( self.WayPoints ) - - self:SetTask( self:TaskRoute( self.WayPoints ), WaitTime ) - - return self -end - --- Message APIs - ---- Returns a message with the callsign embedded (if there is one). --- @param #CONTROLLABLE self --- @param #string Message The message text --- @param DCSTypes#Duration Duration The duration of the message. --- @return Message#MESSAGE -function CONTROLLABLE:GetMessage( Message, Duration ) - self:E( { Message, Duration } ) - - local DCSObject = self:GetDCSObject() - if DCSObject then - return MESSAGE:New( Message, Duration, self:GetCallsign() .. " (" .. self:GetTypeName() .. ")" ) - end - - return nil -end - ---- Send a message to all coalitions. --- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. --- @param #CONTROLLABLE self --- @param #string Message The message text --- @param DCSTypes#Duration Duration The duration of the message. -function CONTROLLABLE:MessageToAll( Message, Duration ) - self:F2( { Message, Duration } ) - - local DCSObject = self:GetDCSObject() - if DCSObject then - self:GetMessage( Message, Duration ):ToAll() - end - - return nil -end - ---- Send a message to the red coalition. --- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. --- @param #CONTROLLABLE self --- @param #string Message The message text --- @param DCSTYpes#Duration Duration The duration of the message. -function CONTROLLABLE:MessageToRed( Message, Duration ) - self:F2( { Message, Duration } ) - - local DCSObject = self:GetDCSObject() - if DCSObject then - self:GetMessage( Message, Duration ):ToRed() - end - - return nil -end - ---- Send a message to the blue coalition. --- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. --- @param #CONTROLLABLE self --- @param #string Message The message text --- @param DCSTypes#Duration Duration The duration of the message. -function CONTROLLABLE:MessageToBlue( Message, Duration ) - self:F2( { Message, Duration } ) - - local DCSObject = self:GetDCSObject() - if DCSObject then - self:GetMessage( Message, Duration ):ToBlue() - end - - return nil -end - ---- Send a message to a client. --- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. --- @param #CONTROLLABLE self --- @param #string Message The message text --- @param DCSTypes#Duration Duration The duration of the message. --- @param Client#CLIENT Client The client object receiving the message. -function CONTROLLABLE:MessageToClient( Message, Duration, Client ) - self:F2( { Message, Duration } ) - - local DCSObject = self:GetDCSObject() - if DCSObject then - self:GetMessage( Message, Duration ):ToClient( Client ) - end - - return nil -end - ---- Send a message to a @{Group}. --- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. --- @param #CONTROLLABLE self --- @param #string Message The message text --- @param DCSTypes#Duration Duration The duration of the message. --- @param Group#GROUP MessageGroup The GROUP object receiving the message. -function CONTROLLABLE:MessageToGroup( Message, Duration, MessageGroup ) - self:F2( { Message, Duration } ) - - local DCSObject = self:GetDCSObject() - if DCSObject then - if DCSObject:isExist() then - self:GetMessage( Message, Duration ):ToGroup( MessageGroup ) - end - end - - return nil -end - ---- Send a message to the players in the @{Group}. --- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. --- @param #CONTROLLABLE self --- @param #string Message The message text --- @param DCSTypes#Duration Duration The duration of the message. -function CONTROLLABLE:Message( Message, Duration ) - self:F2( { Message, Duration } ) - - local DCSObject = self:GetDCSObject() - if DCSObject then - self:GetMessage( Message, Duration ):ToGroup( self ) - end - - return nil -end - ---- This module contains the SCHEDULER class. --- --- 1) @{Scheduler#SCHEDULER} class, extends @{Base#BASE} --- ===================================================== --- The @{Scheduler#SCHEDULER} class models time events calling given event handling functions. --- --- 1.1) SCHEDULER constructor --- -------------------------- --- The SCHEDULER class is quite easy to use: --- --- * @{Scheduler#SCHEDULER.New}: Setup a new scheduler and start it with the specified parameters. --- --- 1.2) SCHEDULER timer stop and start --- ----------------------------------- --- The SCHEDULER can be stopped and restarted with the following methods: --- --- * @{Scheduler#SCHEDULER.Start}: (Re-)Start the scheduler. --- * @{Scheduler#SCHEDULER.Stop}: Stop the scheduler. --- --- @module Scheduler --- @author FlightControl - - ---- The SCHEDULER class --- @type SCHEDULER --- @field #number ScheduleID the ID of the scheduler. --- @extends Base#BASE -SCHEDULER = { - ClassName = "SCHEDULER", -} - ---- SCHEDULER constructor. --- @param #SCHEDULER self --- @param #table TimeEventObject Specified for which Moose object the timer is setup. If a value of nil is provided, a scheduler will be setup without an object reference. --- @param #function TimeEventFunction The event function to be called when a timer event occurs. The event function needs to accept the parameters specified in TimeEventFunctionArguments. --- @param #table TimeEventFunctionArguments Optional arguments that can be given as part of scheduler. The arguments need to be given as a table { param1, param 2, ... }. --- @param #number StartSeconds Specifies the amount of seconds that will be waited before the scheduling is started, and the event function is called. --- @param #number RepeatSecondsInterval Specifies the interval in seconds when the scheduler will call the event function. --- @param #number RandomizationFactor Specifies a randomization factor between 0 and 1 to randomize the RepeatSecondsInterval. --- @param #number StopSeconds Specifies the amount of seconds when the scheduler will be stopped. --- @return #SCHEDULER self -function SCHEDULER:New( TimeEventObject, TimeEventFunction, TimeEventFunctionArguments, StartSeconds, RepeatSecondsInterval, RandomizationFactor, StopSeconds ) - local self = BASE:Inherit( self, BASE:New() ) - self:F2( { TimeEventObject, TimeEventFunction, TimeEventFunctionArguments, StartSeconds, RepeatSecondsInterval, RandomizationFactor, StopSeconds } ) - - self.TimeEventObject = TimeEventObject - self.TimeEventFunction = TimeEventFunction - self.TimeEventFunctionArguments = TimeEventFunctionArguments - self.StartSeconds = StartSeconds - self.Repeat = false - - if RepeatSecondsInterval then - self.RepeatSecondsInterval = RepeatSecondsInterval - else - self.RepeatSecondsInterval = 0 - end - - if RandomizationFactor then - self.RandomizationFactor = RandomizationFactor - else - self.RandomizationFactor = 0 - end - - if StopSeconds then - self.StopSeconds = StopSeconds - end - - - self.StartTime = timer.getTime() - - self:Start() - - return self -end - ---- (Re-)Starts the scheduler. --- @param #SCHEDULER self --- @return #SCHEDULER self -function SCHEDULER:Start() - self:F2( self.TimeEventObject ) - - if self.RepeatSecondsInterval ~= 0 then - self.Repeat = true - end - self.ScheduleID = timer.scheduleFunction( self._Scheduler, self, timer.getTime() + self.StartSeconds + .01 ) - - return self -end - ---- Stops the scheduler. --- @param #SCHEDULER self --- @return #SCHEDULER self -function SCHEDULER:Stop() - self:F2( self.TimeEventObject ) - - self.Repeat = false - if self.ScheduleID then - self:E( "Stop Schedule" ) - timer.removeFunction( self.ScheduleID ) - end - self.ScheduleID = nil - - return self -end - --- Private Functions - ---- @param #SCHEDULER self -function SCHEDULER:_Scheduler() - self:F2( self.TimeEventFunctionArguments ) - - local ErrorHandler = function( errmsg ) - - env.info( "Error in SCHEDULER function:" .. errmsg ) - if debug ~= nil then - env.info( debug.traceback() ) - end - - return errmsg - end - - local Status, Result - if self.TimeEventObject then - Status, Result = xpcall( function() return self.TimeEventFunction( self.TimeEventObject, unpack( self.TimeEventFunctionArguments ) ) end, ErrorHandler ) - else - Status, Result = xpcall( function() return self.TimeEventFunction( unpack( self.TimeEventFunctionArguments ) ) end, ErrorHandler ) - end - - self:T( { self.TimeEventFunctionArguments, Status, Result, self.StartTime, self.RepeatSecondsInterval, self.RandomizationFactor, self.StopSeconds } ) - - if Status and ( ( Result == nil ) or ( Result and Result ~= false ) ) then - if self.Repeat and ( not self.StopSeconds or ( self.StopSeconds and timer.getTime() <= self.StartTime + self.StopSeconds ) ) then - local ScheduleTime = - timer.getTime() + - self.RepeatSecondsInterval + - math.random( - - ( self.RandomizationFactor * self.RepeatSecondsInterval / 2 ), - ( self.RandomizationFactor * self.RepeatSecondsInterval / 2 ) - ) + - 0.01 - self:T( { self.TimeEventFunctionArguments, "Repeat:", timer.getTime(), ScheduleTime } ) - return ScheduleTime -- returns the next time the function needs to be called. - else - timer.removeFunction( self.ScheduleID ) - self.ScheduleID = nil - end - else - timer.removeFunction( self.ScheduleID ) - self.ScheduleID = nil - end - - return nil -end - - - - - - - - - - - - - - - - ---- The EVENT class models an efficient event handling process between other classes and its units, weapons. --- @module Event --- @author FlightControl - ---- The EVENT structure --- @type EVENT --- @field #EVENT.Events Events -EVENT = { - ClassName = "EVENT", - ClassID = 0, -} - -local _EVENTCODES = { - "S_EVENT_SHOT", - "S_EVENT_HIT", - "S_EVENT_TAKEOFF", - "S_EVENT_LAND", - "S_EVENT_CRASH", - "S_EVENT_EJECTION", - "S_EVENT_REFUELING", - "S_EVENT_DEAD", - "S_EVENT_PILOT_DEAD", - "S_EVENT_BASE_CAPTURED", - "S_EVENT_MISSION_START", - "S_EVENT_MISSION_END", - "S_EVENT_TOOK_CONTROL", - "S_EVENT_REFUELING_STOP", - "S_EVENT_BIRTH", - "S_EVENT_HUMAN_FAILURE", - "S_EVENT_ENGINE_STARTUP", - "S_EVENT_ENGINE_SHUTDOWN", - "S_EVENT_PLAYER_ENTER_UNIT", - "S_EVENT_PLAYER_LEAVE_UNIT", - "S_EVENT_PLAYER_COMMENT", - "S_EVENT_SHOOTING_START", - "S_EVENT_SHOOTING_END", - "S_EVENT_MAX", -} - ---- The Event structure --- @type EVENTDATA --- @field id --- @field initiator --- @field target --- @field weapon --- @field IniDCSUnit --- @field IniDCSUnitName --- @field Unit#UNIT IniUnit --- @field #string IniUnitName --- @field IniDCSGroup --- @field IniDCSGroupName --- @field TgtDCSUnit --- @field TgtDCSUnitName --- @field Unit#UNIT TgtUnit --- @field #string TgtUnitName --- @field TgtDCSGroup --- @field TgtDCSGroupName --- @field Weapon --- @field WeaponName --- @field WeaponTgtDCSUnit - ---- The Events structure --- @type EVENT.Events --- @field #number IniUnit - -function EVENT:New() - local self = BASE:Inherit( self, BASE:New() ) - self:F2() - self.EventHandler = world.addEventHandler( self ) - return self -end - -function EVENT:EventText( EventID ) - - local EventText = _EVENTCODES[EventID] - - return EventText -end - - ---- Initializes the Events structure for the event --- @param #EVENT self --- @param DCSWorld#world.event EventID --- @param #string EventClass --- @return #EVENT.Events -function EVENT:Init( EventID, EventClass ) - self:F3( { _EVENTCODES[EventID], EventClass } ) - if not self.Events[EventID] then - self.Events[EventID] = {} - end - if not self.Events[EventID][EventClass] then - self.Events[EventID][EventClass] = {} - end - return self.Events[EventID][EventClass] -end - ---- Removes an Events entry --- @param #EVENT self --- @param Base#BASE EventSelf The self instance of the class for which the event is. --- @param DCSWorld#world.event EventID --- @return #EVENT.Events -function EVENT:Remove( EventSelf, EventID ) - self:F3( { EventSelf, _EVENTCODES[EventID] } ) - - local EventClass = EventSelf:GetClassNameAndID() - self.Events[EventID][EventClass] = nil -end - - ---- Create an OnDead event handler for a group --- @param #EVENT self --- @param #table EventTemplate --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param EventSelf The self instance of the class for which the event is. --- @param #function OnEventFunction --- @return #EVENT -function EVENT:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, OnEventFunction ) - self:F2( EventTemplate.name ) - - for EventUnitID, EventUnit in pairs( EventTemplate.units ) do - OnEventFunction( self, EventUnit.name, EventFunction, EventSelf ) - end - return self -end - ---- Set a new listener for an S_EVENT_X event independent from a unit or a weapon. --- @param #EVENT self --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf The self instance of the class for which the event is. --- @param EventID --- @return #EVENT -function EVENT:OnEventGeneric( EventFunction, EventSelf, EventID ) - self:F2( { EventID } ) - - local Event = self:Init( EventID, EventSelf:GetClassNameAndID() ) - Event.EventFunction = EventFunction - Event.EventSelf = EventSelf - return self -end - - ---- Set a new listener for an S_EVENT_X event --- @param #EVENT self --- @param #string EventDCSUnitName --- @param #function EventFunction The function to be called when the event occurs for the unit. --- @param Base#BASE EventSelf The self instance of the class for which the event is. --- @param EventID --- @return #EVENT -function EVENT:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, EventID ) - self:F2( EventDCSUnitName ) - - local Event = self:Init( EventID, EventSelf:GetClassNameAndID() ) - if not Event.IniUnit then - Event.IniUnit = {} - end - Event.IniUnit[EventDCSUnitName] = {} - Event.IniUnit[EventDCSUnitName].EventFunction = EventFunction - Event.IniUnit[EventDCSUnitName].EventSelf = EventSelf - return self -end - -do -- OnBirth - - --- Create an OnBirth event handler for a group - -- @param #EVENT self - -- @param Group#GROUP EventGroup - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param EventSelf The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnBirthForTemplate( EventTemplate, EventFunction, EventSelf ) - self:F2( EventTemplate.name ) - - self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnBirthForUnit ) - - return self - end - - --- Set a new listener for an S_EVENT_BIRTH event, and registers the unit born. - -- @param #EVENT self - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf - -- @return #EVENT - function EVENT:OnBirth( EventFunction, EventSelf ) - self:F2() - - self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_BIRTH ) - - return self - end - - --- Set a new listener for an S_EVENT_BIRTH event. - -- @param #EVENT self - -- @param #string EventDCSUnitName The id of the unit for the event to be handled. - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf - -- @return #EVENT - function EVENT:OnBirthForUnit( EventDCSUnitName, EventFunction, EventSelf ) - self:F2( EventDCSUnitName ) - - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_BIRTH ) - - return self - end - - --- Stop listening to S_EVENT_BIRTH event. - -- @param #EVENT self - -- @param Base#BASE EventSelf - -- @return #EVENT - function EVENT:OnBirthRemove( EventSelf ) - self:F2() - - self:Remove( EventSelf, world.event.S_EVENT_BIRTH ) - - return self - end - - -end - -do -- OnCrash - - --- Create an OnCrash event handler for a group - -- @param #EVENT self - -- @param Group#GROUP EventGroup - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param EventSelf The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnCrashForTemplate( EventTemplate, EventFunction, EventSelf ) - self:F2( EventTemplate.name ) - - self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnCrashForUnit ) - - return self - end - - --- Set a new listener for an S_EVENT_CRASH event. - -- @param #EVENT self - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf - -- @return #EVENT - function EVENT:OnCrash( EventFunction, EventSelf ) - self:F2() - - self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_CRASH ) - - return self - end - - --- Set a new listener for an S_EVENT_CRASH event. - -- @param #EVENT self - -- @param #string EventDCSUnitName - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnCrashForUnit( EventDCSUnitName, EventFunction, EventSelf ) - self:F2( EventDCSUnitName ) - - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_CRASH ) - - return self - end - - --- Stop listening to S_EVENT_CRASH event. - -- @param #EVENT self - -- @param Base#BASE EventSelf - -- @return #EVENT - function EVENT:OnCrashRemove( EventSelf ) - self:F2() - - self:Remove( EventSelf, world.event.S_EVENT_CRASH ) - - return self - end - -end - -do -- OnDead - - --- Create an OnDead event handler for a group - -- @param #EVENT self - -- @param Group#GROUP EventGroup - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param EventSelf The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnDeadForTemplate( EventTemplate, EventFunction, EventSelf ) - self:F2( EventTemplate.name ) - - self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnDeadForUnit ) - - return self - end - - --- Set a new listener for an S_EVENT_DEAD event. - -- @param #EVENT self - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf - -- @return #EVENT - function EVENT:OnDead( EventFunction, EventSelf ) - self:F2() - - self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_DEAD ) - - return self - end - - - --- Set a new listener for an S_EVENT_DEAD event. - -- @param #EVENT self - -- @param #string EventDCSUnitName - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnDeadForUnit( EventDCSUnitName, EventFunction, EventSelf ) - self:F2( EventDCSUnitName ) - - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_DEAD ) - - return self - end - - --- Stop listening to S_EVENT_DEAD event. - -- @param #EVENT self - -- @param Base#BASE EventSelf - -- @return #EVENT - function EVENT:OnDeadRemove( EventSelf ) - self:F2() - - self:Remove( EventSelf, world.event.S_EVENT_DEAD ) - - return self - end - - -end - -do -- OnPilotDead - - --- Set a new listener for an S_EVENT_PILOT_DEAD event. - -- @param #EVENT self - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf - -- @return #EVENT - function EVENT:OnPilotDead( EventFunction, EventSelf ) - self:F2() - - self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_PILOT_DEAD ) - - return self - end - - --- Set a new listener for an S_EVENT_PILOT_DEAD event. - -- @param #EVENT self - -- @param #string EventDCSUnitName - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnPilotDeadForUnit( EventDCSUnitName, EventFunction, EventSelf ) - self:F2( EventDCSUnitName ) - - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_PILOT_DEAD ) - - return self - end - - --- Stop listening to S_EVENT_PILOT_DEAD event. - -- @param #EVENT self - -- @param Base#BASE EventSelf - -- @return #EVENT - function EVENT:OnPilotDeadRemove( EventSelf ) - self:F2() - - self:Remove( EventSelf, world.event.S_EVENT_PILOT_DEAD ) - - return self - end - -end - -do -- OnLand - --- Create an OnLand event handler for a group - -- @param #EVENT self - -- @param #table EventTemplate - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param EventSelf The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnLandForTemplate( EventTemplate, EventFunction, EventSelf ) - self:F2( EventTemplate.name ) - - self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnLandForUnit ) - - return self - end - - --- Set a new listener for an S_EVENT_LAND event. - -- @param #EVENT self - -- @param #string EventDCSUnitName - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnLandForUnit( EventDCSUnitName, EventFunction, EventSelf ) - self:F2( EventDCSUnitName ) - - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_LAND ) - - return self - end - - --- Stop listening to S_EVENT_LAND event. - -- @param #EVENT self - -- @param Base#BASE EventSelf - -- @return #EVENT - function EVENT:OnLandRemove( EventSelf ) - self:F2() - - self:Remove( EventSelf, world.event.S_EVENT_LAND ) - - return self - end - - -end - -do -- OnTakeOff - --- Create an OnTakeOff event handler for a group - -- @param #EVENT self - -- @param #table EventTemplate - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param EventSelf The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnTakeOffForTemplate( EventTemplate, EventFunction, EventSelf ) - self:F2( EventTemplate.name ) - - self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnTakeOffForUnit ) - - return self - end - - --- Set a new listener for an S_EVENT_TAKEOFF event. - -- @param #EVENT self - -- @param #string EventDCSUnitName - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnTakeOffForUnit( EventDCSUnitName, EventFunction, EventSelf ) - self:F2( EventDCSUnitName ) - - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_TAKEOFF ) - - return self - end - - --- Stop listening to S_EVENT_TAKEOFF event. - -- @param #EVENT self - -- @param Base#BASE EventSelf - -- @return #EVENT - function EVENT:OnTakeOffRemove( EventSelf ) - self:F2() - - self:Remove( EventSelf, world.event.S_EVENT_TAKEOFF ) - - return self - end - - -end - -do -- OnEngineShutDown - - --- Create an OnDead event handler for a group - -- @param #EVENT self - -- @param #table EventTemplate - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param EventSelf The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnEngineShutDownForTemplate( EventTemplate, EventFunction, EventSelf ) - self:F2( EventTemplate.name ) - - self:OnEventForTemplate( EventTemplate, EventFunction, EventSelf, self.OnEngineShutDownForUnit ) - - return self - end - - --- Set a new listener for an S_EVENT_ENGINE_SHUTDOWN event. - -- @param #EVENT self - -- @param #string EventDCSUnitName - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnEngineShutDownForUnit( EventDCSUnitName, EventFunction, EventSelf ) - self:F2( EventDCSUnitName ) - - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_ENGINE_SHUTDOWN ) - - return self - end - - --- Stop listening to S_EVENT_ENGINE_SHUTDOWN event. - -- @param #EVENT self - -- @param Base#BASE EventSelf - -- @return #EVENT - function EVENT:OnEngineShutDownRemove( EventSelf ) - self:F2() - - self:Remove( EventSelf, world.event.S_EVENT_ENGINE_SHUTDOWN ) - - return self - end - -end - -do -- OnEngineStartUp - - --- Set a new listener for an S_EVENT_ENGINE_STARTUP event. - -- @param #EVENT self - -- @param #string EventDCSUnitName - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnEngineStartUpForUnit( EventDCSUnitName, EventFunction, EventSelf ) - self:F2( EventDCSUnitName ) - - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_ENGINE_STARTUP ) - - return self - end - - --- Stop listening to S_EVENT_ENGINE_STARTUP event. - -- @param #EVENT self - -- @param Base#BASE EventSelf - -- @return #EVENT - function EVENT:OnEngineStartUpRemove( EventSelf ) - self:F2() - - self:Remove( EventSelf, world.event.S_EVENT_ENGINE_STARTUP ) - - return self - end - -end - -do -- OnShot - --- Set a new listener for an S_EVENT_SHOT event. - -- @param #EVENT self - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnShot( EventFunction, EventSelf ) - self:F2() - - self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_SHOT ) - - return self - end - - --- Set a new listener for an S_EVENT_SHOT event for a unit. - -- @param #EVENT self - -- @param #string EventDCSUnitName - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnShotForUnit( EventDCSUnitName, EventFunction, EventSelf ) - self:F2( EventDCSUnitName ) - - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_SHOT ) - - return self - end - - --- Stop listening to S_EVENT_SHOT event. - -- @param #EVENT self - -- @param Base#BASE EventSelf - -- @return #EVENT - function EVENT:OnShotRemove( EventSelf ) - self:F2() - - self:Remove( EventSelf, world.event.S_EVENT_SHOT ) - - return self - end - - -end - -do -- OnHit - - --- Set a new listener for an S_EVENT_HIT event. - -- @param #EVENT self - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnHit( EventFunction, EventSelf ) - self:F2() - - self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_HIT ) - - return self - end - - --- Set a new listener for an S_EVENT_HIT event. - -- @param #EVENT self - -- @param #string EventDCSUnitName - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnHitForUnit( EventDCSUnitName, EventFunction, EventSelf ) - self:F2( EventDCSUnitName ) - - self:OnEventForUnit( EventDCSUnitName, EventFunction, EventSelf, world.event.S_EVENT_HIT ) - - return self - end - - --- Stop listening to S_EVENT_HIT event. - -- @param #EVENT self - -- @param Base#BASE EventSelf - -- @return #EVENT - function EVENT:OnHitRemove( EventSelf ) - self:F2() - - self:Remove( EventSelf, world.event.S_EVENT_HIT ) - - return self - end - -end - -do -- OnPlayerEnterUnit - - --- Set a new listener for an S_EVENT_PLAYER_ENTER_UNIT event. - -- @param #EVENT self - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnPlayerEnterUnit( EventFunction, EventSelf ) - self:F2() - - self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_PLAYER_ENTER_UNIT ) - - return self - end - - --- Stop listening to S_EVENT_PLAYER_ENTER_UNIT event. - -- @param #EVENT self - -- @param Base#BASE EventSelf - -- @return #EVENT - function EVENT:OnPlayerEnterRemove( EventSelf ) - self:F2() - - self:Remove( EventSelf, world.event.S_EVENT_PLAYER_ENTER_UNIT ) - - return self - end - -end - -do -- OnPlayerLeaveUnit - --- Set a new listener for an S_EVENT_PLAYER_LEAVE_UNIT event. - -- @param #EVENT self - -- @param #function EventFunction The function to be called when the event occurs for the unit. - -- @param Base#BASE EventSelf The self instance of the class for which the event is. - -- @return #EVENT - function EVENT:OnPlayerLeaveUnit( EventFunction, EventSelf ) - self:F2() - - self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_PLAYER_LEAVE_UNIT ) - - return self - end - - --- Stop listening to S_EVENT_PLAYER_LEAVE_UNIT event. - -- @param #EVENT self - -- @param Base#BASE EventSelf - -- @return #EVENT - function EVENT:OnPlayerLeaveRemove( EventSelf ) - self:F2() - - self:Remove( EventSelf, world.event.S_EVENT_PLAYER_LEAVE_UNIT ) - - return self - end - -end - - - ---- @param #EVENT self --- @param #EVENTDATA Event -function EVENT:onEvent( Event ) - - if self and self.Events and self.Events[Event.id] then - if Event.initiator and Event.initiator:getCategory() == Object.Category.UNIT then - Event.IniDCSUnit = Event.initiator - Event.IniDCSGroup = Event.IniDCSUnit:getGroup() - Event.IniDCSUnitName = Event.IniDCSUnit:getName() - Event.IniUnitName = Event.IniDCSUnitName - Event.IniUnit = UNIT:FindByName( Event.IniDCSUnitName ) - Event.IniDCSGroupName = "" - if Event.IniDCSGroup and Event.IniDCSGroup:isExist() then - Event.IniDCSGroupName = Event.IniDCSGroup:getName() - end - end - if Event.target then - if Event.target and Event.target:getCategory() == Object.Category.UNIT then - Event.TgtDCSUnit = Event.target - Event.TgtDCSGroup = Event.TgtDCSUnit:getGroup() - Event.TgtDCSUnitName = Event.TgtDCSUnit:getName() - Event.TgtUnitName = Event.TgtDCSUnitName - Event.TgtUnit = UNIT:FindByName( Event.TgtDCSUnitName ) - Event.TgtDCSGroupName = "" - if Event.TgtDCSGroup and Event.TgtDCSGroup:isExist() then - Event.TgtDCSGroupName = Event.TgtDCSGroup:getName() - end - end - end - if Event.weapon then - Event.Weapon = Event.weapon - Event.WeaponName = Event.Weapon:getTypeName() - --Event.WeaponTgtDCSUnit = Event.Weapon:getTarget() - end - self:E( { _EVENTCODES[Event.id], Event.IniUnitName, Event.TgtUnitName, Event.WeaponName } ) - for ClassName, EventData in pairs( self.Events[Event.id] ) do - if Event.IniDCSUnitName and EventData.IniUnit and EventData.IniUnit[Event.IniDCSUnitName] then - self:T( { "Calling event function for class ", ClassName, " unit ", Event.IniUnitName } ) - EventData.IniUnit[Event.IniDCSUnitName].EventFunction( EventData.IniUnit[Event.IniDCSUnitName].EventSelf, Event ) - else - if Event.IniDCSUnit and not EventData.IniUnit then - if ClassName == EventData.EventSelf:GetClassNameAndID() then - self:T( { "Calling event function for class ", ClassName } ) - EventData.EventFunction( EventData.EventSelf, Event ) - end - end - end - end - else - self:E( { _EVENTCODES[Event.id], Event } ) - end -end - ---- Encapsulation of DCS World Menu system in a set of MENU classes. --- @module Menu - ---- The MENU class --- @type MENU --- @extends Base#BASE -MENU = { - ClassName = "MENU", - MenuPath = nil, - MenuText = "", - MenuParentPath = nil -} - ---- -function MENU:New( MenuText, MenuParentPath ) - - -- Arrange meta tables - local Child = BASE:Inherit( self, BASE:New() ) - - Child.MenuPath = nil - Child.MenuText = MenuText - Child.MenuParentPath = MenuParentPath - return Child -end - ---- The COMMANDMENU class --- @type COMMANDMENU --- @extends Menu#MENU -COMMANDMENU = { - ClassName = "COMMANDMENU", - CommandMenuFunction = nil, - CommandMenuArgument = nil -} - -function COMMANDMENU:New( MenuText, ParentMenu, CommandMenuFunction, CommandMenuArgument ) - - -- Arrange meta tables - - local MenuParentPath = nil - if ParentMenu ~= nil then - MenuParentPath = ParentMenu.MenuPath - end - - local Child = BASE:Inherit( self, MENU:New( MenuText, MenuParentPath ) ) - - Child.MenuPath = missionCommands.addCommand( MenuText, MenuParentPath, CommandMenuFunction, CommandMenuArgument ) - Child.CommandMenuFunction = CommandMenuFunction - Child.CommandMenuArgument = CommandMenuArgument - return Child -end - ---- The SUBMENU class --- @type SUBMENU --- @extends Menu#MENU -SUBMENU = { - ClassName = "SUBMENU" -} - -function SUBMENU:New( MenuText, ParentMenu ) - - -- Arrange meta tables - local MenuParentPath = nil - if ParentMenu ~= nil then - MenuParentPath = ParentMenu.MenuPath - end - - local Child = BASE:Inherit( self, MENU:New( MenuText, MenuParentPath ) ) - - Child.MenuPath = missionCommands.addSubMenu( MenuText, MenuParentPath ) - return Child -end - -do - - -- This local variable is used to cache the menus registered under clients. - -- Menus don't dissapear when clients are destroyed and restarted. - -- So every menu for a client created must be tracked so that program logic accidentally does not create - -- the same menus twice during initialization logic. - -- These menu classes are handling this logic with this variable. - local _MENUCLIENTS = {} - - --- The MENU_CLIENT class - -- @type MENU_CLIENT - -- @extends Menu#MENU - MENU_CLIENT = { - ClassName = "MENU_CLIENT" - } - - --- Creates a new menu item for a group - -- @param self - -- @param Client#CLIENT MenuClient The Client owning the menu. - -- @param #string MenuText The text for the menu. - -- @param #table ParentMenu The parent menu. - -- @return #MENU_CLIENT self - function MENU_CLIENT:New( MenuClient, MenuText, ParentMenu ) - - -- Arrange meta tables - local MenuParentPath = {} - if ParentMenu ~= nil then - MenuParentPath = ParentMenu.MenuPath - end - - local self = BASE:Inherit( self, MENU:New( MenuText, MenuParentPath ) ) - self:F( { MenuClient, MenuText, ParentMenu } ) - - self.MenuClient = MenuClient - self.MenuClientGroupID = MenuClient:GetClientGroupID() - self.MenuParentPath = MenuParentPath - self.MenuText = MenuText - self.ParentMenu = ParentMenu - - self.Menus = {} - - if not _MENUCLIENTS[self.MenuClientGroupID] then - _MENUCLIENTS[self.MenuClientGroupID] = {} - end - - local MenuPath = _MENUCLIENTS[self.MenuClientGroupID] - - self:T( { MenuClient:GetClientGroupName(), MenuPath[table.concat(MenuParentPath)], MenuParentPath, MenuText } ) - - local MenuPathID = table.concat(MenuParentPath) .. "/" .. MenuText - if MenuPath[MenuPathID] then - missionCommands.removeItemForGroup( self.MenuClient:GetClientGroupID(), MenuPath[MenuPathID] ) - end - - self.MenuPath = missionCommands.addSubMenuForGroup( self.MenuClient:GetClientGroupID(), MenuText, MenuParentPath ) - MenuPath[MenuPathID] = self.MenuPath - - self:T( { MenuClient:GetClientGroupName(), self.MenuPath } ) - - if ParentMenu and ParentMenu.Menus then - ParentMenu.Menus[self.MenuPath] = self - end - return self - end - - --- Removes the sub menus recursively of this MENU_CLIENT. - -- @param #MENU_CLIENT self - -- @return #MENU_CLIENT self - function MENU_CLIENT:RemoveSubMenus() - self:F( self.MenuPath ) - - for MenuID, Menu in pairs( self.Menus ) do - Menu:Remove() - end - - end - - --- Removes the sub menus recursively of this MENU_CLIENT. - -- @param #MENU_CLIENT self - -- @return #MENU_CLIENT self - function MENU_CLIENT:Remove() - self:F( self.MenuPath ) - - self:RemoveSubMenus() - - if not _MENUCLIENTS[self.MenuClientGroupID] then - _MENUCLIENTS[self.MenuClientGroupID] = {} - end - - local MenuPath = _MENUCLIENTS[self.MenuClientGroupID] - - if MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] then - MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] = nil - end - - missionCommands.removeItemForGroup( self.MenuClient:GetClientGroupID(), self.MenuPath ) - self.ParentMenu.Menus[self.MenuPath] = nil - return nil - end - - - --- The MENU_CLIENT_COMMAND class - -- @type MENU_CLIENT_COMMAND - -- @extends Menu#MENU - MENU_CLIENT_COMMAND = { - ClassName = "MENU_CLIENT_COMMAND" - } - - --- Creates a new radio command item for a group - -- @param self - -- @param Client#CLIENT MenuClient The Client owning the menu. - -- @param MenuText The text for the menu. - -- @param ParentMenu The parent menu. - -- @param CommandMenuFunction A function that is called when the menu key is pressed. - -- @param CommandMenuArgument An argument for the function. - -- @return Menu#MENU_CLIENT_COMMAND self - function MENU_CLIENT_COMMAND:New( MenuClient, MenuText, ParentMenu, CommandMenuFunction, CommandMenuArgument ) - - -- Arrange meta tables - - local MenuParentPath = {} - if ParentMenu ~= nil then - MenuParentPath = ParentMenu.MenuPath - end - - local self = BASE:Inherit( self, MENU:New( MenuText, MenuParentPath ) ) - - self.MenuClient = MenuClient - self.MenuClientGroupID = MenuClient:GetClientGroupID() - self.MenuParentPath = MenuParentPath - self.MenuText = MenuText - self.ParentMenu = ParentMenu - - if not _MENUCLIENTS[self.MenuClientGroupID] then - _MENUCLIENTS[self.MenuClientGroupID] = {} - end - - local MenuPath = _MENUCLIENTS[self.MenuClientGroupID] - - self:T( { MenuClient:GetClientGroupName(), MenuPath[table.concat(MenuParentPath)], MenuParentPath, MenuText, CommandMenuFunction, CommandMenuArgument } ) - - local MenuPathID = table.concat(MenuParentPath) .. "/" .. MenuText - if MenuPath[MenuPathID] then - missionCommands.removeItemForGroup( self.MenuClient:GetClientGroupID(), MenuPath[MenuPathID] ) - end - - self.MenuPath = missionCommands.addCommandForGroup( self.MenuClient:GetClientGroupID(), MenuText, MenuParentPath, CommandMenuFunction, CommandMenuArgument ) - MenuPath[MenuPathID] = self.MenuPath - - self.CommandMenuFunction = CommandMenuFunction - self.CommandMenuArgument = CommandMenuArgument - - ParentMenu.Menus[self.MenuPath] = self - - return self - end - - function MENU_CLIENT_COMMAND:Remove() - self:F( self.MenuPath ) - - if not _MENUCLIENTS[self.MenuClientGroupID] then - _MENUCLIENTS[self.MenuClientGroupID] = {} - end - - local MenuPath = _MENUCLIENTS[self.MenuClientGroupID] - - if MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] then - MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] = nil - end - - missionCommands.removeItemForGroup( self.MenuClient:GetClientGroupID(), self.MenuPath ) - self.ParentMenu.Menus[self.MenuPath] = nil - return nil - end -end - ---- MENU_GROUP - -do - -- This local variable is used to cache the menus registered under clients. - -- Menus don't dissapear when clients are destroyed and restarted. - -- So every menu for a client created must be tracked so that program logic accidentally does not create - -- the same menus twice during initialization logic. - -- These menu classes are handling this logic with this variable. - local _MENUGROUPS = {} - - --- The MENU_GROUP class - -- @type MENU_GROUP - -- @extends Menu#MENU - MENU_GROUP = { - ClassName = "MENU_GROUP" - } - - --- Creates a new menu item for a group - -- @param self - -- @param Group#GROUP MenuGroup The Group owning the menu. - -- @param #string MenuText The text for the menu. - -- @param #table ParentMenu The parent menu. - -- @return #MENU_GROUP self - function MENU_GROUP:New( MenuGroup, MenuText, ParentMenu ) - - -- Arrange meta tables - local MenuParentPath = {} - if ParentMenu ~= nil then - MenuParentPath = ParentMenu.MenuPath - end - - local self = BASE:Inherit( self, MENU:New( MenuText, MenuParentPath ) ) - self:F( { MenuGroup, MenuText, ParentMenu } ) - - self.MenuGroup = MenuGroup - self.MenuGroupID = MenuGroup:GetID() - self.MenuParentPath = MenuParentPath - self.MenuText = MenuText - self.ParentMenu = ParentMenu - - self.Menus = {} - - if not _MENUGROUPS[self.MenuGroupID] then - _MENUGROUPS[self.MenuGroupID] = {} - end - - local MenuPath = _MENUGROUPS[self.MenuGroupID] - - self:T( { MenuGroup:GetName(), MenuPath[table.concat(MenuParentPath)], MenuParentPath, MenuText } ) - - local MenuPathID = table.concat(MenuParentPath) .. "/" .. MenuText - if MenuPath[MenuPathID] then - missionCommands.removeItemForGroup( self.MenuGroupID, MenuPath[MenuPathID] ) - end - - self:T( { "Adding for MenuPath ", MenuText, MenuParentPath } ) - self.MenuPath = missionCommands.addSubMenuForGroup( self.MenuGroupID, MenuText, MenuParentPath ) - MenuPath[MenuPathID] = self.MenuPath - - self:T( { self.MenuGroupID, self.MenuPath } ) - - if ParentMenu and ParentMenu.Menus then - ParentMenu.Menus[self.MenuPath] = self - end - return self - end - - --- Removes the sub menus recursively of this MENU_GROUP. - -- @param #MENU_GROUP self - -- @return #MENU_GROUP self - function MENU_GROUP:RemoveSubMenus() - self:F( self.MenuPath ) - - for MenuID, Menu in pairs( self.Menus ) do - Menu:Remove() - end - - end - - --- Removes the sub menus recursively of this MENU_GROUP. - -- @param #MENU_GROUP self - -- @return #MENU_GROUP self - function MENU_GROUP:Remove() - self:F( self.MenuPath ) - - self:RemoveSubMenus() - - if not _MENUGROUPS[self.MenuGroupID] then - _MENUGROUPS[self.MenuGroupID] = {} - end - - local MenuPath = _MENUGROUPS[self.MenuGroupID] - - if MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] then - MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] = nil - end - - missionCommands.removeItemForGroup( self.MenuGroupID, self.MenuPath ) - if self.ParentMenu then - self.ParentMenu.Menus[self.MenuPath] = nil - end - return nil - end - - - --- The MENU_GROUP_COMMAND class - -- @type MENU_GROUP_COMMAND - -- @extends Menu#MENU - MENU_GROUP_COMMAND = { - ClassName = "MENU_GROUP_COMMAND" - } - - --- Creates a new radio command item for a group - -- @param #MENU_GROUP_COMMAND self - -- @param Group#GROUP MenuGroup The Group owning the menu. - -- @param MenuText The text for the menu. - -- @param ParentMenu The parent menu. - -- @param CommandMenuFunction A function that is called when the menu key is pressed. - -- @param CommandMenuArgument An argument for the function. - -- @return Menu#MENU_GROUP_COMMAND self - function MENU_GROUP_COMMAND:New( MenuGroup, MenuText, ParentMenu, CommandMenuFunction, CommandMenuArgument ) - - -- Arrange meta tables - - local MenuParentPath = {} - if ParentMenu ~= nil then - MenuParentPath = ParentMenu.MenuPath - end - - local self = BASE:Inherit( self, MENU:New( MenuText, MenuParentPath ) ) - - self.MenuGroup = MenuGroup - self.MenuGroupID = MenuGroup:GetID() - self.MenuParentPath = MenuParentPath - self.MenuText = MenuText - self.ParentMenu = ParentMenu - - if not _MENUGROUPS[self.MenuGroupID] then - _MENUGROUPS[self.MenuGroupID] = {} - end - - local MenuPath = _MENUGROUPS[self.MenuGroupID] - - self:T( { MenuGroup:GetName(), MenuPath[table.concat(MenuParentPath)], MenuParentPath, MenuText, CommandMenuFunction, CommandMenuArgument } ) - - local MenuPathID = table.concat(MenuParentPath) .. "/" .. MenuText - if MenuPath[MenuPathID] then - missionCommands.removeItemForGroup( self.MenuGroupID, MenuPath[MenuPathID] ) - end - - self:T( { "Adding for MenuPath ", MenuText, MenuParentPath } ) - self.MenuPath = missionCommands.addCommandForGroup( self.MenuGroupID, MenuText, MenuParentPath, CommandMenuFunction, CommandMenuArgument ) - MenuPath[MenuPathID] = self.MenuPath - - self.CommandMenuFunction = CommandMenuFunction - self.CommandMenuArgument = CommandMenuArgument - - ParentMenu.Menus[self.MenuPath] = self - - return self - end - - function MENU_GROUP_COMMAND:Remove() - self:F( self.MenuPath ) - - if not _MENUGROUPS[self.MenuGroupID] then - _MENUGROUPS[self.MenuGroupID] = {} - end - - local MenuPath = _MENUGROUPS[self.MenuGroupID] - - - if MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] then - MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] = nil - end - - missionCommands.removeItemForGroup( self.MenuGroupID, self.MenuPath ) - self.ParentMenu.Menus[self.MenuPath] = nil - return nil - end - -end - ---- The MENU_COALITION class --- @type MENU_COALITION --- @extends Menu#MENU -MENU_COALITION = { - ClassName = "MENU_COALITION" -} - ---- Creates a new coalition menu item --- @param #MENU_COALITION self --- @param DCSCoalition#coalition.side Coalition The coalition owning the menu. --- @param #string MenuText The text for the menu. --- @param #table ParentMenu The parent menu. --- @return #MENU_COALITION self -function MENU_COALITION:New( Coalition, MenuText, ParentMenu ) - - -- Arrange meta tables - local MenuParentPath = {} - if ParentMenu ~= nil then - MenuParentPath = ParentMenu.MenuPath - end - - local self = BASE:Inherit( self, MENU:New( MenuText, MenuParentPath ) ) - self:F( { Coalition, MenuText, ParentMenu } ) - - self.Coalition = Coalition - self.MenuParentPath = MenuParentPath - self.MenuText = MenuText - self.ParentMenu = ParentMenu - - self.Menus = {} - - self:T( { MenuParentPath, MenuText } ) - - self.MenuPath = missionCommands.addSubMenuForCoalition( self.Coalition, MenuText, MenuParentPath ) - - self:T( { self.MenuPath } ) - - if ParentMenu and ParentMenu.Menus then - ParentMenu.Menus[self.MenuPath] = self - end - return self -end - ---- Removes the sub menus recursively of this MENU_COALITION. --- @param #MENU_COALITION self --- @return #MENU_COALITION self -function MENU_COALITION:RemoveSubMenus() - self:F( self.MenuPath ) - - for MenuID, Menu in pairs( self.Menus ) do - Menu:Remove() - end - -end - ---- Removes the sub menus recursively of this MENU_COALITION. --- @param #MENU_COALITION self --- @return #MENU_COALITION self -function MENU_COALITION:Remove() - self:F( self.MenuPath ) - - self:RemoveSubMenus() - missionCommands.removeItemForCoalition( self.MenuCoalition, self.MenuPath ) - self.ParentMenu.Menus[self.MenuPath] = nil - - return nil -end - - ---- The MENU_COALITION_COMMAND class --- @type MENU_COALITION_COMMAND --- @extends Menu#MENU -MENU_COALITION_COMMAND = { - ClassName = "MENU_COALITION_COMMAND" -} - ---- Creates a new radio command item for a group --- @param #MENU_COALITION_COMMAND self --- @param DCSCoalition#coalition.side MenuCoalition The coalition owning the menu. --- @param MenuText The text for the menu. --- @param ParentMenu The parent menu. --- @param CommandMenuFunction A function that is called when the menu key is pressed. --- @param CommandMenuArgument An argument for the function. --- @return #MENU_COALITION_COMMAND self -function MENU_COALITION_COMMAND:New( MenuCoalition, MenuText, ParentMenu, CommandMenuFunction, CommandMenuArgument ) - - -- Arrange meta tables - - local MenuParentPath = {} - if ParentMenu ~= nil then - MenuParentPath = ParentMenu.MenuPath - end - - local self = BASE:Inherit( self, MENU:New( MenuText, MenuParentPath ) ) - - self.MenuCoalition = MenuCoalition - self.MenuParentPath = MenuParentPath - self.MenuText = MenuText - self.ParentMenu = ParentMenu - - self:T( { MenuParentPath, MenuText, CommandMenuFunction, CommandMenuArgument } ) - - self.MenuPath = missionCommands.addCommandForCoalition( self.MenuCoalition, MenuText, MenuParentPath, CommandMenuFunction, CommandMenuArgument ) - - self.CommandMenuFunction = CommandMenuFunction - self.CommandMenuArgument = CommandMenuArgument - - ParentMenu.Menus[self.MenuPath] = self - - return self -end - ---- Removes a radio command item for a coalition --- @param #MENU_COALITION_COMMAND self --- @return #MENU_COALITION_COMMAND self -function MENU_COALITION_COMMAND:Remove() - self:F( self.MenuPath ) - - missionCommands.removeItemForCoalition( self.MenuCoalition, self.MenuPath ) - self.ParentMenu.Menus[self.MenuPath] = nil - return nil -end ---- This module contains the GROUP class. --- --- 1) @{Group#GROUP} class, extends @{Controllable#CONTROLLABLE} --- ============================================================= --- The @{Group#GROUP} class is a wrapper class to handle the DCS Group objects: --- --- * Support all DCS Group APIs. --- * Enhance with Group specific APIs not in the DCS Group API set. --- * Handle local Group Controller. --- * Manage the "state" of the DCS Group. --- --- **IMPORTANT: ONE SHOULD NEVER SANATIZE these GROUP OBJECT REFERENCES! (make the GROUP object references nil).** --- --- 1.1) GROUP reference methods --- ----------------------- --- For each DCS Group object alive within a running mission, a GROUP wrapper object (instance) will be created within the _@{DATABASE} object. --- This is done at the beginning of the mission (when the mission starts), and dynamically when new DCS Group objects are spawned (using the @{SPAWN} class). --- --- The GROUP class does not contain a :New() method, rather it provides :Find() methods to retrieve the object reference --- using the DCS Group or the DCS GroupName. --- --- Another thing to know is that GROUP objects do not "contain" the DCS Group object. --- The GROUP methods will reference the DCS Group object by name when it is needed during API execution. --- If the DCS Group object does not exist or is nil, the GROUP methods will return nil and log an exception in the DCS.log file. --- --- The GROUP class provides the following functions to retrieve quickly the relevant GROUP instance: --- --- * @{#GROUP.Find}(): Find a GROUP instance from the _DATABASE object using a DCS Group object. --- * @{#GROUP.FindByName}(): Find a GROUP instance from the _DATABASE object using a DCS Group name. --- --- 1.2) GROUP task methods --- ----------------------- --- Several group task methods are available that help you to prepare tasks. --- These methods return a string consisting of the task description, which can then be given to either a --- @{Controllable#CONTROLLABLE.PushTask} or @{Controllable#CONTROLLABLE.SetTask} method to assign the task to the GROUP. --- Tasks are specific for the category of the GROUP, more specific, for AIR, GROUND or AIR and GROUND. --- Each task description where applicable indicates for which group category the task is valid. --- There are 2 main subdivisions of tasks: Assigned tasks and EnRoute tasks. --- --- ### 1.2.1) Assigned task methods --- --- Assigned task methods make the group execute the task where the location of the (possible) targets of the task are known before being detected. --- This is different from the EnRoute tasks, where the targets of the task need to be detected before the task can be executed. --- --- Find below a list of the **assigned task** methods: --- --- * @{Controllable#CONTROLLABLE.TaskAttackGroup}: (AIR) Attack a Group. --- * @{Controllable#CONTROLLABLE.TaskAttackMapObject}: (AIR) Attacking the map object (building, structure, e.t.c). --- * @{Controllable#CONTROLLABLE.TaskAttackUnit}: (AIR) Attack the Unit. --- * @{Controllable#CONTROLLABLE.TaskBombing}: (Controllable#CONTROLLABLEDelivering weapon at the point on the ground. --- * @{Controllable#CONTROLLABLE.TaskBombingRunway}: (AIR) Delivering weapon on the runway. --- * @{Controllable#CONTROLLABLE.TaskEmbarking}: (AIR) Move the group to a Vec2 Point, wait for a defined duration and embark a group. --- * @{Controllable#CONTROLLABLE.TaskEmbarkToTransport}: (GROUND) Embark to a Transport landed at a location. --- * @{Controllable#CONTROLLABLE.TaskEscort}: (AIR) Escort another airborne group. --- * @{Controllable#CONTROLLABLE.TaskFAC_AttackGroup}: (AIR + GROUND) The task makes the group/unit a FAC and orders the FAC to control the target (enemy ground group) destruction. --- * @{Controllable#CONTROLLABLE.TaskFireAtPoint}: (GROUND) Fire at a VEC2 point until ammunition is finished. --- * @{Controllable#CONTROLLABLE.TaskFollow}: (AIR) Following another airborne group. --- * @{Controllable#CONTROLLABLE.TaskHold}: (GROUND) Hold ground group from moving. --- * @{Controllable#CONTROLLABLE.TaskHoldPosition}: (AIR) Hold position at the current position of the first unit of the group. --- * @{Controllable#CONTROLLABLE.TaskLand}: (AIR HELICOPTER) Landing at the ground. For helicopters only. --- * @{Controllable#CONTROLLABLE.TaskLandAtZone}: (AIR) Land the group at a @{Zone#ZONE_RADIUS). --- * @{Controllable#CONTROLLABLE.TaskOrbitCircle}: (AIR) Orbit at the current position of the first unit of the group at a specified alititude. --- * @{Controllable#CONTROLLABLE.TaskOrbitCircleAtVec2}: (AIR) Orbit at a specified position at a specified alititude during a specified duration with a specified speed. --- * @{Controllable#CONTROLLABLE.TaskRefueling}: (AIR) Refueling from the nearest tanker. No parameters. --- * @{Controllable#CONTROLLABLE.TaskRoute}: (AIR + GROUND) Return a Misson task to follow a given route defined by Points. --- * @{Controllable#CONTROLLABLE.TaskRouteToVec2}: (AIR + GROUND) Make the Group move to a given point. --- * @{Controllable#CONTROLLABLE.TaskRouteToVec3}: (AIR + GROUND) Make the Group move to a given point. --- * @{Controllable#CONTROLLABLE.TaskRouteToZone}: (AIR + GROUND) Route the group to a given zone. --- * @{Controllable#CONTROLLABLE.TaskReturnToBase}: (AIR) Route the group to an airbase. --- --- ### 1.2.2) EnRoute task methods --- --- EnRoute tasks require the targets of the task need to be detected by the group (using its sensors) before the task can be executed: --- --- * @{Controllable#CONTROLLABLE.EnRouteTaskAWACS}: (AIR) Aircraft will act as an AWACS for friendly units (will provide them with information about contacts). No parameters. --- * @{Controllable#CONTROLLABLE.EnRouteTaskEngageGroup}: (AIR) Engaging a group. The task does not assign the target group to the unit/group to attack now; it just allows the unit/group to engage the target group as well as other assigned targets. --- * @{Controllable#CONTROLLABLE.EnRouteTaskEngageTargets}: (AIR) Engaging targets of defined types. --- * @{Controllable#CONTROLLABLE.EnRouteTaskEWR}: (AIR) Attack the Unit. --- * @{Controllable#CONTROLLABLE.EnRouteTaskFAC}: (AIR + GROUND) The task makes the group/unit a FAC and lets the FAC to choose a targets (enemy ground group) around as well as other assigned targets. --- * @{Controllable#CONTROLLABLE.EnRouteTaskFAC_EngageGroup}: (AIR + GROUND) The task makes the group/unit a FAC and lets the FAC to choose the target (enemy ground group) as well as other assigned targets. --- * @{Controllable#CONTROLLABLE.EnRouteTaskTanker}: (AIR) Aircraft will act as a tanker for friendly units. No parameters. --- --- ### 1.2.3) Preparation task methods --- --- There are certain task methods that allow to tailor the task behaviour: --- --- * @{Controllable#CONTROLLABLE.TaskWrappedAction}: Return a WrappedAction Task taking a Command. --- * @{Controllable#CONTROLLABLE.TaskCombo}: Return a Combo Task taking an array of Tasks. --- * @{Controllable#CONTROLLABLE.TaskCondition}: Return a condition section for a controlled task. --- * @{Controllable#CONTROLLABLE.TaskControlled}: Return a Controlled Task taking a Task and a TaskCondition. --- --- ### 1.2.4) Obtain the mission from group templates --- --- Group templates contain complete mission descriptions. Sometimes you want to copy a complete mission from a group and assign it to another: --- --- * @{Controllable#CONTROLLABLE.TaskMission}: (AIR + GROUND) Return a mission task from a mission template. --- --- 1.3) GROUP Command methods --- -------------------------- --- Group **command methods** prepare the execution of commands using the @{Controllable#CONTROLLABLE.SetCommand} method: --- --- * @{Controllable#CONTROLLABLE.CommandDoScript}: Do Script command. --- * @{Controllable#CONTROLLABLE.CommandSwitchWayPoint}: Perform a switch waypoint command. --- --- 1.4) GROUP Option methods --- ------------------------- --- Group **Option methods** change the behaviour of the Group while being alive. --- --- ### 1.4.1) Rule of Engagement: --- --- * @{Controllable#CONTROLLABLE.OptionROEWeaponFree} --- * @{Controllable#CONTROLLABLE.OptionROEOpenFire} --- * @{Controllable#CONTROLLABLE.OptionROEReturnFire} --- * @{Controllable#CONTROLLABLE.OptionROEEvadeFire} --- --- To check whether an ROE option is valid for a specific group, use: --- --- * @{Controllable#CONTROLLABLE.OptionROEWeaponFreePossible} --- * @{Controllable#CONTROLLABLE.OptionROEOpenFirePossible} --- * @{Controllable#CONTROLLABLE.OptionROEReturnFirePossible} --- * @{Controllable#CONTROLLABLE.OptionROEEvadeFirePossible} --- --- ### 1.4.2) Rule on thread: --- --- * @{Controllable#CONTROLLABLE.OptionROTNoReaction} --- * @{Controllable#CONTROLLABLE.OptionROTPassiveDefense} --- * @{Controllable#CONTROLLABLE.OptionROTEvadeFire} --- * @{Controllable#CONTROLLABLE.OptionROTVertical} --- --- To test whether an ROT option is valid for a specific group, use: --- --- * @{Controllable#CONTROLLABLE.OptionROTNoReactionPossible} --- * @{Controllable#CONTROLLABLE.OptionROTPassiveDefensePossible} --- * @{Controllable#CONTROLLABLE.OptionROTEvadeFirePossible} --- * @{Controllable#CONTROLLABLE.OptionROTVerticalPossible} --- --- 1.5) GROUP Zone validation methods --- ---------------------------------- --- The group can be validated whether it is completely, partly or not within a @{Zone}. --- Use the following Zone validation methods on the group: --- --- * @{#GROUP.IsCompletelyInZone}: Returns true if all units of the group are within a @{Zone}. --- * @{#GROUP.IsPartlyInZone}: Returns true if some units of the group are within a @{Zone}. --- * @{#GROUP.IsNotInZone}: Returns true if none of the group units of the group are within a @{Zone}. --- --- The zone can be of any @{Zone} class derived from @{Zone#ZONE_BASE}. So, these methods are polymorphic to the zones tested on. --- --- @module Group --- @author FlightControl - ---- The GROUP class --- @type GROUP --- @extends Controllable#CONTROLLABLE --- @field #string GroupName The name of the group. -GROUP = { - ClassName = "GROUP", -} - ---- Create a new GROUP from a DCSGroup --- @param #GROUP self --- @param DCSGroup#Group GroupName The DCS Group name --- @return #GROUP self -function GROUP:Register( GroupName ) - local self = BASE:Inherit( self, CONTROLLABLE:New( GroupName ) ) - self:F2( GroupName ) - self.GroupName = GroupName - return self -end - --- Reference methods. - ---- Find the GROUP wrapper class instance using the DCS Group. --- @param #GROUP self --- @param DCSGroup#Group DCSGroup The DCS Group. --- @return #GROUP The GROUP. -function GROUP:Find( DCSGroup ) - - local GroupName = DCSGroup:getName() -- Group#GROUP - local GroupFound = _DATABASE:FindGroup( GroupName ) - GroupFound:E( { GroupName, GroupFound:GetClassNameAndID() } ) - return GroupFound -end - ---- Find the created GROUP using the DCS Group Name. --- @param #GROUP self --- @param #string GroupName The DCS Group Name. --- @return #GROUP The GROUP. -function GROUP:FindByName( GroupName ) - - local GroupFound = _DATABASE:FindGroup( GroupName ) - return GroupFound -end - --- DCS Group methods support. - ---- Returns the DCS Group. --- @param #GROUP self --- @return DCSGroup#Group The DCS Group. -function GROUP:GetDCSObject() - local DCSGroup = Group.getByName( self.GroupName ) - - if DCSGroup then - return DCSGroup - end - - return nil -end - - ---- Returns if the DCS Group is alive. --- When the group exists at run-time, this method will return true, otherwise false. --- @param #GROUP self --- @return #boolean true if the DCS Group is alive. -function GROUP:IsAlive() - self:F2( self.GroupName ) - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local GroupIsAlive = DCSGroup:isExist() - self:T3( GroupIsAlive ) - return GroupIsAlive - end - - return nil -end - ---- Destroys the DCS Group and all of its DCS Units. --- Note that this destroy method also raises a destroy event at run-time. --- So all event listeners will catch the destroy event of this DCS Group. --- @param #GROUP self -function GROUP:Destroy() - self:F2( self.GroupName ) - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - for Index, UnitData in pairs( DCSGroup:getUnits() ) do - self:CreateEventCrash( timer.getTime(), UnitData ) - end - DCSGroup:destroy() - DCSGroup = nil - end - - return nil -end - ---- Returns category of the DCS Group. --- @param #GROUP self --- @return DCSGroup#Group.Category The category ID -function GROUP:GetCategory() - self:F2( self.GroupName ) - - local DCSGroup = self:GetDCSObject() - if DCSGroup then - local GroupCategory = DCSGroup:getCategory() - self:T3( GroupCategory ) - return GroupCategory - end - - return nil -end - ---- Returns the category name of the DCS Group. --- @param #GROUP self --- @return #string Category name = Helicopter, Airplane, Ground Unit, Ship -function GROUP:GetCategoryName() - self:F2( self.GroupName ) - - local DCSGroup = self:GetDCSObject() - if DCSGroup then - local CategoryNames = { - [Group.Category.AIRPLANE] = "Airplane", - [Group.Category.HELICOPTER] = "Helicopter", - [Group.Category.GROUND] = "Ground Unit", - [Group.Category.SHIP] = "Ship", - } - local GroupCategory = DCSGroup:getCategory() - self:T3( GroupCategory ) - - return CategoryNames[GroupCategory] - end - - return nil -end - - ---- Returns the coalition of the DCS Group. --- @param #GROUP self --- @return DCSCoalitionObject#coalition.side The coalition side of the DCS Group. -function GROUP:GetCoalition() - self:F2( self.GroupName ) - - local DCSGroup = self:GetDCSObject() - if DCSGroup then - local GroupCoalition = DCSGroup:getCoalition() - self:T3( GroupCoalition ) - return GroupCoalition - end - - return nil -end - ---- Returns the country of the DCS Group. --- @param #GROUP self --- @return DCScountry#country.id The country identifier. --- @return #nil The DCS Group is not existing or alive. -function GROUP:GetCountry() - self:F2( self.GroupName ) - - local DCSGroup = self:GetDCSObject() - if DCSGroup then - local GroupCountry = DCSGroup:getUnit(1):getCountry() - self:T3( GroupCountry ) - return GroupCountry - end - - return nil -end - ---- Returns the UNIT wrapper class with number UnitNumber. --- If the underlying DCS Unit does not exist, the method will return nil. . --- @param #GROUP self --- @param #number UnitNumber The number of the UNIT wrapper class to be returned. --- @return Unit#UNIT The UNIT wrapper class. -function GROUP:GetUnit( UnitNumber ) - self:F2( { self.GroupName, UnitNumber } ) - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local UnitFound = UNIT:Find( DCSGroup:getUnit( UnitNumber ) ) - self:T3( UnitFound.UnitName ) - self:T2( UnitFound ) - return UnitFound - end - - return nil -end - ---- Returns the DCS Unit with number UnitNumber. --- If the underlying DCS Unit does not exist, the method will return nil. . --- @param #GROUP self --- @param #number UnitNumber The number of the DCS Unit to be returned. --- @return DCSUnit#Unit The DCS Unit. -function GROUP:GetDCSUnit( UnitNumber ) - self:F2( { self.GroupName, UnitNumber } ) - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local DCSUnitFound = DCSGroup:getUnit( UnitNumber ) - self:T3( DCSUnitFound ) - return DCSUnitFound - end - - return nil -end - ---- Returns current size of the DCS Group. --- If some of the DCS Units of the DCS Group are destroyed the size of the DCS Group is changed. --- @param #GROUP self --- @return #number The DCS Group size. -function GROUP:GetSize() - self:F2( { self.GroupName } ) - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local GroupSize = DCSGroup:getSize() - self:T3( GroupSize ) - return GroupSize - end - - return nil -end - ---- ---- Returns the initial size of the DCS Group. --- If some of the DCS Units of the DCS Group are destroyed, the initial size of the DCS Group is unchanged. --- @param #GROUP self --- @return #number The DCS Group initial size. -function GROUP:GetInitialSize() - self:F2( { self.GroupName } ) - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local GroupInitialSize = DCSGroup:getInitialSize() - self:T3( GroupInitialSize ) - return GroupInitialSize - end - - return nil -end - ---- Returns the UNITs wrappers of the DCS Units of the DCS Group. --- @param #GROUP self --- @return #table The UNITs wrappers. -function GROUP:GetUnits() - self:F2( { self.GroupName } ) - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local DCSUnits = DCSGroup:getUnits() - local Units = {} - for Index, UnitData in pairs( DCSUnits ) do - Units[#Units+1] = UNIT:Find( UnitData ) - end - self:T3( Units ) - return Units - end - - return nil -end - - ---- Returns the DCS Units of the DCS Group. --- @param #GROUP self --- @return #table The DCS Units. -function GROUP:GetDCSUnits() - self:F2( { self.GroupName } ) - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local DCSUnits = DCSGroup:getUnits() - self:T3( DCSUnits ) - return DCSUnits - end - - return nil -end - - ---- Activates a GROUP. --- @param #GROUP self -function GROUP:Activate() - self:F2( { self.GroupName } ) - trigger.action.activateGroup( self:GetDCSObject() ) - return self:GetDCSObject() -end - - ---- Gets the type name of the group. --- @param #GROUP self --- @return #string The type name of the group. -function GROUP:GetTypeName() - self:F2( self.GroupName ) - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local GroupTypeName = DCSGroup:getUnit(1):getTypeName() - self:T3( GroupTypeName ) - return( GroupTypeName ) - end - - return nil -end - ---- Gets the CallSign of the first DCS Unit of the DCS Group. --- @param #GROUP self --- @return #string The CallSign of the first DCS Unit of the DCS Group. -function GROUP:GetCallsign() - self:F2( self.GroupName ) - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local GroupCallSign = DCSGroup:getUnit(1):getCallsign() - self:T3( GroupCallSign ) - return GroupCallSign - end - - return nil -end - ---- Returns the current point (Vec2 vector) of the first DCS Unit in the DCS Group. --- @param #GROUP self --- @return DCSTypes#Vec2 Current Vec2 point of the first DCS Unit of the DCS Group. -function GROUP:GetVec2() - self:F2( self.GroupName ) - - local UnitPoint = self:GetUnit(1) - UnitPoint:GetVec2() - local GroupPointVec2 = UnitPoint:GetVec2() - self:T3( GroupPointVec2 ) - return GroupPointVec2 -end - ---- Returns the current point (Vec3 vector) of the first DCS Unit in the DCS Group. --- @return DCSTypes#Vec3 Current Vec3 point of the first DCS Unit of the DCS Group. -function GROUP:GetPointVec3() - self:F2( self.GroupName ) - - local GroupPointVec3 = self:GetUnit(1):GetPointVec3() - self:T3( GroupPointVec3 ) - return GroupPointVec3 -end - - - --- Is Zone Functions - ---- Returns true if all units of the group are within a @{Zone}. --- @param #GROUP self --- @param Zone#ZONE_BASE Zone The zone to test. --- @return #boolean Returns true if the Group is completely within the @{Zone#ZONE_BASE} -function GROUP:IsCompletelyInZone( Zone ) - self:F2( { self.GroupName, Zone } ) - - for UnitID, UnitData in pairs( self:GetUnits() ) do - local Unit = UnitData -- Unit#UNIT - if Zone:IsPointVec3InZone( Unit:GetPointVec3() ) then - else - return false - end - end - - return true -end - ---- Returns true if some units of the group are within a @{Zone}. --- @param #GROUP self --- @param Zone#ZONE_BASE Zone The zone to test. --- @return #boolean Returns true if the Group is completely within the @{Zone#ZONE_BASE} -function GROUP:IsPartlyInZone( Zone ) - self:F2( { self.GroupName, Zone } ) - - for UnitID, UnitData in pairs( self:GetUnits() ) do - local Unit = UnitData -- Unit#UNIT - if Zone:IsPointVec3InZone( Unit:GetPointVec3() ) then - return true - end - end - - return false -end - ---- Returns true if none of the group units of the group are within a @{Zone}. --- @param #GROUP self --- @param Zone#ZONE_BASE Zone The zone to test. --- @return #boolean Returns true if the Group is completely within the @{Zone#ZONE_BASE} -function GROUP:IsNotInZone( Zone ) - self:F2( { self.GroupName, Zone } ) - - for UnitID, UnitData in pairs( self:GetUnits() ) do - local Unit = UnitData -- Unit#UNIT - if Zone:IsPointVec3InZone( Unit:GetPointVec3() ) then - return false - end - end - - return true -end - ---- Returns if the group is of an air category. --- If the group is a helicopter or a plane, then this method will return true, otherwise false. --- @param #GROUP self --- @return #boolean Air category evaluation result. -function GROUP:IsAir() - self:F2( self.GroupName ) - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local IsAirResult = DCSGroup:getCategory() == Group.Category.AIRPLANE or DCSGroup:getCategory() == Group.Category.HELICOPTER - self:T3( IsAirResult ) - return IsAirResult - end - - return nil -end - ---- Returns if the DCS Group contains Helicopters. --- @param #GROUP self --- @return #boolean true if DCS Group contains Helicopters. -function GROUP:IsHelicopter() - self:F2( self.GroupName ) - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local GroupCategory = DCSGroup:getCategory() - self:T2( GroupCategory ) - return GroupCategory == Group.Category.HELICOPTER - end - - return nil -end - ---- Returns if the DCS Group contains AirPlanes. --- @param #GROUP self --- @return #boolean true if DCS Group contains AirPlanes. -function GROUP:IsAirPlane() - self:F2() - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local GroupCategory = DCSGroup:getCategory() - self:T2( GroupCategory ) - return GroupCategory == Group.Category.AIRPLANE - end - - return nil -end - ---- Returns if the DCS Group contains Ground troops. --- @param #GROUP self --- @return #boolean true if DCS Group contains Ground troops. -function GROUP:IsGround() - self:F2() - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local GroupCategory = DCSGroup:getCategory() - self:T2( GroupCategory ) - return GroupCategory == Group.Category.GROUND - end - - return nil -end - ---- Returns if the DCS Group contains Ships. --- @param #GROUP self --- @return #boolean true if DCS Group contains Ships. -function GROUP:IsShip() - self:F2() - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local GroupCategory = DCSGroup:getCategory() - self:T2( GroupCategory ) - return GroupCategory == Group.Category.SHIP - end - - return nil -end - ---- Returns if all units of the group are on the ground or landed. --- If all units of this group are on the ground, this function will return true, otherwise false. --- @param #GROUP self --- @return #boolean All units on the ground result. -function GROUP:AllOnGround() - self:F2() - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local AllOnGroundResult = true - - for Index, UnitData in pairs( DCSGroup:getUnits() ) do - if UnitData:inAir() then - AllOnGroundResult = false - end - end - - self:T3( AllOnGroundResult ) - return AllOnGroundResult - end - - return nil -end - ---- Returns the current maximum velocity of the group. --- Each unit within the group gets evaluated, and the maximum velocity (= the unit which is going the fastest) is returned. --- @param #GROUP self --- @return #number Maximum velocity found. -function GROUP:GetMaxVelocity() - self:F2() - - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - local MaxVelocity = 0 - - for Index, UnitData in pairs( DCSGroup:getUnits() ) do - - local Velocity = UnitData:getVelocity() - local VelocityTotal = math.abs( Velocity.x ) + math.abs( Velocity.y ) + math.abs( Velocity.z ) - - if VelocityTotal < MaxVelocity then - MaxVelocity = VelocityTotal - end - end - - return MaxVelocity - end - - return nil -end - ---- Returns the current minimum height of the group. --- Each unit within the group gets evaluated, and the minimum height (= the unit which is the lowest elevated) is returned. --- @param #GROUP self --- @return #number Minimum height found. -function GROUP:GetMinHeight() - self:F2() - -end - ---- Returns the current maximum height of the group. --- Each unit within the group gets evaluated, and the maximum height (= the unit which is the highest elevated) is returned. --- @param #GROUP self --- @return #number Maximum height found. -function GROUP:GetMaxHeight() - self:F2() - -end - --- SPAWNING - ---- Respawn the @{GROUP} using a (tweaked) template of the Group. --- The template must be retrieved with the @{Group#GROUP.GetTemplate}() function. --- The template contains all the definitions as declared within the mission file. --- To understand templates, do the following: --- --- * unpack your .miz file into a directory using 7-zip. --- * browse in the directory created to the file **mission**. --- * open the file and search for the country group definitions. --- --- Your group template will contain the fields as described within the mission file. --- --- This function will: --- --- * Get the current position and heading of the group. --- * When the group is alive, it will tweak the template x, y and heading coordinates of the group and the embedded units to the current units positions. --- * Then it will destroy the current alive group. --- * And it will respawn the group using your new template definition. --- @param Group#GROUP self --- @param #table Template The template of the Group retrieved with GROUP:GetTemplate() -function GROUP:Respawn( Template ) - - local Vec3 = self:GetPointVec3() - Template.x = Vec3.x - Template.y = Vec3.z - --Template.x = nil - --Template.y = nil - - self:E( #Template.units ) - for UnitID, UnitData in pairs( self:GetUnits() ) do - local GroupUnit = UnitData -- Unit#UNIT - self:E( GroupUnit:GetName() ) - if GroupUnit:IsAlive() then - local GroupUnitVec3 = GroupUnit:GetPointVec3() - local GroupUnitHeading = GroupUnit:GetHeading() - Template.units[UnitID].alt = GroupUnitVec3.y - Template.units[UnitID].x = GroupUnitVec3.x - Template.units[UnitID].y = GroupUnitVec3.z - Template.units[UnitID].heading = GroupUnitHeading - self:E( { UnitID, Template.units[UnitID], Template.units[UnitID] } ) - end - end - - self:Destroy() - _DATABASE:Spawn( Template ) -end - ---- Returns the group template from the @{DATABASE} (_DATABASE object). --- @param #GROUP self --- @return #table -function GROUP:GetTemplate() - local GroupName = self:GetName() - self:E( GroupName ) - return _DATABASE:GetGroupTemplate( GroupName ) -end - ---- Sets the controlled status in a Template. --- @param #GROUP self --- @param #boolean Controlled true is controlled, false is uncontrolled. --- @return #table -function GROUP:SetTemplateControlled( Template, Controlled ) - Template.uncontrolled = not Controlled - return Template -end - ---- Sets the CountryID of the group in a Template. --- @param #GROUP self --- @param DCScountry#country.id CountryID The country ID. --- @return #table -function GROUP:SetTemplateCountry( Template, CountryID ) - Template.CountryID = CountryID - return Template -end - ---- Sets the CoalitionID of the group in a Template. --- @param #GROUP self --- @param DCSCoalitionObject#coalition.side CoalitionID The coalition ID. --- @return #table -function GROUP:SetTemplateCoalition( Template, CoalitionID ) - Template.CoalitionID = CoalitionID - return Template -end - - - - ---- Return the mission template of the group. --- @param #GROUP self --- @return #table The MissionTemplate -function GROUP:GetTaskMission() - self:F2( self.GroupName ) - - return routines.utils.deepCopy( _DATABASE.Templates.Groups[self.GroupName].Template ) -end - ---- Return the mission route of the group. --- @param #GROUP self --- @return #table The mission route defined by points. -function GROUP:GetTaskRoute() - self:F2( self.GroupName ) - - return routines.utils.deepCopy( _DATABASE.Templates.Groups[self.GroupName].Template.route.points ) -end - ---- Return the route of a group by using the @{Database#DATABASE} class. --- @param #GROUP self --- @param #number Begin The route point from where the copy will start. The base route point is 0. --- @param #number End The route point where the copy will end. The End point is the last point - the End point. The last point has base 0. --- @param #boolean Randomize Randomization of the route, when true. --- @param #number Radius When randomization is on, the randomization is within the radius. -function GROUP:CopyRoute( Begin, End, Randomize, Radius ) - self:F2( { Begin, End } ) - - local Points = {} - - -- Could be a Spawned Group - local GroupName = string.match( self:GetName(), ".*#" ) - if GroupName then - GroupName = GroupName:sub( 1, -2 ) - else - GroupName = self:GetName() - end - - self:T3( { GroupName } ) - - local Template = _DATABASE.Templates.Groups[GroupName].Template - - if Template then - if not Begin then - Begin = 0 - end - if not End then - End = 0 - end - - for TPointID = Begin + 1, #Template.route.points - End do - if Template.route.points[TPointID] then - Points[#Points+1] = routines.utils.deepCopy( Template.route.points[TPointID] ) - if Randomize then - if not Radius then - Radius = 500 - end - Points[#Points].x = Points[#Points].x + math.random( Radius * -1, Radius ) - Points[#Points].y = Points[#Points].y + math.random( Radius * -1, Radius ) - end - end - end - return Points - else - error( "Template not found for Group : " .. GroupName ) - end - - return nil -end - - ---- This module contains the UNIT class. --- --- 1) @{Unit#UNIT} class, extends @{Controllable#CONTROLLABLE} --- =========================================================== --- The @{Unit#UNIT} class is a wrapper class to handle the DCS Unit objects: --- --- * Support all DCS Unit APIs. --- * Enhance with Unit specific APIs not in the DCS Unit API set. --- * Handle local Unit Controller. --- * Manage the "state" of the DCS Unit. --- --- --- 1.1) UNIT reference methods --- ---------------------- --- For each DCS Unit object alive within a running mission, a UNIT wrapper object (instance) will be created within the _@{DATABASE} object. --- This is done at the beginning of the mission (when the mission starts), and dynamically when new DCS Unit objects are spawned (using the @{SPAWN} class). --- --- The UNIT class **does not contain a :New()** method, rather it provides **:Find()** methods to retrieve the object reference --- using the DCS Unit or the DCS UnitName. --- --- Another thing to know is that UNIT objects do not "contain" the DCS Unit object. --- The UNIT methods will reference the DCS Unit object by name when it is needed during API execution. --- If the DCS Unit object does not exist or is nil, the UNIT methods will return nil and log an exception in the DCS.log file. --- --- The UNIT class provides the following functions to retrieve quickly the relevant UNIT instance: --- --- * @{#UNIT.Find}(): Find a UNIT instance from the _DATABASE object using a DCS Unit object. --- * @{#UNIT.FindByName}(): Find a UNIT instance from the _DATABASE object using a DCS Unit name. --- --- IMPORTANT: ONE SHOULD NEVER SANATIZE these UNIT OBJECT REFERENCES! (make the UNIT object references nil). --- --- 1.2) DCS UNIT APIs --- ------------------ --- The DCS Unit APIs are used extensively within MOOSE. The UNIT class has for each DCS Unit API a corresponding method. --- To be able to distinguish easily in your code the difference between a UNIT API call and a DCS Unit API call, --- the first letter of the method is also capitalized. So, by example, the DCS Unit method @{DCSUnit#Unit.getName}() --- is implemented in the UNIT class as @{#UNIT.GetName}(). --- --- 1.3) Smoke, Flare Units --- ----------------------- --- The UNIT class provides methods to smoke or flare units easily. --- The @{#UNIT.SmokeBlue}(), @{#UNIT.SmokeGreen}(),@{#UNIT.SmokeOrange}(), @{#UNIT.SmokeRed}(), @{#UNIT.SmokeRed}() methods --- will smoke the unit in the corresponding color. Note that smoking a unit is done at the current position of the DCS Unit. --- When the DCS Unit moves for whatever reason, the smoking will still continue! --- The @{#UNIT.FlareGreen}(), @{#UNIT.FlareRed}(), @{#UNIT.FlareWhite}(), @{#UNIT.FlareYellow}() --- methods will fire off a flare in the air with the corresponding color. Note that a flare is a one-off shot and its effect is of very short duration. --- --- 1.4) Location Position, Point --- ----------------------------- --- The UNIT class provides methods to obtain the current point or position of the DCS Unit. --- The @{#UNIT.GetPointVec2}(), @{#UNIT.GetPointVec3}() will obtain the current **location** of the DCS Unit in a Vec2 (2D) or a **point** in a Vec3 (3D) vector respectively. --- If you want to obtain the complete **3D position** including ori�ntation and direction vectors, consult the @{#UNIT.GetPositionVec3}() method respectively. --- --- 1.5) Test if alive --- ------------------ --- The @{#UNIT.IsAlive}(), @{#UNIT.IsActive}() methods determines if the DCS Unit is alive, meaning, it is existing and active. --- --- 1.6) Test for proximity --- ----------------------- --- The UNIT class contains methods to test the location or proximity against zones or other objects. --- --- ### 1.6.1) Zones --- To test whether the Unit is within a **zone**, use the @{#UNIT.IsInZone}() or the @{#UNIT.IsNotInZone}() methods. Any zone can be tested on, but the zone must be derived from @{Zone#ZONE_BASE}. --- --- ### 1.6.2) Units --- Test if another DCS Unit is within a given radius of the current DCS Unit, use the @{#UNIT.OtherUnitInRadius}() method. --- --- @module Unit --- @author FlightControl - - - - - ---- The UNIT class --- @type UNIT --- @extends Controllable#CONTROLLABLE --- @field #UNIT.FlareColor FlareColor --- @field #UNIT.SmokeColor SmokeColor -UNIT = { - ClassName="UNIT", - FlareColor = { - Green = trigger.flareColor.Green, - Red = trigger.flareColor.Red, - White = trigger.flareColor.White, - Yellow = trigger.flareColor.Yellow - }, - SmokeColor = { - Green = trigger.smokeColor.Green, - Red = trigger.smokeColor.Red, - White = trigger.smokeColor.White, - Orange = trigger.smokeColor.Orange, - Blue = trigger.smokeColor.Blue - }, - } - ---- FlareColor --- @type UNIT.FlareColor --- @field Green --- @field Red --- @field White --- @field Yellow - ---- SmokeColor --- @type UNIT.SmokeColor --- @field Green --- @field Red --- @field White --- @field Orange --- @field Blue - ---- Unit.SensorType --- @type Unit.SensorType --- @field OPTIC --- @field RADAR --- @field IRST --- @field RWR - - --- Registration. - ---- Create a new UNIT from DCSUnit. --- @param #UNIT self --- @param #string UnitName The name of the DCS unit. --- @return Unit#UNIT -function UNIT:Register( UnitName ) - local self = BASE:Inherit( self, CONTROLLABLE:New( UnitName ) ) - self.UnitName = UnitName - return self -end - --- Reference methods. - ---- Finds a UNIT from the _DATABASE using a DCSUnit object. --- @param #UNIT self --- @param DCSUnit#Unit DCSUnit An existing DCS Unit object reference. --- @return Unit#UNIT self -function UNIT:Find( DCSUnit ) - - local UnitName = DCSUnit:getName() - local UnitFound = _DATABASE:FindUnit( UnitName ) - return UnitFound -end - ---- Find a UNIT in the _DATABASE using the name of an existing DCS Unit. --- @param #UNIT self --- @param #string UnitName The Unit Name. --- @return Unit#UNIT self -function UNIT:FindByName( UnitName ) - - local UnitFound = _DATABASE:FindUnit( UnitName ) - return UnitFound -end - - ---- @param #UNIT self --- @return DCSUnit#Unit -function UNIT:GetDCSObject() - - local DCSUnit = Unit.getByName( self.UnitName ) - - if DCSUnit then - return DCSUnit - end - - return nil -end - - - - ---- Returns if the unit is activated. --- @param Unit#UNIT self --- @return #boolean true if Unit is activated. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:IsActive() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - - local UnitIsActive = DCSUnit:isActive() - return UnitIsActive - end - - return nil -end - ---- Destroys the @{Unit}. --- @param Unit#UNIT self --- @return #nil The DCS Unit is not existing or alive. -function UNIT:Destroy() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - - DCSUnit:destroy() - end - - return nil -end - - - ---- Returns the Unit's callsign - the localized string. --- @param Unit#UNIT self --- @return #string The Callsign of the Unit. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetCallsign() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitCallSign = DCSUnit:getCallsign() - return UnitCallSign - end - - self:E( self.ClassName .. " " .. self.UnitName .. " not found!" ) - return nil -end - - ---- Returns name of the player that control the unit or nil if the unit is controlled by A.I. --- @param Unit#UNIT self --- @return #string Player Name --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetPlayerName() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - - local PlayerName = DCSUnit:getPlayerName() - if PlayerName == nil then - PlayerName = "" - end - return PlayerName - end - - return nil -end - ---- Returns the unit's number in the group. --- The number is the same number the unit has in ME. --- It may not be changed during the mission. --- If any unit in the group is destroyed, the numbers of another units will not be changed. --- @param Unit#UNIT self --- @return #number The Unit number. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetNumber() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitNumber = DCSUnit:getNumber() - return UnitNumber - end - - return nil -end - ---- Returns the unit's group if it exist and nil otherwise. --- @param Unit#UNIT self --- @return Group#GROUP The Group of the Unit. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetGroup() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitGroup = GROUP:Find( DCSUnit:getGroup() ) - return UnitGroup - end - - return nil -end - - --- Need to add here functions to check if radar is on and which object etc. - ---- Returns the prefix name of the DCS Unit. A prefix name is a part of the name before a '#'-sign. --- DCS Units spawned with the @{SPAWN} class contain a '#'-sign to indicate the end of the (base) DCS Unit name. --- The spawn sequence number and unit number are contained within the name after the '#' sign. --- @param Unit#UNIT self --- @return #string The name of the DCS Unit. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetPrefix() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitPrefix = string.match( self.UnitName, ".*#" ):sub( 1, -2 ) - self:T3( UnitPrefix ) - return UnitPrefix - end - - return nil -end - ---- Returns the Unit's ammunition. --- @param Unit#UNIT self --- @return DCSUnit#Unit.Ammo --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetAmmo() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitAmmo = DCSUnit:getAmmo() - return UnitAmmo - end - - return nil -end - ---- Returns the unit sensors. --- @param Unit#UNIT self --- @return DCSUnit#Unit.Sensors --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetSensors() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitSensors = DCSUnit:getSensors() - return UnitSensors - end - - return nil -end - --- Need to add here a function per sensortype --- unit:hasSensors(Unit.SensorType.RADAR, Unit.RadarType.AS) - ---- Returns if the unit has sensors of a certain type. --- @param Unit#UNIT self --- @return #boolean returns true if the unit has specified types of sensors. This function is more preferable than Unit.getSensors() if you don't want to get information about all the unit's sensors, and just want to check if the unit has specified types of sensors. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:HasSensors( ... ) - self:F2( arg ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local HasSensors = DCSUnit:hasSensors( unpack( arg ) ) - return HasSensors - end - - return nil -end - ---- Returns if the unit is SEADable. --- @param Unit#UNIT self --- @return #boolean returns true if the unit is SEADable. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:HasSEAD() - self:F2() - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitSEADAttributes = DCSUnit:getDesc().attributes - - local HasSEAD = false - if UnitSEADAttributes["RADAR_BAND1_FOR_ARM"] and UnitSEADAttributes["RADAR_BAND1_FOR_ARM"] == true or - UnitSEADAttributes["RADAR_BAND2_FOR_ARM"] and UnitSEADAttributes["RADAR_BAND2_FOR_ARM"] == true then - HasSEAD = true - end - return HasSEAD - end - - return nil -end - ---- Returns two values: --- --- * First value indicates if at least one of the unit's radar(s) is on. --- * Second value is the object of the radar's interest. Not nil only if at least one radar of the unit is tracking a target. --- @param Unit#UNIT self --- @return #boolean Indicates if at least one of the unit's radar(s) is on. --- @return DCSObject#Object The object of the radar's interest. Not nil only if at least one radar of the unit is tracking a target. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetRadar() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitRadarOn, UnitRadarObject = DCSUnit:getRadar() - return UnitRadarOn, UnitRadarObject - end - - return nil, nil -end - ---- Returns relative amount of fuel (from 0.0 to 1.0) the unit has in its internal tanks. If there are additional fuel tanks the value may be greater than 1.0. --- @param Unit#UNIT self --- @return #number The relative amount of fuel (from 0.0 to 1.0). --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetFuel() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitFuel = DCSUnit:getFuel() - return UnitFuel - end - - return nil -end - ---- Returns the unit's health. Dead units has health <= 1.0. --- @param Unit#UNIT self --- @return #number The Unit's health value. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetLife() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitLife = DCSUnit:getLife() - return UnitLife - end - - return nil -end - ---- Returns the Unit's initial health. --- @param Unit#UNIT self --- @return #number The Unit's initial health value. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:GetLife0() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitLife0 = DCSUnit:getLife0() - return UnitLife0 - end - - return nil -end - ---- Returns the Unit's A2G threat level on a scale from 1 to 10 ... --- The following threat levels are foreseen: --- --- * Threat level 0: Unit is unarmed. --- * Threat level 1: Unit is infantry. --- * Threat level 2: Unit is an infantry vehicle. --- * Threat level 3: Unit is ground artillery. --- * Threat level 4: Unit is a tank. --- * Threat level 5: Unit is a modern tank or ifv with ATGM. --- * Threat level 6: Unit is a AAA. --- * Threat level 7: Unit is a SAM or manpad, IR guided. --- * Threat level 8: Unit is a Short Range SAM, radar guided. --- * Threat level 9: Unit is a Medium Range SAM, radar guided. --- * Threat level 10: Unit is a Long Range SAM, radar guided. -function UNIT:GetThreatLevel() - - local Attributes = self:GetDesc().attributes - local ThreatLevel = 0 - - local ThreatLevels = { - "Unarmed", - "Infantry", - "Old Tanks & APCs", - "Tanks & IFVs without ATGM", - "Tanks & IFV with ATGM", - "Modern Tanks", - "AAA", - "IR Guided SAMs", - "SR SAMs", - "MR SAMs", - "LR SAMs" - } - - self:T2( Attributes ) - - if Attributes["LR SAM"] then ThreatLevel = 10 - elseif Attributes["MR SAM"] then ThreatLevel = 9 - elseif Attributes["SR SAM"] and - not Attributes["IR Guided SAM"] then ThreatLevel = 8 - elseif ( Attributes["SR SAM"] or Attributes["MANPADS"] ) and - Attributes["IR Guided SAM"] then ThreatLevel = 7 - elseif Attributes["AAA"] then ThreatLevel = 6 - elseif Attributes["Modern Tanks"] then ThreatLevel = 5 - elseif ( Attributes["Tanks"] or Attributes["IFV"] ) and - Attributes["ATGM"] then ThreatLevel = 4 - elseif ( Attributes["Tanks"] or Attributes["IFV"] ) and - not Attributes["ATGM"] then ThreatLevel = 3 - elseif Attributes["Old Tanks"] or Attributes["APC"] then ThreatLevel = 2 - elseif Attributes["Infantry"] then ThreatLevel = 1 - end - - self:T2( ThreatLevel ) - return ThreatLevel, ThreatLevels[ThreatLevel+1] - -end - - --- Is functions - ---- Returns true if the unit is within a @{Zone}. --- @param #UNIT self --- @param Zone#ZONE_BASE Zone The zone to test. --- @return #boolean Returns true if the unit is within the @{Zone#ZONE_BASE} -function UNIT:IsInZone( Zone ) - self:F2( { self.UnitName, Zone } ) - - if self:IsAlive() then - local IsInZone = Zone:IsPointVec3InZone( self:GetPointVec3() ) - - self:T( { IsInZone } ) - return IsInZone - end - - return false -end - ---- Returns true if the unit is not within a @{Zone}. --- @param #UNIT self --- @param Zone#ZONE_BASE Zone The zone to test. --- @return #boolean Returns true if the unit is not within the @{Zone#ZONE_BASE} -function UNIT:IsNotInZone( Zone ) - self:F2( { self.UnitName, Zone } ) - - if self:IsAlive() then - local IsInZone = not Zone:IsPointVec3InZone( self:GetPointVec3() ) - - self:T( { IsInZone } ) - return IsInZone - else - return false - end -end - - ---- Returns true if there is an **other** DCS Unit within a radius of the current 2D point of the DCS Unit. --- @param Unit#UNIT self --- @param Unit#UNIT AwaitUnit The other UNIT wrapper object. --- @param Radius The radius in meters with the DCS Unit in the centre. --- @return true If the other DCS Unit is within the radius of the 2D point of the DCS Unit. --- @return #nil The DCS Unit is not existing or alive. -function UNIT:OtherUnitInRadius( AwaitUnit, Radius ) - self:F2( { self.UnitName, AwaitUnit.UnitName, Radius } ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitPos = self:GetPointVec3() - local AwaitUnitPos = AwaitUnit:GetPointVec3() - - if (((UnitPos.x - AwaitUnitPos.x)^2 + (UnitPos.z - AwaitUnitPos.z)^2)^0.5 <= Radius) then - self:T3( "true" ) - return true - else - self:T3( "false" ) - return false - end - end - - return nil -end - - - ---- Signal a flare at the position of the UNIT. --- @param #UNIT self -function UNIT:Flare( FlareColor ) - self:F2() - trigger.action.signalFlare( self:GetPointVec3(), FlareColor , 0 ) -end - ---- Signal a white flare at the position of the UNIT. --- @param #UNIT self -function UNIT:FlareWhite() - self:F2() - trigger.action.signalFlare( self:GetPointVec3(), trigger.flareColor.White , 0 ) -end - ---- Signal a yellow flare at the position of the UNIT. --- @param #UNIT self -function UNIT:FlareYellow() - self:F2() - trigger.action.signalFlare( self:GetPointVec3(), trigger.flareColor.Yellow , 0 ) -end - ---- Signal a green flare at the position of the UNIT. --- @param #UNIT self -function UNIT:FlareGreen() - self:F2() - trigger.action.signalFlare( self:GetPointVec3(), trigger.flareColor.Green , 0 ) -end - ---- Signal a red flare at the position of the UNIT. --- @param #UNIT self -function UNIT:FlareRed() - self:F2() - local Vec3 = self:GetPointVec3() - if Vec3 then - trigger.action.signalFlare( Vec3, trigger.flareColor.Red, 0 ) - end -end - ---- Smoke the UNIT. --- @param #UNIT self -function UNIT:Smoke( SmokeColor, Range ) - self:F2() - if Range then - trigger.action.smoke( self:GetRandomPointVec3( Range ), SmokeColor ) - else - trigger.action.smoke( self:GetPointVec3(), SmokeColor ) - end - -end - ---- Smoke the UNIT Green. --- @param #UNIT self -function UNIT:SmokeGreen() - self:F2() - trigger.action.smoke( self:GetPointVec3(), trigger.smokeColor.Green ) -end - ---- Smoke the UNIT Red. --- @param #UNIT self -function UNIT:SmokeRed() - self:F2() - trigger.action.smoke( self:GetPointVec3(), trigger.smokeColor.Red ) -end - ---- Smoke the UNIT White. --- @param #UNIT self -function UNIT:SmokeWhite() - self:F2() - trigger.action.smoke( self:GetPointVec3(), trigger.smokeColor.White ) -end - ---- Smoke the UNIT Orange. --- @param #UNIT self -function UNIT:SmokeOrange() - self:F2() - trigger.action.smoke( self:GetPointVec3(), trigger.smokeColor.Orange ) -end - ---- Smoke the UNIT Blue. --- @param #UNIT self -function UNIT:SmokeBlue() - self:F2() - trigger.action.smoke( self:GetPointVec3(), trigger.smokeColor.Blue ) -end - --- Is methods - ---- Returns if the unit is of an air category. --- If the unit is a helicopter or a plane, then this method will return true, otherwise false. --- @param #UNIT self --- @return #boolean Air category evaluation result. -function UNIT:IsAir() - self:F2() - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitDescriptor = DCSUnit:getDesc() - self:T3( { UnitDescriptor.category, Unit.Category.AIRPLANE, Unit.Category.HELICOPTER } ) - - local IsAirResult = ( UnitDescriptor.category == Unit.Category.AIRPLANE ) or ( UnitDescriptor.category == Unit.Category.HELICOPTER ) - - self:T3( IsAirResult ) - return IsAirResult - end - - return nil -end - ---- Returns if the unit is of an ground category. --- If the unit is a ground vehicle or infantry, this method will return true, otherwise false. --- @param #UNIT self --- @return #boolean Ground category evaluation result. -function UNIT:IsGround() - self:F2() - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitDescriptor = DCSUnit:getDesc() - self:T3( { UnitDescriptor.category, Unit.Category.GROUND_UNIT } ) - - local IsGroundResult = ( UnitDescriptor.category == Unit.Category.GROUND_UNIT ) - - self:T3( IsGroundResult ) - return IsGroundResult - end - - return nil -end - ---- Returns if the unit is a friendly unit. --- @param #UNIT self --- @return #boolean IsFriendly evaluation result. -function UNIT:IsFriendly( FriendlyCoalition ) - self:F2() - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitCoalition = DCSUnit:getCoalition() - self:T3( { UnitCoalition, FriendlyCoalition } ) - - local IsFriendlyResult = ( UnitCoalition == FriendlyCoalition ) - - self:E( IsFriendlyResult ) - return IsFriendlyResult - end - - return nil -end - ---- Returns if the unit is of a ship category. --- If the unit is a ship, this method will return true, otherwise false. --- @param #UNIT self --- @return #boolean Ship category evaluation result. -function UNIT:IsShip() - self:F2() - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitDescriptor = DCSUnit:getDesc() - self:T3( { UnitDescriptor.category, Unit.Category.SHIP } ) - - local IsShipResult = ( UnitDescriptor.category == Unit.Category.SHIP ) - - self:T3( IsShipResult ) - return IsShipResult - end - - return nil -end - ---- This module contains the ZONE classes, inherited from @{Zone#ZONE_BASE}. --- There are essentially two core functions that zones accomodate: --- --- * Test if an object is within the zone boundaries. --- * Provide the zone behaviour. Some zones are static, while others are moveable. --- --- The object classes are using the zone classes to test the zone boundaries, which can take various forms: --- --- * Test if completely within the zone. --- * Test if partly within the zone (for @{Group#GROUP} objects). --- * Test if not in the zone. --- * Distance to the nearest intersecting point of the zone. --- * Distance to the center of the zone. --- * ... --- --- Each of these ZONE classes have a zone name, and specific parameters defining the zone type: --- --- * @{Zone#ZONE_BASE}: The ZONE_BASE class defining the base for all other zone classes. --- * @{Zone#ZONE_RADIUS}: The ZONE_RADIUS class defined by a zone name, a location and a radius. --- * @{Zone#ZONE}: The ZONE class, defined by the zone name as defined within the Mission Editor. --- * @{Zone#ZONE_UNIT}: The ZONE_UNIT class defines by a zone around a @{Unit#UNIT} with a radius. --- * @{Zone#ZONE_GROUP}: The ZONE_GROUP class defines by a zone around a @{Group#GROUP} with a radius. --- * @{Zone#ZONE_POLYGON}: The ZONE_POLYGON class defines by a sequence of @{Group#GROUP} waypoints within the Mission Editor, forming a polygon. --- --- Each zone implements two polymorphic functions defined in @{Zone#ZONE_BASE}: --- --- * @{#ZONE_BASE.IsPointVec2InZone}: Returns if a location is within the zone. --- * @{#ZONE_BASE.IsPointVec3InZone}: Returns if a point is within the zone. --- --- === --- --- 1) @{Zone#ZONE_BASE} class, extends @{Base#BASE} --- ================================================ --- The ZONE_BASE class defining the base for all other zone classes. --- --- === --- --- 2) @{Zone#ZONE_RADIUS} class, extends @{Zone#ZONE_BASE} --- ======================================================= --- The ZONE_RADIUS class defined by a zone name, a location and a radius. --- --- === --- --- 3) @{Zone#ZONE} class, extends @{Zone#ZONE_RADIUS} --- ========================================== --- The ZONE class, defined by the zone name as defined within the Mission Editor. --- --- === --- --- 4) @{Zone#ZONE_UNIT} class, extends @{Zone#ZONE_RADIUS} --- ======================================================= --- The ZONE_UNIT class defined by a zone around a @{Unit#UNIT} with a radius. --- --- === --- --- 5) @{Zone#ZONE_GROUP} class, extends @{Zone#ZONE_RADIUS} --- ======================================================= --- The ZONE_GROUP class defines by a zone around a @{Group#GROUP} with a radius. The current leader of the group defines the center of the zone. --- --- === --- --- 6) @{Zone#ZONE_POLYGON} class, extends @{Zone#ZONE_BASE} --- ======================================================== --- The ZONE_POLYGON class defined by a sequence of @{Group#GROUP} waypoints within the Mission Editor, forming a polygon. --- --- === --- --- @module Zone --- @author FlightControl - - ---- The ZONE_BASE class --- @type ZONE_BASE --- @field #string ZoneName Name of the zone. --- @extends Base#BASE -ZONE_BASE = { - ClassName = "ZONE_BASE", - } - - ---- The ZONE_BASE.BoundingSquare --- @type ZONE_BASE.BoundingSquare --- @field DCSTypes#Distance x1 The lower x coordinate (left down) --- @field DCSTypes#Distance y1 The lower y coordinate (left down) --- @field DCSTypes#Distance x2 The higher x coordinate (right up) --- @field DCSTypes#Distance y2 The higher y coordinate (right up) - - ---- ZONE_BASE constructor --- @param #ZONE_BASE self --- @param #string ZoneName Name of the zone. --- @return #ZONE_BASE self -function ZONE_BASE:New( ZoneName ) - local self = BASE:Inherit( self, BASE:New() ) - self:F( ZoneName ) - - self.ZoneName = ZoneName - - return self -end - ---- Returns if a location is within the zone. --- @param #ZONE_BASE self --- @param DCSTypes#Vec2 Vec2 The location to test. --- @return #boolean true if the location is within the zone. -function ZONE_BASE:IsPointVec2InZone( Vec2 ) - self:F2( Vec2 ) - - return false -end - ---- Returns if a point is within the zone. --- @param #ZONE_BASE self --- @param DCSTypes#Vec3 PointVec3 The point to test. --- @return #boolean true if the point is within the zone. -function ZONE_BASE:IsPointVec3InZone( PointVec3 ) - self:F2( PointVec3 ) - - local InZone = self:IsPointVec2InZone( { x = PointVec3.x, y = PointVec3.z } ) - - return InZone -end - ---- Returns the Vec2 coordinate of the zone. --- @param #ZONE_BASE self --- @return #nil. -function ZONE_BASE:GetVec2() - self:F2( self.ZoneName ) - - return nil -end ---- Define a random @{DCSTypes#Vec2} within the zone. --- @param #ZONE_BASE self --- @return #nil The Vec2 coordinates. -function ZONE_BASE:GetRandomVec2() - return nil -end - ---- Get the bounding square the zone. --- @param #ZONE_BASE self --- @return #nil The bounding square. -function ZONE_BASE:GetBoundingSquare() - --return { x1 = 0, y1 = 0, x2 = 0, y2 = 0 } - return nil -end - - ---- Smokes the zone boundaries in a color. --- @param #ZONE_BASE self --- @param SmokeColor The smoke color. -function ZONE_BASE:SmokeZone( SmokeColor ) - self:F2( SmokeColor ) - -end - - ---- The ZONE_RADIUS class, defined by a zone name, a location and a radius. --- @type ZONE_RADIUS --- @field DCSTypes#Vec2 Vec2 The current location of the zone. --- @field DCSTypes#Distance Radius The radius of the zone. --- @extends Zone#ZONE_BASE -ZONE_RADIUS = { - ClassName="ZONE_RADIUS", - } - ---- Constructor of ZONE_RADIUS, taking the zone name, the zone location and a radius. --- @param #ZONE_RADIUS self --- @param #string ZoneName Name of the zone. --- @param DCSTypes#Vec2 Vec2 The location of the zone. --- @param DCSTypes#Distance Radius The radius of the zone. --- @return #ZONE_RADIUS self -function ZONE_RADIUS:New( ZoneName, Vec2, Radius ) - local self = BASE:Inherit( self, ZONE_BASE:New( ZoneName ) ) - self:F( { ZoneName, Vec2, Radius } ) - - self.Radius = Radius - self.Vec2 = Vec2 - - return self -end - ---- Smokes the zone boundaries in a color. --- @param #ZONE_RADIUS self --- @param #POINT_VEC3.SmokeColor SmokeColor The smoke color. --- @param #number Points (optional) The amount of points in the circle. --- @return #ZONE_RADIUS self -function ZONE_RADIUS:SmokeZone( SmokeColor, Points ) - self:F2( SmokeColor ) - - local Point = {} - local Vec2 = self:GetVec2() - - Points = Points and Points or 360 - - local Angle - local RadialBase = math.pi*2 - - for Angle = 0, 360, 360 / Points do - local Radial = Angle * RadialBase / 360 - Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius() - Point.y = Vec2.y + math.sin( Radial ) * self:GetRadius() - POINT_VEC2:New( Point.x, Point.y ):Smoke( SmokeColor ) - end - - return self -end - - ---- Flares the zone boundaries in a color. --- @param #ZONE_RADIUS self --- @param #POINT_VEC3.FlareColor FlareColor The flare color. --- @param #number Points (optional) The amount of points in the circle. --- @param DCSTypes#Azimuth Azimuth (optional) Azimuth The azimuth of the flare. --- @return #ZONE_RADIUS self -function ZONE_RADIUS:FlareZone( FlareColor, Points, Azimuth ) - self:F2( { FlareColor, Azimuth } ) - - local Point = {} - local Vec2 = self:GetVec2() - - Points = Points and Points or 360 - - local Angle - local RadialBase = math.pi*2 - - for Angle = 0, 360, 360 / Points do - local Radial = Angle * RadialBase / 360 - Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius() - Point.y = Vec2.y + math.sin( Radial ) * self:GetRadius() - POINT_VEC2:New( Point.x, Point.y ):Flare( FlareColor, Azimuth ) - end - - return self -end - ---- Returns the radius of the zone. --- @param #ZONE_RADIUS self --- @return DCSTypes#Distance The radius of the zone. -function ZONE_RADIUS:GetRadius() - self:F2( self.ZoneName ) - - self:T2( { self.Radius } ) - - return self.Radius -end - ---- Sets the radius of the zone. --- @param #ZONE_RADIUS self --- @param DCSTypes#Distance Radius The radius of the zone. --- @return DCSTypes#Distance The radius of the zone. -function ZONE_RADIUS:SetRadius( Radius ) - self:F2( self.ZoneName ) - - self.Radius = Radius - self:T2( { self.Radius } ) - - return self.Radius -end - ---- Returns the location of the zone. --- @param #ZONE_RADIUS self --- @return DCSTypes#Vec2 The location of the zone. -function ZONE_RADIUS:GetVec2() - self:F2( self.ZoneName ) - - self:T2( { self.Vec2 } ) - - return self.Vec2 -end - ---- Sets the location of the zone. --- @param #ZONE_RADIUS self --- @param DCSTypes#Vec2 Vec2 The new location of the zone. --- @return DCSTypes#Vec2 The new location of the zone. -function ZONE_RADIUS:SetPointVec2( Vec2 ) - self:F2( self.ZoneName ) - - self.Vec2 = Vec2 - - self:T2( { self.Vec2 } ) - - return self.Vec2 -end - ---- Returns the point of the zone. --- @param #ZONE_RADIUS self --- @param DCSTypes#Distance Height The height to add to the land height where the center of the zone is located. --- @return DCSTypes#Vec3 The point of the zone. -function ZONE_RADIUS:GetPointVec3( Height ) - self:F2( self.ZoneName ) - - local Vec2 = self:GetVec2() - - local PointVec3 = { x = Vec2.x, y = land.getHeight( self:GetVec2() ) + Height, z = Vec2.y } - - self:T2( { PointVec3 } ) - - return PointVec3 -end - - ---- Returns if a location is within the zone. --- @param #ZONE_RADIUS self --- @param DCSTypes#Vec2 Vec2 The location to test. --- @return #boolean true if the location is within the zone. -function ZONE_RADIUS:IsPointVec2InZone( Vec2 ) - self:F2( Vec2 ) - - local ZoneVec2 = self:GetVec2() - - if ZoneVec2 then - if (( Vec2.x - ZoneVec2.x )^2 + ( Vec2.y - ZoneVec2.y ) ^2 ) ^ 0.5 <= self:GetRadius() then - return true - end - end - - return false -end - ---- Returns if a point is within the zone. --- @param #ZONE_RADIUS self --- @param DCSTypes#Vec3 PointVec3 The point to test. --- @return #boolean true if the point is within the zone. -function ZONE_RADIUS:IsPointVec3InZone( Vec3 ) - self:F2( Vec3 ) - - local InZone = self:IsPointVec2InZone( { x = Vec3.x, y = Vec3.z } ) - - return InZone -end - ---- Returns a random location within the zone. --- @param #ZONE_RADIUS self --- @return DCSTypes#Vec2 The random location within the zone. -function ZONE_RADIUS:GetRandomVec2() - self:F( self.ZoneName ) - - local Point = {} - local Vec2 = self:GetVec2() - - local angle = math.random() * math.pi*2; - Point.x = Vec2.x + math.cos( angle ) * math.random() * self:GetRadius(); - Point.y = Vec2.y + math.sin( angle ) * math.random() * self:GetRadius(); - - self:T( { Point } ) - - return Point -end - - - ---- The ZONE class, defined by the zone name as defined within the Mission Editor. The location and the radius are automatically collected from the mission settings. --- @type ZONE --- @extends Zone#ZONE_RADIUS -ZONE = { - ClassName="ZONE", - } - - ---- Constructor of ZONE, taking the zone name. --- @param #ZONE self --- @param #string ZoneName The name of the zone as defined within the mission editor. --- @return #ZONE -function ZONE:New( ZoneName ) - - local Zone = trigger.misc.getZone( ZoneName ) - - if not Zone then - error( "Zone " .. ZoneName .. " does not exist." ) - return nil - end - - local self = BASE:Inherit( self, ZONE_RADIUS:New( ZoneName, { x = Zone.point.x, y = Zone.point.z }, Zone.radius ) ) - self:F( ZoneName ) - - self.Zone = Zone - - return self -end - - ---- The ZONE_UNIT class defined by a zone around a @{Unit#UNIT} with a radius. --- @type ZONE_UNIT --- @field Unit#UNIT ZoneUNIT --- @extends Zone#ZONE_RADIUS -ZONE_UNIT = { - ClassName="ZONE_UNIT", - } - ---- Constructor to create a ZONE_UNIT instance, taking the zone name, a zone unit and a radius. --- @param #ZONE_UNIT self --- @param #string ZoneName Name of the zone. --- @param Unit#UNIT ZoneUNIT The unit as the center of the zone. --- @param DCSTypes#Distance Radius The radius of the zone. --- @return #ZONE_UNIT self -function ZONE_UNIT:New( ZoneName, ZoneUNIT, Radius ) - local self = BASE:Inherit( self, ZONE_RADIUS:New( ZoneName, ZoneUNIT:GetVec2(), Radius ) ) - self:F( { ZoneName, ZoneUNIT:GetVec2(), Radius } ) - - self.ZoneUNIT = ZoneUNIT - self.LastVec2 = ZoneUNIT:GetVec2() - - return self -end - - ---- Returns the current location of the @{Unit#UNIT}. --- @param #ZONE_UNIT self --- @return DCSTypes#Vec2 The location of the zone based on the @{Unit#UNIT}location. -function ZONE_UNIT:GetVec2() - self:F( self.ZoneName ) - - local ZoneVec2 = self.ZoneUNIT:GetVec2() - if ZoneVec2 then - self.LastVec2 = ZoneVec2 - return ZoneVec2 - else - return self.LastVec2 - end - - self:T( { ZoneVec2 } ) - - return nil -end - ---- Returns a random location within the zone. --- @param #ZONE_UNIT self --- @return DCSTypes#Vec2 The random location within the zone. -function ZONE_UNIT:GetRandomVec2() - self:F( self.ZoneName ) - - local Point = {} - local PointVec2 = self.ZoneUNIT:GetPointVec2() - if not PointVec2 then - PointVec2 = self.LastVec2 - end - - local angle = math.random() * math.pi*2; - Point.x = PointVec2.x + math.cos( angle ) * math.random() * self:GetRadius(); - Point.y = PointVec2.y + math.sin( angle ) * math.random() * self:GetRadius(); - - self:T( { Point } ) - - return Point -end - ---- Returns the point of the zone. --- @param #ZONE_RADIUS self --- @param DCSTypes#Distance Height The height to add to the land height where the center of the zone is located. --- @return DCSTypes#Vec3 The point of the zone. -function ZONE_UNIT:GetPointVec3( Height ) - self:F2( self.ZoneName ) - - Height = Height or 0 - - local Vec2 = self:GetVec2() - - local PointVec3 = { x = Vec2.x, y = land.getHeight( self:GetVec2() ) + Height, z = Vec2.y } - - self:T2( { PointVec3 } ) - - return PointVec3 -end - ---- The ZONE_GROUP class defined by a zone around a @{Group}, taking the average center point of all the units within the Group, with a radius. --- @type ZONE_GROUP --- @field Group#GROUP ZoneGROUP --- @extends Zone#ZONE_RADIUS -ZONE_GROUP = { - ClassName="ZONE_GROUP", - } - ---- Constructor to create a ZONE_GROUP instance, taking the zone name, a zone @{Group#GROUP} and a radius. --- @param #ZONE_GROUP self --- @param #string ZoneName Name of the zone. --- @param Group#GROUP ZoneGROUP The @{Group} as the center of the zone. --- @param DCSTypes#Distance Radius The radius of the zone. --- @return #ZONE_GROUP self -function ZONE_GROUP:New( ZoneName, ZoneGROUP, Radius ) - local self = BASE:Inherit( self, ZONE_RADIUS:New( ZoneName, ZoneGROUP:GetVec2(), Radius ) ) - self:F( { ZoneName, ZoneGROUP:GetVec2(), Radius } ) - - self.ZoneGROUP = ZoneGROUP - - return self -end - - ---- Returns the current location of the @{Group}. --- @param #ZONE_GROUP self --- @return DCSTypes#Vec2 The location of the zone based on the @{Group} location. -function ZONE_GROUP:GetVec2() - self:F( self.ZoneName ) - - local ZoneVec2 = self.ZoneGROUP:GetVec2() - - self:T( { ZoneVec2 } ) - - return ZoneVec2 -end - ---- Returns a random location within the zone of the @{Group}. --- @param #ZONE_GROUP self --- @return DCSTypes#Vec2 The random location of the zone based on the @{Group} location. -function ZONE_GROUP:GetRandomVec2() - self:F( self.ZoneName ) - - local Point = {} - local Vec2 = self.ZoneGROUP:GetVec2() - - local angle = math.random() * math.pi*2; - Point.x = Vec2.x + math.cos( angle ) * math.random() * self:GetRadius(); - Point.y = Vec2.y + math.sin( angle ) * math.random() * self:GetRadius(); - - self:T( { Point } ) - - return Point -end - - - --- Polygons - ---- The ZONE_POLYGON_BASE class defined by an array of @{DCSTypes#Vec2}, forming a polygon. --- @type ZONE_POLYGON_BASE --- @field #ZONE_POLYGON_BASE.ListVec2 Polygon The polygon defined by an array of @{DCSTypes#Vec2}. --- @extends Zone#ZONE_BASE -ZONE_POLYGON_BASE = { - ClassName="ZONE_POLYGON_BASE", - } - ---- A points array. --- @type ZONE_POLYGON_BASE.ListVec2 --- @list - ---- Constructor to create a ZONE_POLYGON_BASE instance, taking the zone name and an array of @{DCSTypes#Vec2}, forming a polygon. --- The @{Group#GROUP} waypoints define the polygon corners. The first and the last point are automatically connected. --- @param #ZONE_POLYGON_BASE self --- @param #string ZoneName Name of the zone. --- @param #ZONE_POLYGON_BASE.ListVec2 PointsArray An array of @{DCSTypes#Vec2}, forming a polygon.. --- @return #ZONE_POLYGON_BASE self -function ZONE_POLYGON_BASE:New( ZoneName, PointsArray ) - local self = BASE:Inherit( self, ZONE_BASE:New( ZoneName ) ) - self:F( { ZoneName, PointsArray } ) - - local i = 0 - - self.Polygon = {} - - for i = 1, #PointsArray do - self.Polygon[i] = {} - self.Polygon[i].x = PointsArray[i].x - self.Polygon[i].y = PointsArray[i].y - end - - return self -end - ---- Flush polygon coordinates as a table in DCS.log. --- @param #ZONE_POLYGON_BASE self --- @return #ZONE_POLYGON_BASE self -function ZONE_POLYGON_BASE:Flush() - self:F2() - - self:E( { Polygon = self.ZoneName, Coordinates = self.Polygon } ) - - return self -end - - ---- Smokes the zone boundaries in a color. --- @param #ZONE_POLYGON_BASE self --- @param #POINT_VEC3.SmokeColor SmokeColor The smoke color. --- @return #ZONE_POLYGON_BASE self -function ZONE_POLYGON_BASE:SmokeZone( SmokeColor ) - self:F2( SmokeColor ) - - local i - local j - local Segments = 10 - - i = 1 - j = #self.Polygon - - while i <= #self.Polygon do - self:T( { i, j, self.Polygon[i], self.Polygon[j] } ) - - local DeltaX = self.Polygon[j].x - self.Polygon[i].x - local DeltaY = self.Polygon[j].y - self.Polygon[i].y - - for Segment = 0, Segments do -- We divide each line in 5 segments and smoke a point on the line. - local PointX = self.Polygon[i].x + ( Segment * DeltaX / Segments ) - local PointY = self.Polygon[i].y + ( Segment * DeltaY / Segments ) - POINT_VEC2:New( PointX, PointY ):Smoke( SmokeColor ) - end - j = i - i = i + 1 - end - - return self -end - - - - ---- Returns if a location is within the zone. --- Source learned and taken from: https://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html --- @param #ZONE_POLYGON_BASE self --- @param DCSTypes#Vec2 Vec2 The location to test. --- @return #boolean true if the location is within the zone. -function ZONE_POLYGON_BASE:IsPointVec2InZone( Vec2 ) - self:F2( Vec2 ) - - local Next - local Prev - local InPolygon = false - - Next = 1 - Prev = #self.Polygon - - while Next <= #self.Polygon do - self:T( { Next, Prev, self.Polygon[Next], self.Polygon[Prev] } ) - if ( ( ( self.Polygon[Next].y > Vec2.y ) ~= ( self.Polygon[Prev].y > Vec2.y ) ) and - ( Vec2.x < ( self.Polygon[Prev].x - self.Polygon[Next].x ) * ( Vec2.y - self.Polygon[Next].y ) / ( self.Polygon[Prev].y - self.Polygon[Next].y ) + self.Polygon[Next].x ) - ) then - InPolygon = not InPolygon - end - self:T2( { InPolygon = InPolygon } ) - Prev = Next - Next = Next + 1 - end - - self:T( { InPolygon = InPolygon } ) - return InPolygon -end - ---- Define a random @{DCSTypes#Vec2} within the zone. --- @param #ZONE_POLYGON_BASE self --- @return DCSTypes#Vec2 The Vec2 coordinate. -function ZONE_POLYGON_BASE:GetRandomVec2() - self:F2() - - --- It is a bit tricky to find a random point within a polygon. Right now i am doing it the dirty and inefficient way... - local Vec2Found = false - local Vec2 - local BS = self:GetBoundingSquare() - - self:T2( BS ) - - while Vec2Found == false do - Vec2 = { x = math.random( BS.x1, BS.x2 ), y = math.random( BS.y1, BS.y2 ) } - self:T2( Vec2 ) - if self:IsPointVec2InZone( Vec2 ) then - Vec2Found = true - end - end - - self:T2( Vec2 ) - - return Vec2 -end - ---- Get the bounding square the zone. --- @param #ZONE_POLYGON_BASE self --- @return #ZONE_POLYGON_BASE.BoundingSquare The bounding square. -function ZONE_POLYGON_BASE:GetBoundingSquare() - - local x1 = self.Polygon[1].x - local y1 = self.Polygon[1].y - local x2 = self.Polygon[1].x - local y2 = self.Polygon[1].y - - for i = 2, #self.Polygon do - self:T2( { self.Polygon[i], x1, y1, x2, y2 } ) - x1 = ( x1 > self.Polygon[i].x ) and self.Polygon[i].x or x1 - x2 = ( x2 < self.Polygon[i].x ) and self.Polygon[i].x or x2 - y1 = ( y1 > self.Polygon[i].y ) and self.Polygon[i].y or y1 - y2 = ( y2 < self.Polygon[i].y ) and self.Polygon[i].y or y2 - - end - - return { x1 = x1, y1 = y1, x2 = x2, y2 = y2 } -end - - - - - ---- The ZONE_POLYGON class defined by a sequence of @{Group#GROUP} waypoints within the Mission Editor, forming a polygon. --- @type ZONE_POLYGON --- @extends Zone#ZONE_POLYGON_BASE -ZONE_POLYGON = { - ClassName="ZONE_POLYGON", - } - ---- Constructor to create a ZONE_POLYGON instance, taking the zone name and the name of the @{Group#GROUP} defined within the Mission Editor. --- The @{Group#GROUP} waypoints define the polygon corners. The first and the last point are automatically connected by ZONE_POLYGON. --- @param #ZONE_POLYGON self --- @param #string ZoneName Name of the zone. --- @param Group#GROUP ZoneGroup The GROUP waypoints as defined within the Mission Editor define the polygon shape. --- @return #ZONE_POLYGON self -function ZONE_POLYGON:New( ZoneName, ZoneGroup ) - - local GroupPoints = ZoneGroup:GetTaskRoute() - - local self = BASE:Inherit( self, ZONE_POLYGON_BASE:New( ZoneName, GroupPoints ) ) - self:F( { ZoneName, ZoneGroup, self.Polygon } ) - - return self -end - ---- This module contains the CLIENT class. --- --- 1) @{Client#CLIENT} class, extends @{Unit#UNIT} --- =============================================== --- Clients are those **Units** defined within the Mission Editor that have the skillset defined as __Client__ or __Player__. --- Note that clients are NOT the same as Units, they are NOT necessarily alive. --- The @{Client#CLIENT} class is a wrapper class to handle the DCS Unit objects that have the skillset defined as __Client__ or __Player__: --- --- * Wraps the DCS Unit objects with skill level set to Player or Client. --- * Support all DCS Unit APIs. --- * Enhance with Unit specific APIs not in the DCS Group API set. --- * When player joins Unit, execute alive init logic. --- * Handles messages to players. --- * Manage the "state" of the DCS Unit. --- --- Clients are being used by the @{MISSION} class to follow players and register their successes. --- --- 1.1) CLIENT reference methods --- ----------------------------- --- For each DCS Unit having skill level Player or Client, a CLIENT wrapper object (instance) will be created within the _@{DATABASE} object. --- This is done at the beginning of the mission (when the mission starts). --- --- The CLIENT class does not contain a :New() method, rather it provides :Find() methods to retrieve the object reference --- using the DCS Unit or the DCS UnitName. --- --- Another thing to know is that CLIENT objects do not "contain" the DCS Unit object. --- The CLIENT methods will reference the DCS Unit object by name when it is needed during API execution. --- If the DCS Unit object does not exist or is nil, the CLIENT methods will return nil and log an exception in the DCS.log file. --- --- The CLIENT class provides the following functions to retrieve quickly the relevant CLIENT instance: --- --- * @{#CLIENT.Find}(): Find a CLIENT instance from the _DATABASE object using a DCS Unit object. --- * @{#CLIENT.FindByName}(): Find a CLIENT instance from the _DATABASE object using a DCS Unit name. --- --- IMPORTANT: ONE SHOULD NEVER SANATIZE these CLIENT OBJECT REFERENCES! (make the CLIENT object references nil). --- --- @module Client --- @author FlightControl - ---- The CLIENT class --- @type CLIENT --- @extends Unit#UNIT -CLIENT = { - ONBOARDSIDE = { - NONE = 0, - LEFT = 1, - RIGHT = 2, - BACK = 3, - FRONT = 4 - }, - ClassName = "CLIENT", - ClientName = nil, - ClientAlive = false, - ClientTransport = false, - ClientBriefingShown = false, - _Menus = {}, - _Tasks = {}, - Messages = { - } -} - - ---- Finds a CLIENT from the _DATABASE using the relevant DCS Unit. --- @param #CLIENT self --- @param #string ClientName Name of the DCS **Unit** as defined within the Mission Editor. --- @param #string ClientBriefing Text that describes the briefing of the mission when a Player logs into the Client. --- @return #CLIENT --- @usage --- -- Create new Clients. --- local Mission = MISSIONSCHEDULER.AddMission( 'Russia Transport Troops SA-6', 'Operational', 'Transport troops from the control center to one of the SA-6 SAM sites to activate their operation.', 'Russia' ) --- Mission:AddGoal( DeploySA6TroopsGoal ) --- --- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*HOT-Deploy Troops 1' ):Transport() ) --- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*RAMP-Deploy Troops 3' ):Transport() ) --- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*HOT-Deploy Troops 2' ):Transport() ) --- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*RAMP-Deploy Troops 4' ):Transport() ) -function CLIENT:Find( DCSUnit ) - local ClientName = DCSUnit:getName() - local ClientFound = _DATABASE:FindClient( ClientName ) - - if ClientFound then - ClientFound:F( ClientName ) - return ClientFound - end - - error( "CLIENT not found for: " .. ClientName ) -end - - ---- Finds a CLIENT from the _DATABASE using the relevant Client Unit Name. --- As an optional parameter, a briefing text can be given also. --- @param #CLIENT self --- @param #string ClientName Name of the DCS **Unit** as defined within the Mission Editor. --- @param #string ClientBriefing Text that describes the briefing of the mission when a Player logs into the Client. --- @return #CLIENT --- @usage --- -- Create new Clients. --- local Mission = MISSIONSCHEDULER.AddMission( 'Russia Transport Troops SA-6', 'Operational', 'Transport troops from the control center to one of the SA-6 SAM sites to activate their operation.', 'Russia' ) --- Mission:AddGoal( DeploySA6TroopsGoal ) --- --- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*HOT-Deploy Troops 1' ):Transport() ) --- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*RAMP-Deploy Troops 3' ):Transport() ) --- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*HOT-Deploy Troops 2' ):Transport() ) --- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*RAMP-Deploy Troops 4' ):Transport() ) -function CLIENT:FindByName( ClientName, ClientBriefing ) - local ClientFound = _DATABASE:FindClient( ClientName ) - - if ClientFound then - ClientFound:F( { ClientName, ClientBriefing } ) - ClientFound:AddBriefing( ClientBriefing ) - ClientFound.MessageSwitch = true - - return ClientFound - end - - error( "CLIENT not found for: " .. ClientName ) -end - -function CLIENT:Register( ClientName ) - local self = BASE:Inherit( self, UNIT:Register( ClientName ) ) - - self:F( ClientName ) - self.ClientName = ClientName - self.MessageSwitch = true - self.ClientAlive2 = false - - --self.AliveCheckScheduler = routines.scheduleFunction( self._AliveCheckScheduler, { self }, timer.getTime() + 1, 5 ) - self.AliveCheckScheduler = SCHEDULER:New( self, self._AliveCheckScheduler, { "Client Alive " .. ClientName }, 1, 5 ) - - self:E( self ) - return self -end - - ---- Transport defines that the Client is a Transport. Transports show cargo. --- @param #CLIENT self --- @return #CLIENT -function CLIENT:Transport() - self:F() - - self.ClientTransport = true - return self -end - ---- AddBriefing adds a briefing to a CLIENT when a player joins a mission. --- @param #CLIENT self --- @param #string ClientBriefing is the text defining the Mission briefing. --- @return #CLIENT self -function CLIENT:AddBriefing( ClientBriefing ) - self:F( ClientBriefing ) - self.ClientBriefing = ClientBriefing - self.ClientBriefingShown = false - - return self -end - ---- Show the briefing of a CLIENT. --- @param #CLIENT self --- @return #CLIENT self -function CLIENT:ShowBriefing() - self:F( { self.ClientName, self.ClientBriefingShown } ) - - if not self.ClientBriefingShown then - self.ClientBriefingShown = true - local Briefing = "" - if self.ClientBriefing then - Briefing = Briefing .. self.ClientBriefing - end - Briefing = Briefing .. " Press [LEFT ALT]+[B] to view the complete mission briefing." - self:Message( Briefing, 60, "Briefing" ) - end - - return self -end - ---- Show the mission briefing of a MISSION to the CLIENT. --- @param #CLIENT self --- @param #string MissionBriefing --- @return #CLIENT self -function CLIENT:ShowMissionBriefing( MissionBriefing ) - self:F( { self.ClientName } ) - - if MissionBriefing then - self:Message( MissionBriefing, 60, "Mission Briefing" ) - end - - return self -end - - - ---- Resets a CLIENT. --- @param #CLIENT self --- @param #string ClientName Name of the Group as defined within the Mission Editor. The Group must have a Unit with the type Client. -function CLIENT:Reset( ClientName ) - self:F() - self._Menus = {} -end - --- Is Functions - ---- Checks if the CLIENT is a multi-seated UNIT. --- @param #CLIENT self --- @return #boolean true if multi-seated. -function CLIENT:IsMultiSeated() - self:F( self.ClientName ) - - local ClientMultiSeatedTypes = { - ["Mi-8MT"] = "Mi-8MT", - ["UH-1H"] = "UH-1H", - ["P-51B"] = "P-51B" - } - - if self:IsAlive() then - local ClientTypeName = self:GetClientGroupUnit():GetTypeName() - if ClientMultiSeatedTypes[ClientTypeName] then - return true - end - end - - return false -end - ---- Checks for a client alive event and calls a function on a continuous basis. --- @param #CLIENT self --- @param #function CallBack Function. --- @return #CLIENT -function CLIENT:Alive( CallBackFunction, ... ) - self:F() - - self.ClientCallBack = CallBackFunction - self.ClientParameters = arg - - return self -end - ---- @param #CLIENT self -function CLIENT:_AliveCheckScheduler( SchedulerName ) - self:F( { SchedulerName, self.ClientName, self.ClientAlive2, self.ClientBriefingShown, self.ClientCallBack } ) - - if self:IsAlive() then - if self.ClientAlive2 == false then - self:ShowBriefing() - if self.ClientCallBack then - self:T("Calling Callback function") - self.ClientCallBack( self, unpack( self.ClientParameters ) ) - end - self.ClientAlive2 = true - end - else - if self.ClientAlive2 == true then - self.ClientAlive2 = false - end - end - - return true -end - ---- Return the DCSGroup of a Client. --- This function is modified to deal with a couple of bugs in DCS 1.5.3 --- @param #CLIENT self --- @return DCSGroup#Group -function CLIENT:GetDCSGroup() - self:F3() - --- local ClientData = Group.getByName( self.ClientName ) --- if ClientData and ClientData:isExist() then --- self:T( self.ClientName .. " : group found!" ) --- return ClientData --- else --- return nil --- end - - local ClientUnit = Unit.getByName( self.ClientName ) - - local CoalitionsData = { AlivePlayersRed = coalition.getPlayers( coalition.side.RED ), AlivePlayersBlue = coalition.getPlayers( coalition.side.BLUE ) } - for CoalitionId, CoalitionData in pairs( CoalitionsData ) do - self:T3( { "CoalitionData:", CoalitionData } ) - for UnitId, UnitData in pairs( CoalitionData ) do - self:T3( { "UnitData:", UnitData } ) - if UnitData and UnitData:isExist() then - - --self:E(self.ClientName) - if ClientUnit then - local ClientGroup = ClientUnit:getGroup() - if ClientGroup then - self:T3( "ClientGroup = " .. self.ClientName ) - if ClientGroup:isExist() and UnitData:getGroup():isExist() then - if ClientGroup:getID() == UnitData:getGroup():getID() then - self:T3( "Normal logic" ) - self:T3( self.ClientName .. " : group found!" ) - self.ClientGroupID = ClientGroup:getID() - self.ClientGroupName = ClientGroup:getName() - return ClientGroup - end - else - -- Now we need to resolve the bugs in DCS 1.5 ... - -- Consult the database for the units of the Client Group. (ClientGroup:getUnits() returns nil) - self:T3( "Bug 1.5 logic" ) - local ClientGroupTemplate = _DATABASE.Templates.Units[self.ClientName].GroupTemplate - self.ClientGroupID = ClientGroupTemplate.groupId - self.ClientGroupName = _DATABASE.Templates.Units[self.ClientName].GroupName - self:T3( self.ClientName .. " : group found in bug 1.5 resolvement logic!" ) - return ClientGroup - end - -- else - -- error( "Client " .. self.ClientName .. " not found!" ) - end - else - --self:E( { "Client not found!", self.ClientName } ) - end - end - end - end - - -- For non player clients - if ClientUnit then - local ClientGroup = ClientUnit:getGroup() - if ClientGroup then - self:T3( "ClientGroup = " .. self.ClientName ) - if ClientGroup:isExist() then - self:T3( "Normal logic" ) - self:T3( self.ClientName .. " : group found!" ) - return ClientGroup - end - end - end - - self.ClientGroupID = nil - self.ClientGroupUnit = nil - - return nil -end - - --- TODO: Check DCSTypes#Group.ID ---- Get the group ID of the client. --- @param #CLIENT self --- @return DCSTypes#Group.ID -function CLIENT:GetClientGroupID() - - local ClientGroup = self:GetDCSGroup() - - --self:E( self.ClientGroupID ) -- Determined in GetDCSGroup() - return self.ClientGroupID -end - - ---- Get the name of the group of the client. --- @param #CLIENT self --- @return #string -function CLIENT:GetClientGroupName() - - local ClientGroup = self:GetDCSGroup() - - self:T( self.ClientGroupName ) -- Determined in GetDCSGroup() - return self.ClientGroupName -end - ---- Returns the UNIT of the CLIENT. --- @param #CLIENT self --- @return Unit#UNIT -function CLIENT:GetClientGroupUnit() - self:F2() - - local ClientDCSUnit = Unit.getByName( self.ClientName ) - - self:T( self.ClientDCSUnit ) - if ClientDCSUnit and ClientDCSUnit:isExist() then - local ClientUnit = _DATABASE:FindUnit( self.ClientName ) - self:T2( ClientUnit ) - return ClientUnit - end -end - ---- Returns the DCSUnit of the CLIENT. --- @param #CLIENT self --- @return DCSTypes#Unit -function CLIENT:GetClientGroupDCSUnit() - self:F2() - - local ClientDCSUnit = Unit.getByName( self.ClientName ) - - if ClientDCSUnit and ClientDCSUnit:isExist() then - self:T2( ClientDCSUnit ) - return ClientDCSUnit - end -end - - ---- Evaluates if the CLIENT is a transport. --- @param #CLIENT self --- @return #boolean true is a transport. -function CLIENT:IsTransport() - self:F() - return self.ClientTransport -end - ---- Shows the @{Cargo#CARGO} contained within the CLIENT to the player as a message. --- The @{Cargo#CARGO} is shown using the @{Message#MESSAGE} distribution system. --- @param #CLIENT self -function CLIENT:ShowCargo() - self:F() - - local CargoMsg = "" - - for CargoName, Cargo in pairs( CARGOS ) do - if self == Cargo:IsLoadedInClient() then - CargoMsg = CargoMsg .. Cargo.CargoName .. " Type:" .. Cargo.CargoType .. " Weight: " .. Cargo.CargoWeight .. "\n" - end - end - - if CargoMsg == "" then - CargoMsg = "empty" - end - - self:Message( CargoMsg, 15, "Co-Pilot: Cargo Status", 30 ) - -end - --- TODO (1) I urgently need to revise this. ---- A local function called by the DCS World Menu system to switch off messages. -function CLIENT.SwitchMessages( PrmTable ) - PrmTable[1].MessageSwitch = PrmTable[2] -end - ---- The main message driver for the CLIENT. --- This function displays various messages to the Player logged into the CLIENT through the DCS World Messaging system. --- @param #CLIENT self --- @param #string Message is the text describing the message. --- @param #number MessageDuration is the duration in seconds that the Message should be displayed. --- @param #string MessageCategory is the category of the message (the title). --- @param #number MessageInterval is the interval in seconds between the display of the @{Message#MESSAGE} when the CLIENT is in the air. --- @param #string MessageID is the identifier of the message when displayed with intervals. -function CLIENT:Message( Message, MessageDuration, MessageCategory, MessageInterval, MessageID ) - self:F( { Message, MessageDuration, MessageCategory, MessageInterval } ) - - if self.MessageSwitch == true then - if MessageCategory == nil then - MessageCategory = "Messages" - end - if MessageID ~= nil then - if self.Messages[MessageID] == nil then - self.Messages[MessageID] = {} - self.Messages[MessageID].MessageId = MessageID - self.Messages[MessageID].MessageTime = timer.getTime() - self.Messages[MessageID].MessageDuration = MessageDuration - if MessageInterval == nil then - self.Messages[MessageID].MessageInterval = 600 - else - self.Messages[MessageID].MessageInterval = MessageInterval - end - MESSAGE:New( Message, MessageDuration, MessageCategory ):ToClient( self ) - else - if self:GetClientGroupDCSUnit() and not self:GetClientGroupDCSUnit():inAir() then - if timer.getTime() - self.Messages[MessageID].MessageTime >= self.Messages[MessageID].MessageDuration + 10 then - MESSAGE:New( Message, MessageDuration , MessageCategory):ToClient( self ) - self.Messages[MessageID].MessageTime = timer.getTime() - end - else - if timer.getTime() - self.Messages[MessageID].MessageTime >= self.Messages[MessageID].MessageDuration + self.Messages[MessageID].MessageInterval then - MESSAGE:New( Message, MessageDuration, MessageCategory ):ToClient( self ) - self.Messages[MessageID].MessageTime = timer.getTime() - end - end - end - else - MESSAGE:New( Message, MessageDuration, MessageCategory ):ToClient( self ) - end - end -end ---- This module contains the STATIC class. --- --- 1) @{Static#STATIC} class, extends @{Positionable#POSITIONABLE} --- =============================================================== --- Statics are **Static Units** defined within the Mission Editor. --- Note that Statics are almost the same as Units, but they don't have a controller. --- The @{Static#STATIC} class is a wrapper class to handle the DCS Static objects: --- --- * Wraps the DCS Static objects. --- * Support all DCS Static APIs. --- * Enhance with Static specific APIs not in the DCS API set. --- --- 1.1) STATIC reference methods --- ----------------------------- --- For each DCS Static will have a STATIC wrapper object (instance) within the _@{DATABASE} object. --- This is done at the beginning of the mission (when the mission starts). --- --- The STATIC class does not contain a :New() method, rather it provides :Find() methods to retrieve the object reference --- using the Static Name. --- --- Another thing to know is that STATIC objects do not "contain" the DCS Static object. --- The STATIc methods will reference the DCS Static object by name when it is needed during API execution. --- If the DCS Static object does not exist or is nil, the STATIC methods will return nil and log an exception in the DCS.log file. --- --- The STATIc class provides the following functions to retrieve quickly the relevant STATIC instance: --- --- * @{#STATIC.FindByName}(): Find a STATIC instance from the _DATABASE object using a DCS Static name. --- --- IMPORTANT: ONE SHOULD NEVER SANATIZE these STATIC OBJECT REFERENCES! (make the STATIC object references nil). --- --- @module Static --- @author FlightControl - - - - - - ---- The STATIC class --- @type STATIC --- @extends Positionable#POSITIONABLE -STATIC = { - ClassName = "STATIC", -} - - ---- Finds a STATIC from the _DATABASE using the relevant Static Name. --- As an optional parameter, a briefing text can be given also. --- @param #STATIC self --- @param #string StaticName Name of the DCS **Static** as defined within the Mission Editor. --- @return #STATIC -function STATIC:FindByName( StaticName ) - local StaticFound = _DATABASE:FindStatic( StaticName ) - - if StaticFound then - StaticFound:F( { StaticName } ) - - return StaticFound - end - - error( "STATIC not found for: " .. StaticName ) -end - -function STATIC:Register( StaticName ) - local self = BASE:Inherit( self, POSITIONABLE:New( StaticName ) ) - return self -end - - -function STATIC:GetDCSUnit() - local DCSStatic = StaticObject.getByName( self.UnitName ) - - if DCSStatic then - return DCSStatic - end - - return nil -end ---- This module contains the AIRBASE classes. --- --- === --- --- 1) @{Airbase#AIRBASE} class, extends @{Positionable#POSITIONABLE} --- ================================================================= --- The @{AIRBASE} class is a wrapper class to handle the DCS Airbase objects: --- --- * Support all DCS Airbase APIs. --- * Enhance with Airbase specific APIs not in the DCS Airbase API set. --- --- --- 1.1) AIRBASE reference methods --- ------------------------------ --- For each DCS Airbase object alive within a running mission, a AIRBASE wrapper object (instance) will be created within the _@{DATABASE} object. --- This is done at the beginning of the mission (when the mission starts). --- --- The AIRBASE class **does not contain a :New()** method, rather it provides **:Find()** methods to retrieve the object reference --- using the DCS Airbase or the DCS AirbaseName. --- --- Another thing to know is that AIRBASE objects do not "contain" the DCS Airbase object. --- The AIRBASE methods will reference the DCS Airbase object by name when it is needed during API execution. --- If the DCS Airbase object does not exist or is nil, the AIRBASE methods will return nil and log an exception in the DCS.log file. --- --- The AIRBASE class provides the following functions to retrieve quickly the relevant AIRBASE instance: --- --- * @{#AIRBASE.Find}(): Find a AIRBASE instance from the _DATABASE object using a DCS Airbase object. --- * @{#AIRBASE.FindByName}(): Find a AIRBASE instance from the _DATABASE object using a DCS Airbase name. --- --- IMPORTANT: ONE SHOULD NEVER SANATIZE these AIRBASE OBJECT REFERENCES! (make the AIRBASE object references nil). --- --- 1.2) DCS AIRBASE APIs --- --------------------- --- The DCS Airbase APIs are used extensively within MOOSE. The AIRBASE class has for each DCS Airbase API a corresponding method. --- To be able to distinguish easily in your code the difference between a AIRBASE API call and a DCS Airbase API call, --- the first letter of the method is also capitalized. So, by example, the DCS Airbase method @{DCSAirbase#Airbase.getName}() --- is implemented in the AIRBASE class as @{#AIRBASE.GetName}(). --- --- More functions will be added --- ---------------------------- --- During the MOOSE development, more functions will be added. --- --- @module Airbase --- @author FlightControl - - - - - ---- The AIRBASE class --- @type AIRBASE --- @extends Positionable#POSITIONABLE -AIRBASE = { - ClassName="AIRBASE", - CategoryName = { - [Airbase.Category.AIRDROME] = "Airdrome", - [Airbase.Category.HELIPAD] = "Helipad", - [Airbase.Category.SHIP] = "Ship", - }, - } - --- Registration. - ---- Create a new AIRBASE from DCSAirbase. --- @param #AIRBASE self --- @param #string AirbaseName The name of the airbase. --- @return Airbase#AIRBASE -function AIRBASE:Register( AirbaseName ) - - local self = BASE:Inherit( self, POSITIONABLE:New( AirbaseName ) ) - self.AirbaseName = AirbaseName - return self -end - --- Reference methods. - ---- Finds a AIRBASE from the _DATABASE using a DCSAirbase object. --- @param #AIRBASE self --- @param DCSAirbase#Airbase DCSAirbase An existing DCS Airbase object reference. --- @return Airbase#AIRBASE self -function AIRBASE:Find( DCSAirbase ) - - local AirbaseName = DCSAirbase:getName() - local AirbaseFound = _DATABASE:FindAirbase( AirbaseName ) - return AirbaseFound -end - ---- Find a AIRBASE in the _DATABASE using the name of an existing DCS Airbase. --- @param #AIRBASE self --- @param #string AirbaseName The Airbase Name. --- @return Airbase#AIRBASE self -function AIRBASE:FindByName( AirbaseName ) - - local AirbaseFound = _DATABASE:FindAirbase( AirbaseName ) - return AirbaseFound -end - -function AIRBASE:GetDCSObject() - local DCSAirbase = Airbase.getByName( self.AirbaseName ) - - if DCSAirbase then - return DCSAirbase - end - - return nil -end - - - ---- This module contains the DATABASE class, managing the database of mission objects. --- --- ==== --- --- 1) @{Database#DATABASE} class, extends @{Base#BASE} --- =================================================== --- Mission designers can use the DATABASE class to refer to: --- --- * UNITS --- * GROUPS --- * CLIENTS --- * AIRPORTS --- * PLAYERSJOINED --- * PLAYERS --- --- On top, for internal MOOSE administration purposes, the DATBASE administers the Unit and Group TEMPLATES as defined within the Mission Editor. --- --- Moose will automatically create one instance of the DATABASE class into the **global** object _DATABASE. --- Moose refers to _DATABASE within the framework extensively, but you can also refer to the _DATABASE object within your missions if required. --- --- 1.1) DATABASE iterators --- ----------------------- --- You can iterate the database with the available iterator methods. --- The iterator methods will walk the DATABASE set, and call for each element within the set a function that you provide. --- The following iterator methods are currently available within the DATABASE: --- --- * @{#DATABASE.ForEachUnit}: Calls a function for each @{UNIT} it finds within the DATABASE. --- * @{#DATABASE.ForEachGroup}: Calls a function for each @{GROUP} it finds within the DATABASE. --- * @{#DATABASE.ForEachPlayer}: Calls a function for each alive player it finds within the DATABASE. --- * @{#DATABASE.ForEachPlayerJoined}: Calls a function for each joined player it finds within the DATABASE. --- * @{#DATABASE.ForEachClient}: Calls a function for each @{CLIENT} it finds within the DATABASE. --- * @{#DATABASE.ForEachClientAlive}: Calls a function for each alive @{CLIENT} it finds within the DATABASE. --- --- === --- --- @module Database --- @author FlightControl - ---- DATABASE class --- @type DATABASE --- @extends Base#BASE -DATABASE = { - ClassName = "DATABASE", - Templates = { - Units = {}, - Groups = {}, - ClientsByName = {}, - ClientsByID = {}, - }, - UNITS = {}, - STATICS = {}, - GROUPS = {}, - PLAYERS = {}, - PLAYERSJOINED = {}, - CLIENTS = {}, - AIRBASES = {}, - NavPoints = {}, -} - -local _DATABASECoalition = - { - [1] = "Red", - [2] = "Blue", - } - -local _DATABASECategory = - { - ["plane"] = Unit.Category.AIRPLANE, - ["helicopter"] = Unit.Category.HELICOPTER, - ["vehicle"] = Unit.Category.GROUND_UNIT, - ["ship"] = Unit.Category.SHIP, - ["static"] = Unit.Category.STRUCTURE, - } - - ---- Creates a new DATABASE object, building a set of units belonging to a coalitions, categories, countries, types or with defined prefix names. --- @param #DATABASE self --- @return #DATABASE --- @usage --- -- Define a new DATABASE Object. This DBObject will contain a reference to all Group and Unit Templates defined within the ME and the DCSRTE. --- DBObject = DATABASE:New() -function DATABASE:New() - - -- Inherits from BASE - local self = BASE:Inherit( self, BASE:New() ) - - _EVENTDISPATCHER:OnBirth( self._EventOnBirth, self ) - _EVENTDISPATCHER:OnDead( self._EventOnDeadOrCrash, self ) - _EVENTDISPATCHER:OnCrash( self._EventOnDeadOrCrash, self ) - - - -- Follow alive players and clients - _EVENTDISPATCHER:OnPlayerEnterUnit( self._EventOnPlayerEnterUnit, self ) - _EVENTDISPATCHER:OnPlayerLeaveUnit( self._EventOnPlayerLeaveUnit, self ) - - self:_RegisterTemplates() - self:_RegisterGroupsAndUnits() - self:_RegisterClients() - self:_RegisterStatics() - self:_RegisterPlayers() - self:_RegisterAirbases() - - return self -end - ---- Finds a Unit based on the Unit Name. --- @param #DATABASE self --- @param #string UnitName --- @return Unit#UNIT The found Unit. -function DATABASE:FindUnit( UnitName ) - - local UnitFound = self.UNITS[UnitName] - return UnitFound -end - - ---- Adds a Unit based on the Unit Name in the DATABASE. --- @param #DATABASE self -function DATABASE:AddUnit( DCSUnitName ) - - if not self.UNITS[DCSUnitName] then - local UnitRegister = UNIT:Register( DCSUnitName ) - self.UNITS[DCSUnitName] = UNIT:Register( DCSUnitName ) - end - - return self.UNITS[DCSUnitName] -end - - ---- Deletes a Unit from the DATABASE based on the Unit Name. --- @param #DATABASE self -function DATABASE:DeleteUnit( DCSUnitName ) - - --self.UNITS[DCSUnitName] = nil -end - ---- Adds a Static based on the Static Name in the DATABASE. --- @param #DATABASE self -function DATABASE:AddStatic( DCSStaticName ) - - if not self.STATICS[DCSStaticName] then - self.STATICS[DCSStaticName] = STATIC:Register( DCSStaticName ) - end -end - - ---- Deletes a Static from the DATABASE based on the Static Name. --- @param #DATABASE self -function DATABASE:DeleteStatic( DCSStaticName ) - - --self.STATICS[DCSStaticName] = nil -end - ---- Finds a STATIC based on the StaticName. --- @param #DATABASE self --- @param #string StaticName --- @return Static#STATIC The found STATIC. -function DATABASE:FindStatic( StaticName ) - - local StaticFound = self.STATICS[StaticName] - return StaticFound -end - ---- Adds a Airbase based on the Airbase Name in the DATABASE. --- @param #DATABASE self -function DATABASE:AddAirbase( DCSAirbaseName ) - - if not self.AIRBASES[DCSAirbaseName] then - self.AIRBASES[DCSAirbaseName] = AIRBASE:Register( DCSAirbaseName ) - end -end - - ---- Deletes a Airbase from the DATABASE based on the Airbase Name. --- @param #DATABASE self -function DATABASE:DeleteAirbase( DCSAirbaseName ) - - --self.AIRBASES[DCSAirbaseName] = nil -end - ---- Finds a AIRBASE based on the AirbaseName. --- @param #DATABASE self --- @param #string AirbaseName --- @return Airbase#AIRBASE The found AIRBASE. -function DATABASE:FindAirbase( AirbaseName ) - - local AirbaseFound = self.AIRBASES[AirbaseName] - return AirbaseFound -end - - ---- Finds a CLIENT based on the ClientName. --- @param #DATABASE self --- @param #string ClientName --- @return Client#CLIENT The found CLIENT. -function DATABASE:FindClient( ClientName ) - - local ClientFound = self.CLIENTS[ClientName] - return ClientFound -end - - ---- Adds a CLIENT based on the ClientName in the DATABASE. --- @param #DATABASE self -function DATABASE:AddClient( ClientName ) - - if not self.CLIENTS[ClientName] then - self.CLIENTS[ClientName] = CLIENT:Register( ClientName ) - end - - return self.CLIENTS[ClientName] -end - - ---- Finds a GROUP based on the GroupName. --- @param #DATABASE self --- @param #string GroupName --- @return Group#GROUP The found GROUP. -function DATABASE:FindGroup( GroupName ) - - local GroupFound = self.GROUPS[GroupName] - return GroupFound -end - - ---- Adds a GROUP based on the GroupName in the DATABASE. --- @param #DATABASE self -function DATABASE:AddGroup( GroupName ) - - if not self.GROUPS[GroupName] then - self.GROUPS[GroupName] = GROUP:Register( GroupName ) - end - - return self.GROUPS[GroupName] -end - ---- Adds a player based on the Player Name in the DATABASE. --- @param #DATABASE self -function DATABASE:AddPlayer( UnitName, PlayerName ) - - if PlayerName then - self:E( { "Add player for unit:", UnitName, PlayerName } ) - self.PLAYERS[PlayerName] = self:FindUnit( UnitName ) - self.PLAYERSJOINED[PlayerName] = PlayerName - end -end - ---- Deletes a player from the DATABASE based on the Player Name. --- @param #DATABASE self -function DATABASE:DeletePlayer( PlayerName ) - - if PlayerName then - self:E( { "Clean player:", PlayerName } ) - self.PLAYERS[PlayerName] = nil - end -end - - ---- Instantiate new Groups within the DCSRTE. --- This method expects EXACTLY the same structure as a structure within the ME, and needs 2 additional fields defined: --- SpawnCountryID, SpawnCategoryID --- This method is used by the SPAWN class. --- @param #DATABASE self --- @param #table SpawnTemplate --- @return #DATABASE self -function DATABASE:Spawn( SpawnTemplate ) - self:F2( SpawnTemplate.name ) - - self:T2( { SpawnTemplate.SpawnCountryID, SpawnTemplate.SpawnCategoryID } ) - - -- Copy the spawn variables of the template in temporary storage, nullify, and restore the spawn variables. - local SpawnCoalitionID = SpawnTemplate.SpawnCoalitionID - local SpawnCountryID = SpawnTemplate.SpawnCountryID - local SpawnCategoryID = SpawnTemplate.SpawnCategoryID - - -- Nullify - SpawnTemplate.SpawnCoalitionID = nil - SpawnTemplate.SpawnCountryID = nil - SpawnTemplate.SpawnCategoryID = nil - - self:_RegisterTemplate( SpawnTemplate, SpawnCoalitionID, SpawnCategoryID, SpawnCountryID ) - - self:T3( SpawnTemplate ) - coalition.addGroup( SpawnCountryID, SpawnCategoryID, SpawnTemplate ) - - -- Restore - SpawnTemplate.SpawnCoalitionID = SpawnCoalitionID - SpawnTemplate.SpawnCountryID = SpawnCountryID - SpawnTemplate.SpawnCategoryID = SpawnCategoryID - - local SpawnGroup = self:AddGroup( SpawnTemplate.name ) - return SpawnGroup -end - ---- Set a status to a Group within the Database, this to check crossing events for example. -function DATABASE:SetStatusGroup( GroupName, Status ) - self:F2( Status ) - - self.Templates.Groups[GroupName].Status = Status -end - ---- Get a status to a Group within the Database, this to check crossing events for example. -function DATABASE:GetStatusGroup( GroupName ) - self:F2( Status ) - - if self.Templates.Groups[GroupName] then - return self.Templates.Groups[GroupName].Status - else - return "" - end -end - ---- Private method that registers new Group Templates within the DATABASE Object. --- @param #DATABASE self --- @param #table GroupTemplate --- @return #DATABASE self -function DATABASE:_RegisterTemplate( GroupTemplate, CoalitionID, CategoryID, CountryID ) - - local GroupTemplateName = env.getValueDictByKey(GroupTemplate.name) - - local TraceTable = {} - - if not self.Templates.Groups[GroupTemplateName] then - self.Templates.Groups[GroupTemplateName] = {} - self.Templates.Groups[GroupTemplateName].Status = nil - end - - -- Delete the spans from the route, it is not needed and takes memory. - if GroupTemplate.route and GroupTemplate.route.spans then - GroupTemplate.route.spans = nil - end - - self.Templates.Groups[GroupTemplateName].GroupName = GroupTemplateName - self.Templates.Groups[GroupTemplateName].Template = GroupTemplate - self.Templates.Groups[GroupTemplateName].groupId = GroupTemplate.groupId - self.Templates.Groups[GroupTemplateName].UnitCount = #GroupTemplate.units - self.Templates.Groups[GroupTemplateName].Units = GroupTemplate.units - self.Templates.Groups[GroupTemplateName].CategoryID = CategoryID - self.Templates.Groups[GroupTemplateName].CoalitionID = CoalitionID - self.Templates.Groups[GroupTemplateName].CountryID = CountryID - - - TraceTable[#TraceTable+1] = "Group" - TraceTable[#TraceTable+1] = self.Templates.Groups[GroupTemplateName].GroupName - - TraceTable[#TraceTable+1] = "Coalition" - TraceTable[#TraceTable+1] = self.Templates.Groups[GroupTemplateName].CoalitionID - TraceTable[#TraceTable+1] = "Category" - TraceTable[#TraceTable+1] = self.Templates.Groups[GroupTemplateName].CategoryID - TraceTable[#TraceTable+1] = "Country" - TraceTable[#TraceTable+1] = self.Templates.Groups[GroupTemplateName].CountryID - - TraceTable[#TraceTable+1] = "Units" - - for unit_num, UnitTemplate in pairs( GroupTemplate.units ) do - - local UnitTemplateName = env.getValueDictByKey(UnitTemplate.name) - self.Templates.Units[UnitTemplateName] = {} - self.Templates.Units[UnitTemplateName].UnitName = UnitTemplateName - self.Templates.Units[UnitTemplateName].Template = UnitTemplate - self.Templates.Units[UnitTemplateName].GroupName = GroupTemplateName - self.Templates.Units[UnitTemplateName].GroupTemplate = GroupTemplate - self.Templates.Units[UnitTemplateName].GroupId = GroupTemplate.groupId - self.Templates.Units[UnitTemplateName].CategoryID = CategoryID - self.Templates.Units[UnitTemplateName].CoalitionID = CoalitionID - self.Templates.Units[UnitTemplateName].CountryID = CountryID - - if UnitTemplate.skill and (UnitTemplate.skill == "Client" or UnitTemplate.skill == "Player") then - self.Templates.ClientsByName[UnitTemplateName] = UnitTemplate - self.Templates.ClientsByName[UnitTemplateName].CategoryID = CategoryID - self.Templates.ClientsByName[UnitTemplateName].CoalitionID = CoalitionID - self.Templates.ClientsByName[UnitTemplateName].CountryID = CountryID - self.Templates.ClientsByID[UnitTemplate.unitId] = UnitTemplate - end - - TraceTable[#TraceTable+1] = self.Templates.Units[UnitTemplateName].UnitName - end - - self:E( TraceTable ) -end - -function DATABASE:GetGroupTemplate( GroupName ) - local GroupTemplate = self.Templates.Groups[GroupName].Template - GroupTemplate.SpawnCoalitionID = self.Templates.Groups[GroupName].CoalitionID - GroupTemplate.SpawnCategoryID = self.Templates.Groups[GroupName].CategoryID - GroupTemplate.SpawnCountryID = self.Templates.Groups[GroupName].CountryID - return GroupTemplate -end - -function DATABASE:GetCoalitionFromClientTemplate( ClientName ) - return self.Templates.ClientsByName[ClientName].CoalitionID -end - -function DATABASE:GetCategoryFromClientTemplate( ClientName ) - return self.Templates.ClientsByName[ClientName].CategoryID -end - -function DATABASE:GetCountryFromClientTemplate( ClientName ) - return self.Templates.ClientsByName[ClientName].CountryID -end - ---- Airbase - -function DATABASE:GetCoalitionFromAirbase( AirbaseName ) - return self.AIRBASES[AirbaseName]:GetCoalition() -end - -function DATABASE:GetCategoryFromAirbase( AirbaseName ) - return self.AIRBASES[AirbaseName]:GetCategory() -end - - - ---- Private method that registers all alive players in the mission. --- @param #DATABASE self --- @return #DATABASE self -function DATABASE:_RegisterPlayers() - - local CoalitionsData = { AlivePlayersRed = coalition.getPlayers( coalition.side.RED ), AlivePlayersBlue = coalition.getPlayers( coalition.side.BLUE ) } - for CoalitionId, CoalitionData in pairs( CoalitionsData ) do - for UnitId, UnitData in pairs( CoalitionData ) do - self:T3( { "UnitData:", UnitData } ) - if UnitData and UnitData:isExist() then - local UnitName = UnitData:getName() - local PlayerName = UnitData:getPlayerName() - if not self.PLAYERS[PlayerName] then - self:E( { "Add player for unit:", UnitName, PlayerName } ) - self:AddPlayer( UnitName, PlayerName ) - end - end - end - end - - return self -end - - ---- Private method that registers all Groups and Units within in the mission. --- @param #DATABASE self --- @return #DATABASE self -function DATABASE:_RegisterGroupsAndUnits() - - local CoalitionsData = { GroupsRed = coalition.getGroups( coalition.side.RED ), GroupsBlue = coalition.getGroups( coalition.side.BLUE ) } - for CoalitionId, CoalitionData in pairs( CoalitionsData ) do - for DCSGroupId, DCSGroup in pairs( CoalitionData ) do - - if DCSGroup:isExist() then - local DCSGroupName = DCSGroup:getName() - - self:E( { "Register Group:", DCSGroupName } ) - self:AddGroup( DCSGroupName ) - - for DCSUnitId, DCSUnit in pairs( DCSGroup:getUnits() ) do - - local DCSUnitName = DCSUnit:getName() - self:E( { "Register Unit:", DCSUnitName } ) - self:AddUnit( DCSUnitName ) - end - else - self:E( { "Group does not exist: ", DCSGroup } ) - end - - end - end - - return self -end - ---- Private method that registers all Units of skill Client or Player within in the mission. --- @param #DATABASE self --- @return #DATABASE self -function DATABASE:_RegisterClients() - - for ClientName, ClientTemplate in pairs( self.Templates.ClientsByName ) do - self:E( { "Register Client:", ClientName } ) - self:AddClient( ClientName ) - end - - return self -end - ---- @param #DATABASE self -function DATABASE:_RegisterStatics() - - local CoalitionsData = { GroupsRed = coalition.getStaticObjects( coalition.side.RED ), GroupsBlue = coalition.getStaticObjects( coalition.side.BLUE ) } - for CoalitionId, CoalitionData in pairs( CoalitionsData ) do - for DCSStaticId, DCSStatic in pairs( CoalitionData ) do - - if DCSStatic:isExist() then - local DCSStaticName = DCSStatic:getName() - - self:E( { "Register Static:", DCSStaticName } ) - self:AddStatic( DCSStaticName ) - else - self:E( { "Static does not exist: ", DCSStatic } ) - end - end - end - - return self -end - ---- @param #DATABASE self -function DATABASE:_RegisterAirbases() - - local CoalitionsData = { AirbasesRed = coalition.getAirbases( coalition.side.RED ), AirbasesBlue = coalition.getAirbases( coalition.side.BLUE ), AirbasesNeutral = coalition.getAirbases( coalition.side.NEUTRAL ) } - for CoalitionId, CoalitionData in pairs( CoalitionsData ) do - for DCSAirbaseId, DCSAirbase in pairs( CoalitionData ) do - - local DCSAirbaseName = DCSAirbase:getName() - - self:E( { "Register Airbase:", DCSAirbaseName } ) - self:AddAirbase( DCSAirbaseName ) - end - end - - return self -end - - ---- Events - ---- Handles the OnBirth event for the alive units set. --- @param #DATABASE self --- @param Event#EVENTDATA Event -function DATABASE:_EventOnBirth( Event ) - self:F2( { Event } ) - - if Event.IniDCSUnit then - self:AddUnit( Event.IniDCSUnitName ) - self:AddGroup( Event.IniDCSGroupName ) - self:_EventOnPlayerEnterUnit( Event ) - end -end - - ---- Handles the OnDead or OnCrash event for alive units set. --- @param #DATABASE self --- @param Event#EVENTDATA Event -function DATABASE:_EventOnDeadOrCrash( Event ) - self:F2( { Event } ) - - if Event.IniDCSUnit then - if self.UNITS[Event.IniDCSUnitName] then - self:DeleteUnit( Event.IniDCSUnitName ) - -- add logic to correctly remove a group once all units are destroyed... - end - end -end - - ---- Handles the OnPlayerEnterUnit event to fill the active players table (with the unit filter applied). --- @param #DATABASE self --- @param Event#EVENTDATA Event -function DATABASE:_EventOnPlayerEnterUnit( Event ) - self:F2( { Event } ) - - if Event.IniUnit then - local PlayerName = Event.IniUnit:GetPlayerName() - if not self.PLAYERS[PlayerName] then - self:AddPlayer( Event.IniUnitName, PlayerName ) - end - end -end - - ---- Handles the OnPlayerLeaveUnit event to clean the active players table. --- @param #DATABASE self --- @param Event#EVENTDATA Event -function DATABASE:_EventOnPlayerLeaveUnit( Event ) - self:F2( { Event } ) - - if Event.IniUnit then - local PlayerName = Event.IniUnit:GetPlayerName() - if self.PLAYERS[PlayerName] then - self:DeletePlayer( PlayerName ) - end - end -end - ---- Iterators - ---- Iterate the DATABASE and call an iterator function for the given set, providing the Object for each element within the set and optional parameters. --- @param #DATABASE self --- @param #function IteratorFunction The function that will be called when there is an alive player in the database. --- @return #DATABASE self -function DATABASE:ForEach( IteratorFunction, FinalizeFunction, arg, Set ) - self:F2( arg ) - - local function CoRoutine() - local Count = 0 - for ObjectID, Object in pairs( Set ) do - self:T2( Object ) - IteratorFunction( Object, unpack( arg ) ) - Count = Count + 1 --- if Count % 100 == 0 then --- coroutine.yield( false ) --- end - end - return true - end - --- local co = coroutine.create( CoRoutine ) - local co = CoRoutine - - local function Schedule() - --- local status, res = coroutine.resume( co ) - local status, res = co() - self:T3( { status, res } ) - - if status == false then - error( res ) - end - if res == false then - return true -- resume next time the loop - end - if FinalizeFunction then - FinalizeFunction( unpack( arg ) ) - end - return false - end - - local Scheduler = SCHEDULER:New( self, Schedule, {}, 0.001, 0.001, 0 ) - - return self -end - - ---- Iterate the DATABASE and call an iterator function for each **alive** UNIT, providing the UNIT and optional parameters. --- @param #DATABASE self --- @param #function IteratorFunction The function that will be called when there is an alive UNIT in the database. The function needs to accept a UNIT parameter. --- @return #DATABASE self -function DATABASE:ForEachUnit( IteratorFunction, FinalizeFunction, ... ) - self:F2( arg ) - - self:ForEach( IteratorFunction, FinalizeFunction, arg, self.UNITS ) - - return self -end - ---- Iterate the DATABASE and call an iterator function for each **alive** GROUP, providing the GROUP and optional parameters. --- @param #DATABASE self --- @param #function IteratorFunction The function that will be called when there is an alive GROUP in the database. The function needs to accept a GROUP parameter. --- @return #DATABASE self -function DATABASE:ForEachGroup( IteratorFunction, ... ) - self:F2( arg ) - - self:ForEach( IteratorFunction, arg, self.GROUPS ) - - return self -end - - ---- Iterate the DATABASE and call an iterator function for each **ALIVE** player, providing the player name and optional parameters. --- @param #DATABASE self --- @param #function IteratorFunction The function that will be called when there is an player in the database. The function needs to accept the player name. --- @return #DATABASE self -function DATABASE:ForEachPlayer( IteratorFunction, ... ) - self:F2( arg ) - - self:ForEach( IteratorFunction, arg, self.PLAYERS ) - - return self -end - - ---- Iterate the DATABASE and call an iterator function for each player who has joined the mission, providing the Unit of the player and optional parameters. --- @param #DATABASE self --- @param #function IteratorFunction The function that will be called when there is was a player in the database. The function needs to accept a UNIT parameter. --- @return #DATABASE self -function DATABASE:ForEachPlayerJoined( IteratorFunction, ... ) - self:F2( arg ) - - self:ForEach( IteratorFunction, arg, self.PLAYERSJOINED ) - - return self -end - ---- Iterate the DATABASE and call an iterator function for each CLIENT, providing the CLIENT to the function and optional parameters. --- @param #DATABASE self --- @param #function IteratorFunction The function that will be called when there is an alive player in the database. The function needs to accept a CLIENT parameter. --- @return #DATABASE self -function DATABASE:ForEachClient( IteratorFunction, ... ) - self:F2( arg ) - - self:ForEach( IteratorFunction, arg, self.CLIENTS ) - - return self -end - - -function DATABASE:_RegisterTemplates() - self:F2() - - self.Navpoints = {} - self.UNITS = {} - --Build routines.db.units and self.Navpoints - for CoalitionName, coa_data in pairs(env.mission.coalition) do - - if (CoalitionName == 'red' or CoalitionName == 'blue') and type(coa_data) == 'table' then - --self.Units[coa_name] = {} - - ---------------------------------------------- - -- build nav points DB - self.Navpoints[CoalitionName] = {} - if coa_data.nav_points then --navpoints - for nav_ind, nav_data in pairs(coa_data.nav_points) do - - if type(nav_data) == 'table' then - self.Navpoints[CoalitionName][nav_ind] = routines.utils.deepCopy(nav_data) - - self.Navpoints[CoalitionName][nav_ind]['name'] = nav_data.callsignStr -- name is a little bit more self-explanatory. - self.Navpoints[CoalitionName][nav_ind]['point'] = {} -- point is used by SSE, support it. - self.Navpoints[CoalitionName][nav_ind]['point']['x'] = nav_data.x - self.Navpoints[CoalitionName][nav_ind]['point']['y'] = 0 - self.Navpoints[CoalitionName][nav_ind]['point']['z'] = nav_data.y - end - end - end - ------------------------------------------------- - if coa_data.country then --there is a country table - for cntry_id, cntry_data in pairs(coa_data.country) do - - local CountryName = string.upper(cntry_data.name) - --self.Units[coa_name][countryName] = {} - --self.Units[coa_name][countryName]["countryId"] = cntry_data.id - - if type(cntry_data) == 'table' then --just making sure - - for obj_type_name, obj_type_data in pairs(cntry_data) do - - if obj_type_name == "helicopter" or obj_type_name == "ship" or obj_type_name == "plane" or obj_type_name == "vehicle" or obj_type_name == "static" then --should be an unncessary check - - local CategoryName = obj_type_name - - if ((type(obj_type_data) == 'table') and obj_type_data.group and (type(obj_type_data.group) == 'table') and (#obj_type_data.group > 0)) then --there's a group! - - --self.Units[coa_name][countryName][category] = {} - - for group_num, GroupTemplate in pairs(obj_type_data.group) do - - if GroupTemplate and GroupTemplate.units and type(GroupTemplate.units) == 'table' then --making sure again- this is a valid group - self:_RegisterTemplate( - GroupTemplate, - coalition.side[string.upper(CoalitionName)], - _DATABASECategory[string.lower(CategoryName)], - country.id[string.upper(CountryName)] - ) - end --if GroupTemplate and GroupTemplate.units then - end --for group_num, GroupTemplate in pairs(obj_type_data.group) do - end --if ((type(obj_type_data) == 'table') and obj_type_data.group and (type(obj_type_data.group) == 'table') and (#obj_type_data.group > 0)) then - end --if obj_type_name == "helicopter" or obj_type_name == "ship" or obj_type_name == "plane" or obj_type_name == "vehicle" or obj_type_name == "static" then - end --for obj_type_name, obj_type_data in pairs(cntry_data) do - end --if type(cntry_data) == 'table' then - end --for cntry_id, cntry_data in pairs(coa_data.country) do - end --if coa_data.country then --there is a country table - end --if coa_name == 'red' or coa_name == 'blue' and type(coa_data) == 'table' then - end --for coa_name, coa_data in pairs(mission.coalition) do - - return self -end - - - - ---- This module contains the SET classes. --- --- === --- --- 1) @{Set#SET_BASE} class, extends @{Base#BASE} --- ============================================== --- The @{Set#SET_BASE} class defines the core functions that define a collection of objects. --- A SET provides iterators to iterate the SET, but will **temporarily** yield the ForEach interator loop at defined **"intervals"** to the mail simulator loop. --- In this way, large loops can be done while not blocking the simulator main processing loop. --- The default **"yield interval"** is after 10 objects processed. --- The default **"time interval"** is after 0.001 seconds. --- --- 1.1) Add or remove objects from the SET --- --------------------------------------- --- Some key core functions are @{Set#SET_BASE.Add} and @{Set#SET_BASE.Remove} to add or remove objects from the SET in your logic. --- --- 1.2) Define the SET iterator **"yield interval"** and the **"time interval"** --- ----------------------------------------------------------------------------- --- Modify the iterator intervals with the @{Set#SET_BASE.SetInteratorIntervals} method. --- You can set the **"yield interval"**, and the **"time interval"**. (See above). --- --- === --- --- 2) @{Set#SET_GROUP} class, extends @{Set#SET_BASE} --- ================================================== --- Mission designers can use the @{Set#SET_GROUP} class to build sets of groups belonging to certain: --- --- * Coalitions --- * Categories --- * Countries --- * Starting with certain prefix strings. --- --- 2.1) SET_GROUP construction method: --- ----------------------------------- --- Create a new SET_GROUP object with the @{#SET_GROUP.New} method: --- --- * @{#SET_GROUP.New}: Creates a new SET_GROUP object. --- --- 2.2) Add or Remove GROUP(s) from SET_GROUP: --- ------------------------------------------- --- GROUPS can be added and removed using the @{Set#SET_GROUP.AddGroupsByName} and @{Set#SET_GROUP.RemoveGroupsByName} respectively. --- These methods take a single GROUP name or an array of GROUP names to be added or removed from SET_GROUP. --- --- 2.3) SET_GROUP filter criteria: --- ------------------------------- --- You can set filter criteria to define the set of groups within the SET_GROUP. --- Filter criteria are defined by: --- --- * @{#SET_GROUP.FilterCoalitions}: Builds the SET_GROUP with the groups belonging to the coalition(s). --- * @{#SET_GROUP.FilterCategories}: Builds the SET_GROUP with the groups belonging to the category(ies). --- * @{#SET_GROUP.FilterCountries}: Builds the SET_GROUP with the gruops belonging to the country(ies). --- * @{#SET_GROUP.FilterPrefixes}: Builds the SET_GROUP with the groups starting with the same prefix string(s). --- --- Once the filter criteria have been set for the SET_GROUP, you can start filtering using: --- --- * @{#SET_GROUP.FilterStart}: Starts the filtering of the groups within the SET_GROUP and add or remove GROUP objects **dynamically**. --- --- Planned filter criteria within development are (so these are not yet available): --- --- * @{#SET_GROUP.FilterZones}: Builds the SET_GROUP with the groups within a @{Zone#ZONE}. --- --- 2.4) SET_GROUP iterators: --- ------------------------- --- Once the filters have been defined and the SET_GROUP has been built, you can iterate the SET_GROUP with the available iterator methods. --- The iterator methods will walk the SET_GROUP set, and call for each element within the set a function that you provide. --- The following iterator methods are currently available within the SET_GROUP: --- --- * @{#SET_GROUP.ForEachGroup}: Calls a function for each alive group it finds within the SET_GROUP. --- * @{#SET_GROUP.ForEachGroupCompletelyInZone}: Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence completely in a @{Zone}, providing the GROUP and optional parameters to the called function. --- * @{#SET_GROUP.ForEachGroupPartlyInZone}: Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence partly in a @{Zone}, providing the GROUP and optional parameters to the called function. --- * @{#SET_GROUP.ForEachGroupNotInZone}: Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence not in a @{Zone}, providing the GROUP and optional parameters to the called function. --- --- ==== --- --- 3) @{Set#SET_UNIT} class, extends @{Set#SET_BASE} --- =================================================== --- Mission designers can use the @{Set#SET_UNIT} class to build sets of units belonging to certain: --- --- * Coalitions --- * Categories --- * Countries --- * Unit types --- * Starting with certain prefix strings. --- --- 3.1) SET_UNIT construction method: --- ---------------------------------- --- Create a new SET_UNIT object with the @{#SET_UNIT.New} method: --- --- * @{#SET_UNIT.New}: Creates a new SET_UNIT object. --- --- 3.2) Add or Remove UNIT(s) from SET_UNIT: --- ----------------------------------------- --- UNITs can be added and removed using the @{Set#SET_UNIT.AddUnitsByName} and @{Set#SET_UNIT.RemoveUnitsByName} respectively. --- These methods take a single UNIT name or an array of UNIT names to be added or removed from SET_UNIT. --- --- 3.3) SET_UNIT filter criteria: --- ------------------------------ --- You can set filter criteria to define the set of units within the SET_UNIT. --- Filter criteria are defined by: --- --- * @{#SET_UNIT.FilterCoalitions}: Builds the SET_UNIT with the units belonging to the coalition(s). --- * @{#SET_UNIT.FilterCategories}: Builds the SET_UNIT with the units belonging to the category(ies). --- * @{#SET_UNIT.FilterTypes}: Builds the SET_UNIT with the units belonging to the unit type(s). --- * @{#SET_UNIT.FilterCountries}: Builds the SET_UNIT with the units belonging to the country(ies). --- * @{#SET_UNIT.FilterPrefixes}: Builds the SET_UNIT with the units starting with the same prefix string(s). --- --- Once the filter criteria have been set for the SET_UNIT, you can start filtering using: --- --- * @{#SET_UNIT.FilterStart}: Starts the filtering of the units within the SET_UNIT. --- --- Planned filter criteria within development are (so these are not yet available): --- --- * @{#SET_UNIT.FilterZones}: Builds the SET_UNIT with the units within a @{Zone#ZONE}. --- --- 3.4) SET_UNIT iterators: --- ------------------------ --- Once the filters have been defined and the SET_UNIT has been built, you can iterate the SET_UNIT with the available iterator methods. --- The iterator methods will walk the SET_UNIT set, and call for each element within the set a function that you provide. --- The following iterator methods are currently available within the SET_UNIT: --- --- * @{#SET_UNIT.ForEachUnit}: Calls a function for each alive unit it finds within the SET_UNIT. --- * @{#SET_GROUP.ForEachGroupCompletelyInZone}: Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence completely in a @{Zone}, providing the GROUP and optional parameters to the called function. --- * @{#SET_GROUP.ForEachGroupNotInZone}: Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence not in a @{Zone}, providing the GROUP and optional parameters to the called function. --- --- Planned iterators methods in development are (so these are not yet available): --- --- * @{#SET_UNIT.ForEachUnitInUnit}: Calls a function for each unit contained within the SET_UNIT. --- * @{#SET_UNIT.ForEachUnitCompletelyInZone}: Iterate and call an iterator function for each **alive** UNIT presence completely in a @{Zone}, providing the UNIT and optional parameters to the called function. --- * @{#SET_UNIT.ForEachUnitNotInZone}: Iterate and call an iterator function for each **alive** UNIT presence not in a @{Zone}, providing the UNIT and optional parameters to the called function. --- --- === --- --- 4) @{Set#SET_CLIENT} class, extends @{Set#SET_BASE} --- =================================================== --- Mission designers can use the @{Set#SET_CLIENT} class to build sets of units belonging to certain: --- --- * Coalitions --- * Categories --- * Countries --- * Client types --- * Starting with certain prefix strings. --- --- 4.1) SET_CLIENT construction method: --- ---------------------------------- --- Create a new SET_CLIENT object with the @{#SET_CLIENT.New} method: --- --- * @{#SET_CLIENT.New}: Creates a new SET_CLIENT object. --- --- 4.2) Add or Remove CLIENT(s) from SET_CLIENT: --- ----------------------------------------- --- CLIENTs can be added and removed using the @{Set#SET_CLIENT.AddClientsByName} and @{Set#SET_CLIENT.RemoveClientsByName} respectively. --- These methods take a single CLIENT name or an array of CLIENT names to be added or removed from SET_CLIENT. --- --- 4.3) SET_CLIENT filter criteria: --- ------------------------------ --- You can set filter criteria to define the set of clients within the SET_CLIENT. --- Filter criteria are defined by: --- --- * @{#SET_CLIENT.FilterCoalitions}: Builds the SET_CLIENT with the clients belonging to the coalition(s). --- * @{#SET_CLIENT.FilterCategories}: Builds the SET_CLIENT with the clients belonging to the category(ies). --- * @{#SET_CLIENT.FilterTypes}: Builds the SET_CLIENT with the clients belonging to the client type(s). --- * @{#SET_CLIENT.FilterCountries}: Builds the SET_CLIENT with the clients belonging to the country(ies). --- * @{#SET_CLIENT.FilterPrefixes}: Builds the SET_CLIENT with the clients starting with the same prefix string(s). --- --- Once the filter criteria have been set for the SET_CLIENT, you can start filtering using: --- --- * @{#SET_CLIENT.FilterStart}: Starts the filtering of the clients within the SET_CLIENT. --- --- Planned filter criteria within development are (so these are not yet available): --- --- * @{#SET_CLIENT.FilterZones}: Builds the SET_CLIENT with the clients within a @{Zone#ZONE}. --- --- 4.4) SET_CLIENT iterators: --- ------------------------ --- Once the filters have been defined and the SET_CLIENT has been built, you can iterate the SET_CLIENT with the available iterator methods. --- The iterator methods will walk the SET_CLIENT set, and call for each element within the set a function that you provide. --- The following iterator methods are currently available within the SET_CLIENT: --- --- * @{#SET_CLIENT.ForEachClient}: Calls a function for each alive client it finds within the SET_CLIENT. --- --- ==== --- --- 5) @{Set#SET_AIRBASE} class, extends @{Set#SET_BASE} --- ==================================================== --- Mission designers can use the @{Set#SET_AIRBASE} class to build sets of airbases optionally belonging to certain: --- --- * Coalitions --- --- 5.1) SET_AIRBASE construction --- ----------------------------- --- Create a new SET_AIRBASE object with the @{#SET_AIRBASE.New} method: --- --- * @{#SET_AIRBASE.New}: Creates a new SET_AIRBASE object. --- --- 5.2) Add or Remove AIRBASEs from SET_AIRBASE --- -------------------------------------------- --- AIRBASEs can be added and removed using the @{Set#SET_AIRBASE.AddAirbasesByName} and @{Set#SET_AIRBASE.RemoveAirbasesByName} respectively. --- These methods take a single AIRBASE name or an array of AIRBASE names to be added or removed from SET_AIRBASE. --- --- 5.3) SET_AIRBASE filter criteria --- -------------------------------- --- You can set filter criteria to define the set of clients within the SET_AIRBASE. --- Filter criteria are defined by: --- --- * @{#SET_AIRBASE.FilterCoalitions}: Builds the SET_AIRBASE with the airbases belonging to the coalition(s). --- --- Once the filter criteria have been set for the SET_AIRBASE, you can start filtering using: --- --- * @{#SET_AIRBASE.FilterStart}: Starts the filtering of the airbases within the SET_AIRBASE. --- --- 5.4) SET_AIRBASE iterators: --- --------------------------- --- Once the filters have been defined and the SET_AIRBASE has been built, you can iterate the SET_AIRBASE with the available iterator methods. --- The iterator methods will walk the SET_AIRBASE set, and call for each airbase within the set a function that you provide. --- The following iterator methods are currently available within the SET_AIRBASE: --- --- * @{#SET_AIRBASE.ForEachAirbase}: Calls a function for each airbase it finds within the SET_AIRBASE. --- --- ==== --- --- @module Set --- @author FlightControl - - ---- SET_BASE class --- @type SET_BASE --- @field #table Filter --- @field #table Set --- @field #table List --- @extends Base#BASE -SET_BASE = { - ClassName = "SET_BASE", - Filter = {}, - Set = {}, - List = {}, -} - ---- Creates a new SET_BASE object, building a set of units belonging to a coalitions, categories, countries, types or with defined prefix names. --- @param #SET_BASE self --- @return #SET_BASE --- @usage --- -- Define a new SET_BASE Object. This DBObject will contain a reference to all Group and Unit Templates defined within the ME and the DCSRTE. --- DBObject = SET_BASE:New() -function SET_BASE:New( Database ) - - -- Inherits from BASE - local self = BASE:Inherit( self, BASE:New() ) - - self.Database = Database - - self.YieldInterval = 10 - self.TimeInterval = 0.001 - - self.List = {} - self.List.__index = self.List - self.List = setmetatable( { Count = 0 }, self.List ) - - return self -end - ---- Finds an @{Base#BASE} object based on the object Name. --- @param #SET_BASE self --- @param #string ObjectName --- @return Base#BASE The Object found. -function SET_BASE:_Find( ObjectName ) - - local ObjectFound = self.Set[ObjectName] - return ObjectFound -end - - ---- Gets the Set. --- @param #SET_BASE self --- @return #SET_BASE self -function SET_BASE:GetSet() - self:F2() - - return self.Set -end - ---- Adds a @{Base#BASE} object in the @{Set#SET_BASE}, using the Object Name as the index. --- @param #SET_BASE self --- @param #string ObjectName --- @param Base#BASE Object --- @return Base#BASE The added BASE Object. -function SET_BASE:Add( ObjectName, Object ) - self:F2( ObjectName ) - - local t = { _ = Object } - - if self.List.last then - self.List.last._next = t - t._prev = self.List.last - self.List.last = t - else - -- this is the first node - self.List.first = t - self.List.last = t - end - - self.List.Count = self.List.Count + 1 - - self.Set[ObjectName] = t._ - -end - ---- Removes a @{Base#BASE} object from the @{Set#SET_BASE} and derived classes, based on the Object Name. --- @param #SET_BASE self --- @param #string ObjectName -function SET_BASE:Remove( ObjectName ) - self:E( ObjectName ) - - local t = self.Set[ObjectName] - - if t then - if t._next then - if t._prev then - t._next._prev = t._prev - t._prev._next = t._next - else - -- this was the first node - t._next._prev = nil - self.List._first = t._next - end - elseif t._prev then - -- this was the last node - t._prev._next = nil - self.List._last = t._prev - else - -- this was the only node - self.List._first = nil - self.List._last = nil - end - - t._next = nil - t._prev = nil - self.List.Count = self.List.Count - 1 - - self.Set[ObjectName] = nil - end - -end - ---- Retrieves the amount of objects in the @{Set#SET_BASE} and derived classes. --- @param #SET_BASE self --- @return #number Count -function SET_BASE:Count() - - return self.List.Count -end - - - ---- Copies the Filter criteria from a given Set (for rebuilding a new Set based on an existing Set). --- @param #SET_BASE self --- @param #SET_BASE BaseSet --- @return #SET_BASE -function SET_BASE:SetDatabase( BaseSet ) - - -- Copy the filter criteria of the BaseSet - local OtherFilter = routines.utils.deepCopy( BaseSet.Filter ) - self.Filter = OtherFilter - - -- Now base the new Set on the BaseSet - self.Database = BaseSet:GetSet() - return self -end - - - ---- Define the SET iterator **"yield interval"** and the **"time interval"**. --- @param #SET_BASE self --- @param #number YieldInterval Sets the frequency when the iterator loop will yield after the number of objects processed. The default frequency is 10 objects processed. --- @param #number TimeInterval Sets the time in seconds when the main logic will resume the iterator loop. The default time is 0.001 seconds. --- @return #SET_BASE self -function SET_BASE:SetIteratorIntervals( YieldInterval, TimeInterval ) - - self.YieldInterval = YieldInterval - self.TimeInterval = TimeInterval - - return self -end - - ---- Filters for the defined collection. --- @param #SET_BASE self --- @return #SET_BASE self -function SET_BASE:FilterOnce() - - for ObjectName, Object in pairs( self.Database ) do - - if self:IsIncludeObject( Object ) then - self:Add( ObjectName, Object ) - end - end - - return self -end - ---- Starts the filtering for the defined collection. --- @param #SET_BASE self --- @return #SET_BASE self -function SET_BASE:_FilterStart() - - for ObjectName, Object in pairs( self.Database ) do - - if self:IsIncludeObject( Object ) then - self:E( { "Adding Object:", ObjectName } ) - self:Add( ObjectName, Object ) - end - end - - _EVENTDISPATCHER:OnBirth( self._EventOnBirth, self ) - _EVENTDISPATCHER:OnDead( self._EventOnDeadOrCrash, self ) - _EVENTDISPATCHER:OnCrash( self._EventOnDeadOrCrash, self ) - - -- Follow alive players and clients - _EVENTDISPATCHER:OnPlayerEnterUnit( self._EventOnPlayerEnterUnit, self ) - _EVENTDISPATCHER:OnPlayerLeaveUnit( self._EventOnPlayerLeaveUnit, self ) - - - return self -end - ---- Stops the filtering for the defined collection. --- @param #SET_BASE self --- @return #SET_BASE self -function SET_BASE:FilterStop() - - _EVENTDISPATCHER:OnBirthRemove( self ) - _EVENTDISPATCHER:OnDeadRemove( self ) - _EVENTDISPATCHER:OnCrashRemove( self ) - - return self -end - ---- Iterate the SET_BASE while identifying the nearest object from a @{Point#POINT_VEC2}. --- @param #SET_BASE self --- @param Point#POINT_VEC2 PointVec2 A @{Point#POINT_VEC2} object from where to evaluate the closest object in the set. --- @return Base#BASE The closest object. -function SET_BASE:FindNearestObjectFromPointVec2( PointVec2 ) - self:F2( PointVec2 ) - - local NearestObject = nil - local ClosestDistance = nil - - for ObjectID, ObjectData in pairs( self.Set ) do - if NearestObject == nil then - NearestObject = ObjectData - ClosestDistance = PointVec2:DistanceFromVec2( ObjectData:GetVec2() ) - else - local Distance = PointVec2:DistanceFromVec2( ObjectData:GetVec2() ) - if Distance < ClosestDistance then - NearestObject = ObjectData - ClosestDistance = Distance - end - end - end - - return NearestObject -end - - - ------ Private method that registers all alive players in the mission. ----- @param #SET_BASE self ----- @return #SET_BASE self ---function SET_BASE:_RegisterPlayers() --- --- local CoalitionsData = { AlivePlayersRed = coalition.getPlayers( coalition.side.RED ), AlivePlayersBlue = coalition.getPlayers( coalition.side.BLUE ) } --- for CoalitionId, CoalitionData in pairs( CoalitionsData ) do --- for UnitId, UnitData in pairs( CoalitionData ) do --- self:T3( { "UnitData:", UnitData } ) --- if UnitData and UnitData:isExist() then --- local UnitName = UnitData:getName() --- if not self.PlayersAlive[UnitName] then --- self:E( { "Add player for unit:", UnitName, UnitData:getPlayerName() } ) --- self.PlayersAlive[UnitName] = UnitData:getPlayerName() --- end --- end --- end --- end --- --- return self ---end - ---- Events - ---- Handles the OnBirth event for the Set. --- @param #SET_BASE self --- @param Event#EVENTDATA Event -function SET_BASE:_EventOnBirth( Event ) - self:F3( { Event } ) - - if Event.IniDCSUnit then - local ObjectName, Object = self:AddInDatabase( Event ) - self:T3( ObjectName, Object ) - if self:IsIncludeObject( Object ) then - self:Add( ObjectName, Object ) - --self:_EventOnPlayerEnterUnit( Event ) - end - end -end - ---- Handles the OnDead or OnCrash event for alive units set. --- @param #SET_BASE self --- @param Event#EVENTDATA Event -function SET_BASE:_EventOnDeadOrCrash( Event ) - self:E( { Event } ) - - if Event.IniDCSUnit then - local ObjectName, Object = self:FindInDatabase( Event ) - self:E({ObjectName, Object}) - if ObjectName and Object ~= nil then - self:Remove( ObjectName ) - end - end -end - ---- Handles the OnPlayerEnterUnit event to fill the active players table (with the unit filter applied). --- @param #SET_BASE self --- @param Event#EVENTDATA Event -function SET_BASE:_EventOnPlayerEnterUnit( Event ) - self:F3( { Event } ) - - if Event.IniDCSUnit then - local ObjectName, Object = self:AddInDatabase( Event ) - self:T3( ObjectName, Object ) - if self:IsIncludeObject( Object ) then - self:Add( ObjectName, Object ) - --self:_EventOnPlayerEnterUnit( Event ) - end - end -end - ---- Handles the OnPlayerLeaveUnit event to clean the active players table. --- @param #SET_BASE self --- @param Event#EVENTDATA Event -function SET_BASE:_EventOnPlayerLeaveUnit( Event ) - self:F3( { Event } ) - - local ObjectName = Event.IniDCSUnit - if Event.IniDCSUnit then - if Event.IniDCSGroup then - local GroupUnits = Event.IniDCSGroup:getUnits() - local PlayerCount = 0 - for _, DCSUnit in pairs( GroupUnits ) do - if DCSUnit ~= Event.IniDCSUnit then - if DCSUnit:getPlayer() ~= nil then - PlayerCount = PlayerCount + 1 - end - end - end - self:E(PlayerCount) - if PlayerCount == 0 then - self:Remove( Event.IniDCSGroupName ) - end - end - end -end - --- Iterators - ---- Iterate the SET_BASE and derived classes and call an iterator function for the given SET_BASE, providing the Object for each element within the set and optional parameters. --- @param #SET_BASE self --- @param #function IteratorFunction The function that will be called. --- @return #SET_BASE self -function SET_BASE:ForEach( IteratorFunction, arg, Set, Function, FunctionArguments ) - self:F3( arg ) - - local function CoRoutine() - local Count = 0 - for ObjectID, ObjectData in pairs( Set ) do - local Object = ObjectData - self:T3( Object ) - if Function then - if Function( unpack( FunctionArguments ), Object ) == true then - IteratorFunction( Object, unpack( arg ) ) - end - else - IteratorFunction( Object, unpack( arg ) ) - end - Count = Count + 1 --- if Count % self.YieldInterval == 0 then --- coroutine.yield( false ) --- end - end - return true - end - --- local co = coroutine.create( CoRoutine ) - local co = CoRoutine - - local function Schedule() - --- local status, res = coroutine.resume( co ) - local status, res = co() - self:T3( { status, res } ) - - if status == false then - error( res ) - end - if res == false then - return true -- resume next time the loop - end - - return false - end - - local Scheduler = SCHEDULER:New( self, Schedule, {}, self.TimeInterval, self.TimeInterval, 0 ) - - return self -end - - ------ Iterate the SET_BASE and call an interator function for each **alive** unit, providing the Unit and optional parameters. ----- @param #SET_BASE self ----- @param #function IteratorFunction The function that will be called when there is an alive unit in the SET_BASE. The function needs to accept a UNIT parameter. ----- @return #SET_BASE self ---function SET_BASE:ForEachDCSUnitAlive( IteratorFunction, ... ) --- self:F3( arg ) --- --- self:ForEach( IteratorFunction, arg, self.DCSUnitsAlive ) --- --- return self ---end --- ------ Iterate the SET_BASE and call an interator function for each **alive** player, providing the Unit of the player and optional parameters. ----- @param #SET_BASE self ----- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_BASE. The function needs to accept a UNIT parameter. ----- @return #SET_BASE self ---function SET_BASE:ForEachPlayer( IteratorFunction, ... ) --- self:F3( arg ) --- --- self:ForEach( IteratorFunction, arg, self.PlayersAlive ) --- --- return self ---end --- --- ------ Iterate the SET_BASE and call an interator function for each client, providing the Client to the function and optional parameters. ----- @param #SET_BASE self ----- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_BASE. The function needs to accept a CLIENT parameter. ----- @return #SET_BASE self ---function SET_BASE:ForEachClient( IteratorFunction, ... ) --- self:F3( arg ) --- --- self:ForEach( IteratorFunction, arg, self.Clients ) --- --- return self ---end - - ---- Decides whether to include the Object --- @param #SET_BASE self --- @param #table Object --- @return #SET_BASE self -function SET_BASE:IsIncludeObject( Object ) - self:F3( Object ) - - return true -end - ---- Flushes the current SET_BASE contents in the log ... (for debugging reasons). --- @param #SET_BASE self --- @return #string A string with the names of the objects. -function SET_BASE:Flush() - self:F3() - - local ObjectNames = "" - for ObjectName, Object in pairs( self.Set ) do - ObjectNames = ObjectNames .. ObjectName .. ", " - end - self:E( { "Objects in Set:", ObjectNames } ) - - return ObjectNames -end - --- SET_GROUP - ---- SET_GROUP class --- @type SET_GROUP --- @extends Set#SET_BASE -SET_GROUP = { - ClassName = "SET_GROUP", - Filter = { - Coalitions = nil, - Categories = nil, - Countries = nil, - GroupPrefixes = nil, - }, - FilterMeta = { - Coalitions = { - red = coalition.side.RED, - blue = coalition.side.BLUE, - neutral = coalition.side.NEUTRAL, - }, - Categories = { - plane = Group.Category.AIRPLANE, - helicopter = Group.Category.HELICOPTER, - ground = Group.Category.GROUND_UNIT, - ship = Group.Category.SHIP, - structure = Group.Category.STRUCTURE, - }, - }, -} - - ---- Creates a new SET_GROUP object, building a set of groups belonging to a coalitions, categories, countries, types or with defined prefix names. --- @param #SET_GROUP self --- @return #SET_GROUP --- @usage --- -- Define a new SET_GROUP Object. This DBObject will contain a reference to all alive GROUPS. --- DBObject = SET_GROUP:New() -function SET_GROUP:New() - - -- Inherits from BASE - local self = BASE:Inherit( self, SET_BASE:New( _DATABASE.GROUPS ) ) - - return self -end - ---- Add GROUP(s) to SET_GROUP. --- @param Set#SET_GROUP self --- @param #string AddGroupNames A single name or an array of GROUP names. --- @return self -function SET_GROUP:AddGroupsByName( AddGroupNames ) - - local AddGroupNamesArray = ( type( AddGroupNames ) == "table" ) and AddGroupNames or { AddGroupNames } - - for AddGroupID, AddGroupName in pairs( AddGroupNamesArray ) do - self:Add( AddGroupName, GROUP:FindByName( AddGroupName ) ) - end - - return self -end - ---- Remove GROUP(s) from SET_GROUP. --- @param Set#SET_GROUP self --- @param Group#GROUP RemoveGroupNames A single name or an array of GROUP names. --- @return self -function SET_GROUP:RemoveGroupsByName( RemoveGroupNames ) - - local RemoveGroupNamesArray = ( type( RemoveGroupNames ) == "table" ) and RemoveGroupNames or { RemoveGroupNames } - - for RemoveGroupID, RemoveGroupName in pairs( RemoveGroupNamesArray ) do - self:Remove( RemoveGroupName.GroupName ) - end - - return self -end - - - - ---- Finds a Group based on the Group Name. --- @param #SET_GROUP self --- @param #string GroupName --- @return Group#GROUP The found Group. -function SET_GROUP:FindGroup( GroupName ) - - local GroupFound = self.Set[GroupName] - return GroupFound -end - - - ---- Builds a set of groups of coalitions. --- Possible current coalitions are red, blue and neutral. --- @param #SET_GROUP self --- @param #string Coalitions Can take the following values: "red", "blue", "neutral". --- @return #SET_GROUP self -function SET_GROUP:FilterCoalitions( Coalitions ) - if not self.Filter.Coalitions then - self.Filter.Coalitions = {} - end - if type( Coalitions ) ~= "table" then - Coalitions = { Coalitions } - end - for CoalitionID, Coalition in pairs( Coalitions ) do - self.Filter.Coalitions[Coalition] = Coalition - end - return self -end - - ---- Builds a set of groups out of categories. --- Possible current categories are plane, helicopter, ground, ship. --- @param #SET_GROUP self --- @param #string Categories Can take the following values: "plane", "helicopter", "ground", "ship". --- @return #SET_GROUP self -function SET_GROUP:FilterCategories( Categories ) - if not self.Filter.Categories then - self.Filter.Categories = {} - end - if type( Categories ) ~= "table" then - Categories = { Categories } - end - for CategoryID, Category in pairs( Categories ) do - self.Filter.Categories[Category] = Category - end - return self -end - ---- Builds a set of groups of defined countries. --- Possible current countries are those known within DCS world. --- @param #SET_GROUP self --- @param #string Countries Can take those country strings known within DCS world. --- @return #SET_GROUP self -function SET_GROUP:FilterCountries( Countries ) - if not self.Filter.Countries then - self.Filter.Countries = {} - end - if type( Countries ) ~= "table" then - Countries = { Countries } - end - for CountryID, Country in pairs( Countries ) do - self.Filter.Countries[Country] = Country - end - return self -end - - ---- Builds a set of groups of defined GROUP prefixes. --- All the groups starting with the given prefixes will be included within the set. --- @param #SET_GROUP self --- @param #string Prefixes The prefix of which the group name starts with. --- @return #SET_GROUP self -function SET_GROUP:FilterPrefixes( Prefixes ) - if not self.Filter.GroupPrefixes then - self.Filter.GroupPrefixes = {} - end - if type( Prefixes ) ~= "table" then - Prefixes = { Prefixes } - end - for PrefixID, Prefix in pairs( Prefixes ) do - self.Filter.GroupPrefixes[Prefix] = Prefix - end - return self -end - - ---- Starts the filtering. --- @param #SET_GROUP self --- @return #SET_GROUP self -function SET_GROUP:FilterStart() - - if _DATABASE then - self:_FilterStart() - end - - - - return self -end - ---- Handles the Database to check on an event (birth) that the Object was added in the Database. --- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! --- @param #SET_GROUP self --- @param Event#EVENTDATA Event --- @return #string The name of the GROUP --- @return #table The GROUP -function SET_GROUP:AddInDatabase( Event ) - self:F3( { Event } ) - - if not self.Database[Event.IniDCSGroupName] then - self.Database[Event.IniDCSGroupName] = GROUP:Register( Event.IniDCSGroupName ) - self:T3( self.Database[Event.IniDCSGroupName] ) - end - - return Event.IniDCSGroupName, self.Database[Event.IniDCSGroupName] -end - ---- Handles the Database to check on any event that Object exists in the Database. --- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa! --- @param #SET_GROUP self --- @param Event#EVENTDATA Event --- @return #string The name of the GROUP --- @return #table The GROUP -function SET_GROUP:FindInDatabase( Event ) - self:F3( { Event } ) - - return Event.IniDCSGroupName, self.Database[Event.IniDCSGroupName] -end - ---- Iterate the SET_GROUP and call an iterator function for each **alive** GROUP, providing the GROUP and optional parameters. --- @param #SET_GROUP self --- @param #function IteratorFunction The function that will be called when there is an alive GROUP in the SET_GROUP. The function needs to accept a GROUP parameter. --- @return #SET_GROUP self -function SET_GROUP:ForEachGroup( IteratorFunction, ... ) - self:F2( arg ) - - self:ForEach( IteratorFunction, arg, self.Set ) - - return self -end - ---- Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence completely in a @{Zone}, providing the GROUP and optional parameters to the called function. --- @param #SET_GROUP self --- @param Zone#ZONE ZoneObject The Zone to be tested for. --- @param #function IteratorFunction The function that will be called when there is an alive GROUP in the SET_GROUP. The function needs to accept a GROUP parameter. --- @return #SET_GROUP self -function SET_GROUP:ForEachGroupCompletelyInZone( ZoneObject, IteratorFunction, ... ) - self:F2( arg ) - - self:ForEach( IteratorFunction, arg, self.Set, - --- @param Zone#ZONE_BASE ZoneObject - -- @param Group#GROUP GroupObject - function( ZoneObject, GroupObject ) - if GroupObject:IsCompletelyInZone( ZoneObject ) then - return true - else - return false - end - end, { ZoneObject } ) - - return self -end - ---- Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence partly in a @{Zone}, providing the GROUP and optional parameters to the called function. --- @param #SET_GROUP self --- @param Zone#ZONE ZoneObject The Zone to be tested for. --- @param #function IteratorFunction The function that will be called when there is an alive GROUP in the SET_GROUP. The function needs to accept a GROUP parameter. --- @return #SET_GROUP self -function SET_GROUP:ForEachGroupPartlyInZone( ZoneObject, IteratorFunction, ... ) - self:F2( arg ) - - self:ForEach( IteratorFunction, arg, self.Set, - --- @param Zone#ZONE_BASE ZoneObject - -- @param Group#GROUP GroupObject - function( ZoneObject, GroupObject ) - if GroupObject:IsPartlyInZone( ZoneObject ) then - return true - else - return false - end - end, { ZoneObject } ) - - return self -end - ---- Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence not in a @{Zone}, providing the GROUP and optional parameters to the called function. --- @param #SET_GROUP self --- @param Zone#ZONE ZoneObject The Zone to be tested for. --- @param #function IteratorFunction The function that will be called when there is an alive GROUP in the SET_GROUP. The function needs to accept a GROUP parameter. --- @return #SET_GROUP self -function SET_GROUP:ForEachGroupNotInZone( ZoneObject, IteratorFunction, ... ) - self:F2( arg ) - - self:ForEach( IteratorFunction, arg, self.Set, - --- @param Zone#ZONE_BASE ZoneObject - -- @param Group#GROUP GroupObject - function( ZoneObject, GroupObject ) - if GroupObject:IsNotInZone( ZoneObject ) then - return true - else - return false - end - end, { ZoneObject } ) - - return self -end - - ------ Iterate the SET_GROUP and call an interator function for each **alive** player, providing the Group of the player and optional parameters. ----- @param #SET_GROUP self ----- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_GROUP. The function needs to accept a GROUP parameter. ----- @return #SET_GROUP self ---function SET_GROUP:ForEachPlayer( IteratorFunction, ... ) --- self:F2( arg ) --- --- self:ForEach( IteratorFunction, arg, self.PlayersAlive ) --- --- return self ---end --- --- ------ Iterate the SET_GROUP and call an interator function for each client, providing the Client to the function and optional parameters. ----- @param #SET_GROUP self ----- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_GROUP. The function needs to accept a CLIENT parameter. ----- @return #SET_GROUP self ---function SET_GROUP:ForEachClient( IteratorFunction, ... ) --- self:F2( arg ) --- --- self:ForEach( IteratorFunction, arg, self.Clients ) --- --- return self ---end - - ---- --- @param #SET_GROUP self --- @param Group#GROUP MooseGroup --- @return #SET_GROUP self -function SET_GROUP:IsIncludeObject( MooseGroup ) - self:F2( MooseGroup ) - local MooseGroupInclude = true - - if self.Filter.Coalitions then - local MooseGroupCoalition = false - for CoalitionID, CoalitionName in pairs( self.Filter.Coalitions ) do - self:T3( { "Coalition:", MooseGroup:GetCoalition(), self.FilterMeta.Coalitions[CoalitionName], CoalitionName } ) - if self.FilterMeta.Coalitions[CoalitionName] and self.FilterMeta.Coalitions[CoalitionName] == MooseGroup:GetCoalition() then - MooseGroupCoalition = true - end - end - MooseGroupInclude = MooseGroupInclude and MooseGroupCoalition - end - - if self.Filter.Categories then - local MooseGroupCategory = false - for CategoryID, CategoryName in pairs( self.Filter.Categories ) do - self:T3( { "Category:", MooseGroup:GetCategory(), self.FilterMeta.Categories[CategoryName], CategoryName } ) - if self.FilterMeta.Categories[CategoryName] and self.FilterMeta.Categories[CategoryName] == MooseGroup:GetCategory() then - MooseGroupCategory = true - end - end - MooseGroupInclude = MooseGroupInclude and MooseGroupCategory - end - - if self.Filter.Countries then - local MooseGroupCountry = false - for CountryID, CountryName in pairs( self.Filter.Countries ) do - self:T3( { "Country:", MooseGroup:GetCountry(), CountryName } ) - if country.id[CountryName] == MooseGroup:GetCountry() then - MooseGroupCountry = true - end - end - MooseGroupInclude = MooseGroupInclude and MooseGroupCountry - end - - if self.Filter.GroupPrefixes then - local MooseGroupPrefix = false - for GroupPrefixId, GroupPrefix in pairs( self.Filter.GroupPrefixes ) do - self:T3( { "Prefix:", string.find( MooseGroup:GetName(), GroupPrefix, 1 ), GroupPrefix } ) - if string.find( MooseGroup:GetName(), GroupPrefix, 1 ) then - MooseGroupPrefix = true - end - end - MooseGroupInclude = MooseGroupInclude and MooseGroupPrefix - end - - self:T2( MooseGroupInclude ) - return MooseGroupInclude -end - ---- SET_UNIT class --- @type SET_UNIT --- @extends Set#SET_BASE -SET_UNIT = { - ClassName = "SET_UNIT", - Units = {}, - Filter = { - Coalitions = nil, - Categories = nil, - Types = nil, - Countries = nil, - UnitPrefixes = nil, - }, - FilterMeta = { - Coalitions = { - red = coalition.side.RED, - blue = coalition.side.BLUE, - neutral = coalition.side.NEUTRAL, - }, - Categories = { - plane = Unit.Category.AIRPLANE, - helicopter = Unit.Category.HELICOPTER, - ground = Unit.Category.GROUND_UNIT, - ship = Unit.Category.SHIP, - structure = Unit.Category.STRUCTURE, - }, - }, -} - - ---- Creates a new SET_UNIT object, building a set of units belonging to a coalitions, categories, countries, types or with defined prefix names. --- @param #SET_UNIT self --- @return #SET_UNIT --- @usage --- -- Define a new SET_UNIT Object. This DBObject will contain a reference to all alive Units. --- DBObject = SET_UNIT:New() -function SET_UNIT:New() - - -- Inherits from BASE - local self = BASE:Inherit( self, SET_BASE:New( _DATABASE.UNITS ) ) - - return self -end - ---- Add UNIT(s) to SET_UNIT. --- @param #SET_UNIT self --- @param #string AddUnit A single UNIT. --- @return #SET_UNIT self -function SET_UNIT:AddUnit( AddUnit ) - self:F2( AddUnit:GetName() ) - - self:Add( AddUnit:GetName(), AddUnit ) - - return self -end - - ---- Add UNIT(s) to SET_UNIT. --- @param #SET_UNIT self --- @param #string AddUnitNames A single name or an array of UNIT names. --- @return #SET_UNIT self -function SET_UNIT:AddUnitsByName( AddUnitNames ) - - local AddUnitNamesArray = ( type( AddUnitNames ) == "table" ) and AddUnitNames or { AddUnitNames } - - self:T( AddUnitNamesArray ) - for AddUnitID, AddUnitName in pairs( AddUnitNamesArray ) do - self:Add( AddUnitName, UNIT:FindByName( AddUnitName ) ) - end - - return self -end - ---- Remove UNIT(s) from SET_UNIT. --- @param Set#SET_UNIT self --- @param Unit#UNIT RemoveUnitNames A single name or an array of UNIT names. --- @return self -function SET_UNIT:RemoveUnitsByName( RemoveUnitNames ) - - local RemoveUnitNamesArray = ( type( RemoveUnitNames ) == "table" ) and RemoveUnitNames or { RemoveUnitNames } - - for RemoveUnitID, RemoveUnitName in pairs( RemoveUnitNamesArray ) do - self:Remove( RemoveUnitName.UnitName ) - end - - return self -end - - ---- Finds a Unit based on the Unit Name. --- @param #SET_UNIT self --- @param #string UnitName --- @return Unit#UNIT The found Unit. -function SET_UNIT:FindUnit( UnitName ) - - local UnitFound = self.Set[UnitName] - return UnitFound -end - - - ---- Builds a set of units of coalitions. --- Possible current coalitions are red, blue and neutral. --- @param #SET_UNIT self --- @param #string Coalitions Can take the following values: "red", "blue", "neutral". --- @return #SET_UNIT self -function SET_UNIT:FilterCoalitions( Coalitions ) - if not self.Filter.Coalitions then - self.Filter.Coalitions = {} - end - if type( Coalitions ) ~= "table" then - Coalitions = { Coalitions } - end - for CoalitionID, Coalition in pairs( Coalitions ) do - self.Filter.Coalitions[Coalition] = Coalition - end - return self -end - - ---- Builds a set of units out of categories. --- Possible current categories are plane, helicopter, ground, ship. --- @param #SET_UNIT self --- @param #string Categories Can take the following values: "plane", "helicopter", "ground", "ship". --- @return #SET_UNIT self -function SET_UNIT:FilterCategories( Categories ) - if not self.Filter.Categories then - self.Filter.Categories = {} - end - if type( Categories ) ~= "table" then - Categories = { Categories } - end - for CategoryID, Category in pairs( Categories ) do - self.Filter.Categories[Category] = Category - end - return self -end - - ---- Builds a set of units of defined unit types. --- Possible current types are those types known within DCS world. --- @param #SET_UNIT self --- @param #string Types Can take those type strings known within DCS world. --- @return #SET_UNIT self -function SET_UNIT:FilterTypes( Types ) - if not self.Filter.Types then - self.Filter.Types = {} - end - if type( Types ) ~= "table" then - Types = { Types } - end - for TypeID, Type in pairs( Types ) do - self.Filter.Types[Type] = Type - end - return self -end - - ---- Builds a set of units of defined countries. --- Possible current countries are those known within DCS world. --- @param #SET_UNIT self --- @param #string Countries Can take those country strings known within DCS world. --- @return #SET_UNIT self -function SET_UNIT:FilterCountries( Countries ) - if not self.Filter.Countries then - self.Filter.Countries = {} - end - if type( Countries ) ~= "table" then - Countries = { Countries } - end - for CountryID, Country in pairs( Countries ) do - self.Filter.Countries[Country] = Country - end - return self -end - - ---- Builds a set of units of defined unit prefixes. --- All the units starting with the given prefixes will be included within the set. --- @param #SET_UNIT self --- @param #string Prefixes The prefix of which the unit name starts with. --- @return #SET_UNIT self -function SET_UNIT:FilterPrefixes( Prefixes ) - if not self.Filter.UnitPrefixes then - self.Filter.UnitPrefixes = {} - end - if type( Prefixes ) ~= "table" then - Prefixes = { Prefixes } - end - for PrefixID, Prefix in pairs( Prefixes ) do - self.Filter.UnitPrefixes[Prefix] = Prefix - end - return self -end - ---- Builds a set of units having a radar of give types. --- All the units having a radar of a given type will be included within the set. --- @param #SET_UNIT self --- @param #table RadarTypes The radar types. --- @return #SET_UNIT self -function SET_UNIT:FilterHasRadar( RadarTypes ) - - self.Filter.RadarTypes = self.Filter.RadarTypes or {} - if type( RadarTypes ) ~= "table" then - RadarTypes = { RadarTypes } - end - for RadarTypeID, RadarType in pairs( RadarTypes ) do - self.Filter.RadarTypes[RadarType] = RadarType - end - return self -end - ---- Builds a set of SEADable units. --- @param #SET_UNIT self --- @return #SET_UNIT self -function SET_UNIT:FilterHasSEAD() - - self.Filter.SEAD = true - return self -end - - - ---- Starts the filtering. --- @param #SET_UNIT self --- @return #SET_UNIT self -function SET_UNIT:FilterStart() - - if _DATABASE then - self:_FilterStart() - end - - return self -end - ---- Handles the Database to check on an event (birth) that the Object was added in the Database. --- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! --- @param #SET_UNIT self --- @param Event#EVENTDATA Event --- @return #string The name of the UNIT --- @return #table The UNIT -function SET_UNIT:AddInDatabase( Event ) - self:F3( { Event } ) - - if not self.Database[Event.IniDCSUnitName] then - self.Database[Event.IniDCSUnitName] = UNIT:Register( Event.IniDCSUnitName ) - self:T3( self.Database[Event.IniDCSUnitName] ) - end - - return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] -end - ---- Handles the Database to check on any event that Object exists in the Database. --- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa! --- @param #SET_UNIT self --- @param Event#EVENTDATA Event --- @return #string The name of the UNIT --- @return #table The UNIT -function SET_UNIT:FindInDatabase( Event ) - self:E( { Event.IniDCSUnitName, self.Set[Event.IniDCSUnitName], Event } ) - - - return Event.IniDCSUnitName, self.Set[Event.IniDCSUnitName] -end - ---- Iterate the SET_UNIT and call an interator function for each **alive** UNIT, providing the UNIT and optional parameters. --- @param #SET_UNIT self --- @param #function IteratorFunction The function that will be called when there is an alive UNIT in the SET_UNIT. The function needs to accept a UNIT parameter. --- @return #SET_UNIT self -function SET_UNIT:ForEachUnit( IteratorFunction, ... ) - self:F2( arg ) - - self:ForEach( IteratorFunction, arg, self.Set ) - - return self -end - ---- Iterate the SET_UNIT and call an iterator function for each **alive** UNIT presence completely in a @{Zone}, providing the UNIT and optional parameters to the called function. --- @param #SET_UNIT self --- @param Zone#ZONE ZoneObject The Zone to be tested for. --- @param #function IteratorFunction The function that will be called when there is an alive UNIT in the SET_UNIT. The function needs to accept a UNIT parameter. --- @return #SET_UNIT self -function SET_UNIT:ForEachUnitCompletelyInZone( ZoneObject, IteratorFunction, ... ) - self:F2( arg ) - - self:ForEach( IteratorFunction, arg, self.Set, - --- @param Zone#ZONE_BASE ZoneObject - -- @param Unit#UNIT UnitObject - function( ZoneObject, UnitObject ) - if UnitObject:IsCompletelyInZone( ZoneObject ) then - return true - else - return false - end - end, { ZoneObject } ) - - return self -end - ---- Iterate the SET_UNIT and call an iterator function for each **alive** UNIT presence not in a @{Zone}, providing the UNIT and optional parameters to the called function. --- @param #SET_UNIT self --- @param Zone#ZONE ZoneObject The Zone to be tested for. --- @param #function IteratorFunction The function that will be called when there is an alive UNIT in the SET_UNIT. The function needs to accept a UNIT parameter. --- @return #SET_UNIT self -function SET_UNIT:ForEachUnitNotInZone( ZoneObject, IteratorFunction, ... ) - self:F2( arg ) - - self:ForEach( IteratorFunction, arg, self.Set, - --- @param Zone#ZONE_BASE ZoneObject - -- @param Unit#UNIT UnitObject - function( ZoneObject, UnitObject ) - if UnitObject:IsNotInZone( ZoneObject ) then - return true - else - return false - end - end, { ZoneObject } ) - - return self -end - ---- Returns map of unit types. --- @param #SET_UNIT self --- @return #map<#string,#number> A map of the unit types found. The key is the UnitTypeName and the value is the amount of unit types found. -function SET_UNIT:GetUnitTypes() - self:F2() - - local MT = {} -- Message Text - local UnitTypes = {} - - for UnitID, UnitData in pairs( self:GetSet() ) do - local TextUnit = UnitData -- Unit#UNIT - if TextUnit:IsAlive() then - local UnitType = TextUnit:GetTypeName() - - if not UnitTypes[UnitType] then - UnitTypes[UnitType] = 1 - else - UnitTypes[UnitType] = UnitTypes[UnitType] + 1 - end - end - end - - for UnitTypeID, UnitType in pairs( UnitTypes ) do - MT[#MT+1] = UnitType .. " of " .. UnitTypeID - end - - return UnitTypes -end - - ---- Returns a comma separated string of the unit types with a count in the @{Set}. --- @param #SET_UNIT self --- @return #string The unit types string -function SET_UNIT:GetUnitTypesText() - self:F2() - - local MT = {} -- Message Text - local UnitTypes = self:GetUnitTypes() - - for UnitTypeID, UnitType in pairs( UnitTypes ) do - MT[#MT+1] = UnitType .. " of " .. UnitTypeID - end - - return table.concat( MT, ", " ) -end - ---- Returns map of unit threat levels. --- @param #SET_UNIT self --- @return #table. -function SET_UNIT:GetUnitThreatLevels() - self:F2() - - local UnitThreatLevels = {} - - for UnitID, UnitData in pairs( self:GetSet() ) do - local ThreatUnit = UnitData -- Unit#UNIT - if ThreatUnit:IsAlive() then - local UnitThreatLevel, UnitThreatLevelText = ThreatUnit:GetThreatLevel() - local ThreatUnitName = ThreatUnit:GetName() - - UnitThreatLevels[UnitThreatLevel] = UnitThreatLevels[UnitThreatLevel] or {} - UnitThreatLevels[UnitThreatLevel].UnitThreatLevelText = UnitThreatLevelText - UnitThreatLevels[UnitThreatLevel].Units = UnitThreatLevels[UnitThreatLevel].Units or {} - UnitThreatLevels[UnitThreatLevel].Units[ThreatUnitName] = ThreatUnit - end - end - - return UnitThreatLevels -end - ---- Returns if the @{Set} has targets having a radar (of a given type). --- @param #SET_UNIT self --- @param DCSUnit#Unit.RadarType RadarType --- @return #number The amount of radars in the Set with the given type -function SET_UNIT:HasRadar( RadarType ) - self:F2( RadarType ) - - local RadarCount = 0 - for UnitID, UnitData in pairs( self:GetSet()) do - local UnitSensorTest = UnitData -- Unit#UNIT - local HasSensors - if RadarType then - HasSensors = UnitSensorTest:HasSensors( Unit.SensorType.RADAR, RadarType ) - else - HasSensors = UnitSensorTest:HasSensors( Unit.SensorType.RADAR ) - end - self:T3(HasSensors) - if HasSensors then - RadarCount = RadarCount + 1 - end - end - - return RadarCount -end - ---- Returns if the @{Set} has targets that can be SEADed. --- @param #SET_UNIT self --- @return #number The amount of SEADable units in the Set -function SET_UNIT:HasSEAD() - self:F2() - - local SEADCount = 0 - for UnitID, UnitData in pairs( self:GetSet()) do - local UnitSEAD = UnitData -- Unit#UNIT - if UnitSEAD:IsAlive() then - local UnitSEADAttributes = UnitSEAD:GetDesc().attributes - - local HasSEAD = UnitSEAD:HasSEAD() - - self:T3(HasSEAD) - if HasSEAD then - SEADCount = SEADCount + 1 - end - end - end - - return SEADCount -end - ---- Returns if the @{Set} has ground targets. --- @param #SET_UNIT self --- @return #number The amount of ground targets in the Set. -function SET_UNIT:HasGroundUnits() - self:F2() - - local GroundUnitCount = 0 - for UnitID, UnitData in pairs( self:GetSet()) do - local UnitTest = UnitData -- Unit#UNIT - if UnitTest:IsGround() then - GroundUnitCount = GroundUnitCount + 1 - end - end - - return GroundUnitCount -end - ---- Returns if the @{Set} has friendly ground units. --- @param #SET_UNIT self --- @return #number The amount of ground targets in the Set. -function SET_UNIT:HasFriendlyUnits( FriendlyCoalition ) - self:F2() - - local FriendlyUnitCount = 0 - for UnitID, UnitData in pairs( self:GetSet()) do - local UnitTest = UnitData -- Unit#UNIT - if UnitTest:IsFriendly( FriendlyCoalition ) then - FriendlyUnitCount = FriendlyUnitCount + 1 - end - end - - return FriendlyUnitCount -end - - - ------ Iterate the SET_UNIT and call an interator function for each **alive** player, providing the Unit of the player and optional parameters. ----- @param #SET_UNIT self ----- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_UNIT. The function needs to accept a UNIT parameter. ----- @return #SET_UNIT self ---function SET_UNIT:ForEachPlayer( IteratorFunction, ... ) --- self:F2( arg ) --- --- self:ForEach( IteratorFunction, arg, self.PlayersAlive ) --- --- return self ---end --- --- ------ Iterate the SET_UNIT and call an interator function for each client, providing the Client to the function and optional parameters. ----- @param #SET_UNIT self ----- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_UNIT. The function needs to accept a CLIENT parameter. ----- @return #SET_UNIT self ---function SET_UNIT:ForEachClient( IteratorFunction, ... ) --- self:F2( arg ) --- --- self:ForEach( IteratorFunction, arg, self.Clients ) --- --- return self ---end - - ---- --- @param #SET_UNIT self --- @param Unit#UNIT MUnit --- @return #SET_UNIT self -function SET_UNIT:IsIncludeObject( MUnit ) - self:F2( MUnit ) - local MUnitInclude = true - - if self.Filter.Coalitions then - local MUnitCoalition = false - for CoalitionID, CoalitionName in pairs( self.Filter.Coalitions ) do - self:T3( { "Coalition:", MUnit:GetCoalition(), self.FilterMeta.Coalitions[CoalitionName], CoalitionName } ) - if self.FilterMeta.Coalitions[CoalitionName] and self.FilterMeta.Coalitions[CoalitionName] == MUnit:GetCoalition() then - MUnitCoalition = true - end - end - MUnitInclude = MUnitInclude and MUnitCoalition - end - - if self.Filter.Categories then - local MUnitCategory = false - for CategoryID, CategoryName in pairs( self.Filter.Categories ) do - self:T3( { "Category:", MUnit:GetDesc().category, self.FilterMeta.Categories[CategoryName], CategoryName } ) - if self.FilterMeta.Categories[CategoryName] and self.FilterMeta.Categories[CategoryName] == MUnit:GetDesc().category then - MUnitCategory = true - end - end - MUnitInclude = MUnitInclude and MUnitCategory - end - - if self.Filter.Types then - local MUnitType = false - for TypeID, TypeName in pairs( self.Filter.Types ) do - self:T3( { "Type:", MUnit:GetTypeName(), TypeName } ) - if TypeName == MUnit:GetTypeName() then - MUnitType = true - end - end - MUnitInclude = MUnitInclude and MUnitType - end - - if self.Filter.Countries then - local MUnitCountry = false - for CountryID, CountryName in pairs( self.Filter.Countries ) do - self:T3( { "Country:", MUnit:GetCountry(), CountryName } ) - if country.id[CountryName] == MUnit:GetCountry() then - MUnitCountry = true - end - end - MUnitInclude = MUnitInclude and MUnitCountry - end - - if self.Filter.UnitPrefixes then - local MUnitPrefix = false - for UnitPrefixId, UnitPrefix in pairs( self.Filter.UnitPrefixes ) do - self:T3( { "Prefix:", string.find( MUnit:GetName(), UnitPrefix, 1 ), UnitPrefix } ) - if string.find( MUnit:GetName(), UnitPrefix, 1 ) then - MUnitPrefix = true - end - end - MUnitInclude = MUnitInclude and MUnitPrefix - end - - if self.Filter.RadarTypes then - local MUnitRadar = false - for RadarTypeID, RadarType in pairs( self.Filter.RadarTypes ) do - self:T3( { "Radar:", RadarType } ) - if MUnit:HasSensors( Unit.SensorType.RADAR, RadarType ) == true then - if MUnit:GetRadar() == true then -- This call is necessary to evaluate the SEAD capability. - self:T3( "RADAR Found" ) - end - MUnitRadar = true - end - end - MUnitInclude = MUnitInclude and MUnitRadar - end - - if self.Filter.SEAD then - local MUnitSEAD = false - if MUnit:HasSEAD() == true then - self:T3( "SEAD Found" ) - MUnitSEAD = true - end - MUnitInclude = MUnitInclude and MUnitSEAD - end - - self:T2( MUnitInclude ) - return MUnitInclude -end - - ---- SET_CLIENT - ---- SET_CLIENT class --- @type SET_CLIENT --- @extends Set#SET_BASE -SET_CLIENT = { - ClassName = "SET_CLIENT", - Clients = {}, - Filter = { - Coalitions = nil, - Categories = nil, - Types = nil, - Countries = nil, - ClientPrefixes = nil, - }, - FilterMeta = { - Coalitions = { - red = coalition.side.RED, - blue = coalition.side.BLUE, - neutral = coalition.side.NEUTRAL, - }, - Categories = { - plane = Unit.Category.AIRPLANE, - helicopter = Unit.Category.HELICOPTER, - ground = Unit.Category.GROUND_UNIT, - ship = Unit.Category.SHIP, - structure = Unit.Category.STRUCTURE, - }, - }, -} - - ---- Creates a new SET_CLIENT object, building a set of clients belonging to a coalitions, categories, countries, types or with defined prefix names. --- @param #SET_CLIENT self --- @return #SET_CLIENT --- @usage --- -- Define a new SET_CLIENT Object. This DBObject will contain a reference to all Clients. --- DBObject = SET_CLIENT:New() -function SET_CLIENT:New() - -- Inherits from BASE - local self = BASE:Inherit( self, SET_BASE:New( _DATABASE.CLIENTS ) ) - - return self -end - ---- Add CLIENT(s) to SET_CLIENT. --- @param Set#SET_CLIENT self --- @param #string AddClientNames A single name or an array of CLIENT names. --- @return self -function SET_CLIENT:AddClientsByName( AddClientNames ) - - local AddClientNamesArray = ( type( AddClientNames ) == "table" ) and AddClientNames or { AddClientNames } - - for AddClientID, AddClientName in pairs( AddClientNamesArray ) do - self:Add( AddClientName, CLIENT:FindByName( AddClientName ) ) - end - - return self -end - ---- Remove CLIENT(s) from SET_CLIENT. --- @param Set#SET_CLIENT self --- @param Client#CLIENT RemoveClientNames A single name or an array of CLIENT names. --- @return self -function SET_CLIENT:RemoveClientsByName( RemoveClientNames ) - - local RemoveClientNamesArray = ( type( RemoveClientNames ) == "table" ) and RemoveClientNames or { RemoveClientNames } - - for RemoveClientID, RemoveClientName in pairs( RemoveClientNamesArray ) do - self:Remove( RemoveClientName.ClientName ) - end - - return self -end - - ---- Finds a Client based on the Client Name. --- @param #SET_CLIENT self --- @param #string ClientName --- @return Client#CLIENT The found Client. -function SET_CLIENT:FindClient( ClientName ) - - local ClientFound = self.Set[ClientName] - return ClientFound -end - - - ---- Builds a set of clients of coalitions. --- Possible current coalitions are red, blue and neutral. --- @param #SET_CLIENT self --- @param #string Coalitions Can take the following values: "red", "blue", "neutral". --- @return #SET_CLIENT self -function SET_CLIENT:FilterCoalitions( Coalitions ) - if not self.Filter.Coalitions then - self.Filter.Coalitions = {} - end - if type( Coalitions ) ~= "table" then - Coalitions = { Coalitions } - end - for CoalitionID, Coalition in pairs( Coalitions ) do - self.Filter.Coalitions[Coalition] = Coalition - end - return self -end - - ---- Builds a set of clients out of categories. --- Possible current categories are plane, helicopter, ground, ship. --- @param #SET_CLIENT self --- @param #string Categories Can take the following values: "plane", "helicopter", "ground", "ship". --- @return #SET_CLIENT self -function SET_CLIENT:FilterCategories( Categories ) - if not self.Filter.Categories then - self.Filter.Categories = {} - end - if type( Categories ) ~= "table" then - Categories = { Categories } - end - for CategoryID, Category in pairs( Categories ) do - self.Filter.Categories[Category] = Category - end - return self -end - - ---- Builds a set of clients of defined client types. --- Possible current types are those types known within DCS world. --- @param #SET_CLIENT self --- @param #string Types Can take those type strings known within DCS world. --- @return #SET_CLIENT self -function SET_CLIENT:FilterTypes( Types ) - if not self.Filter.Types then - self.Filter.Types = {} - end - if type( Types ) ~= "table" then - Types = { Types } - end - for TypeID, Type in pairs( Types ) do - self.Filter.Types[Type] = Type - end - return self -end - - ---- Builds a set of clients of defined countries. --- Possible current countries are those known within DCS world. --- @param #SET_CLIENT self --- @param #string Countries Can take those country strings known within DCS world. --- @return #SET_CLIENT self -function SET_CLIENT:FilterCountries( Countries ) - if not self.Filter.Countries then - self.Filter.Countries = {} - end - if type( Countries ) ~= "table" then - Countries = { Countries } - end - for CountryID, Country in pairs( Countries ) do - self.Filter.Countries[Country] = Country - end - return self -end - - ---- Builds a set of clients of defined client prefixes. --- All the clients starting with the given prefixes will be included within the set. --- @param #SET_CLIENT self --- @param #string Prefixes The prefix of which the client name starts with. --- @return #SET_CLIENT self -function SET_CLIENT:FilterPrefixes( Prefixes ) - if not self.Filter.ClientPrefixes then - self.Filter.ClientPrefixes = {} - end - if type( Prefixes ) ~= "table" then - Prefixes = { Prefixes } - end - for PrefixID, Prefix in pairs( Prefixes ) do - self.Filter.ClientPrefixes[Prefix] = Prefix - end - return self -end - - - - ---- Starts the filtering. --- @param #SET_CLIENT self --- @return #SET_CLIENT self -function SET_CLIENT:FilterStart() - - if _DATABASE then - self:_FilterStart() - end - - return self -end - ---- Handles the Database to check on an event (birth) that the Object was added in the Database. --- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! --- @param #SET_CLIENT self --- @param Event#EVENTDATA Event --- @return #string The name of the CLIENT --- @return #table The CLIENT -function SET_CLIENT:AddInDatabase( Event ) - self:F3( { Event } ) - - return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] -end - ---- Handles the Database to check on any event that Object exists in the Database. --- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa! --- @param #SET_CLIENT self --- @param Event#EVENTDATA Event --- @return #string The name of the CLIENT --- @return #table The CLIENT -function SET_CLIENT:FindInDatabase( Event ) - self:F3( { Event } ) - - return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] -end - ---- Iterate the SET_CLIENT and call an interator function for each **alive** CLIENT, providing the CLIENT and optional parameters. --- @param #SET_CLIENT self --- @param #function IteratorFunction The function that will be called when there is an alive CLIENT in the SET_CLIENT. The function needs to accept a CLIENT parameter. --- @return #SET_CLIENT self -function SET_CLIENT:ForEachClient( IteratorFunction, ... ) - self:F2( arg ) - - self:ForEach( IteratorFunction, arg, self.Set ) - - return self -end - ---- Iterate the SET_CLIENT and call an iterator function for each **alive** CLIENT presence completely in a @{Zone}, providing the CLIENT and optional parameters to the called function. --- @param #SET_CLIENT self --- @param Zone#ZONE ZoneObject The Zone to be tested for. --- @param #function IteratorFunction The function that will be called when there is an alive CLIENT in the SET_CLIENT. The function needs to accept a CLIENT parameter. --- @return #SET_CLIENT self -function SET_CLIENT:ForEachClientInZone( ZoneObject, IteratorFunction, ... ) - self:F2( arg ) - - self:ForEach( IteratorFunction, arg, self.Set, - --- @param Zone#ZONE_BASE ZoneObject - -- @param Client#CLIENT ClientObject - function( ZoneObject, ClientObject ) - if ClientObject:IsInZone( ZoneObject ) then - return true - else - return false - end - end, { ZoneObject } ) - - return self -end - ---- Iterate the SET_CLIENT and call an iterator function for each **alive** CLIENT presence not in a @{Zone}, providing the CLIENT and optional parameters to the called function. --- @param #SET_CLIENT self --- @param Zone#ZONE ZoneObject The Zone to be tested for. --- @param #function IteratorFunction The function that will be called when there is an alive CLIENT in the SET_CLIENT. The function needs to accept a CLIENT parameter. --- @return #SET_CLIENT self -function SET_CLIENT:ForEachClientNotInZone( ZoneObject, IteratorFunction, ... ) - self:F2( arg ) - - self:ForEach( IteratorFunction, arg, self.Set, - --- @param Zone#ZONE_BASE ZoneObject - -- @param Client#CLIENT ClientObject - function( ZoneObject, ClientObject ) - if ClientObject:IsNotInZone( ZoneObject ) then - return true - else - return false - end - end, { ZoneObject } ) - - return self -end - ---- --- @param #SET_CLIENT self --- @param Client#CLIENT MClient --- @return #SET_CLIENT self -function SET_CLIENT:IsIncludeObject( MClient ) - self:F2( MClient ) - - local MClientInclude = true - - if MClient then - local MClientName = MClient.UnitName - - if self.Filter.Coalitions then - local MClientCoalition = false - for CoalitionID, CoalitionName in pairs( self.Filter.Coalitions ) do - local ClientCoalitionID = _DATABASE:GetCoalitionFromClientTemplate( MClientName ) - self:T3( { "Coalition:", ClientCoalitionID, self.FilterMeta.Coalitions[CoalitionName], CoalitionName } ) - if self.FilterMeta.Coalitions[CoalitionName] and self.FilterMeta.Coalitions[CoalitionName] == ClientCoalitionID then - MClientCoalition = true - end - end - self:T( { "Evaluated Coalition", MClientCoalition } ) - MClientInclude = MClientInclude and MClientCoalition - end - - if self.Filter.Categories then - local MClientCategory = false - for CategoryID, CategoryName in pairs( self.Filter.Categories ) do - local ClientCategoryID = _DATABASE:GetCategoryFromClientTemplate( MClientName ) - self:T3( { "Category:", ClientCategoryID, self.FilterMeta.Categories[CategoryName], CategoryName } ) - if self.FilterMeta.Categories[CategoryName] and self.FilterMeta.Categories[CategoryName] == ClientCategoryID then - MClientCategory = true - end - end - self:T( { "Evaluated Category", MClientCategory } ) - MClientInclude = MClientInclude and MClientCategory - end - - if self.Filter.Types then - local MClientType = false - for TypeID, TypeName in pairs( self.Filter.Types ) do - self:T3( { "Type:", MClient:GetTypeName(), TypeName } ) - if TypeName == MClient:GetTypeName() then - MClientType = true - end - end - self:T( { "Evaluated Type", MClientType } ) - MClientInclude = MClientInclude and MClientType - end - - if self.Filter.Countries then - local MClientCountry = false - for CountryID, CountryName in pairs( self.Filter.Countries ) do - local ClientCountryID = _DATABASE:GetCountryFromClientTemplate(MClientName) - self:T3( { "Country:", ClientCountryID, country.id[CountryName], CountryName } ) - if country.id[CountryName] and country.id[CountryName] == ClientCountryID then - MClientCountry = true - end - end - self:T( { "Evaluated Country", MClientCountry } ) - MClientInclude = MClientInclude and MClientCountry - end - - if self.Filter.ClientPrefixes then - local MClientPrefix = false - for ClientPrefixId, ClientPrefix in pairs( self.Filter.ClientPrefixes ) do - self:T3( { "Prefix:", string.find( MClient.UnitName, ClientPrefix, 1 ), ClientPrefix } ) - if string.find( MClient.UnitName, ClientPrefix, 1 ) then - MClientPrefix = true - end - end - self:T( { "Evaluated Prefix", MClientPrefix } ) - MClientInclude = MClientInclude and MClientPrefix - end - end - - self:T2( MClientInclude ) - return MClientInclude -end - ---- SET_AIRBASE - ---- SET_AIRBASE class --- @type SET_AIRBASE --- @extends Set#SET_BASE -SET_AIRBASE = { - ClassName = "SET_AIRBASE", - Airbases = {}, - Filter = { - Coalitions = nil, - }, - FilterMeta = { - Coalitions = { - red = coalition.side.RED, - blue = coalition.side.BLUE, - neutral = coalition.side.NEUTRAL, - }, - Categories = { - airdrome = Airbase.Category.AIRDROME, - helipad = Airbase.Category.HELIPAD, - ship = Airbase.Category.SHIP, - }, - }, -} - - ---- Creates a new SET_AIRBASE object, building a set of airbases belonging to a coalitions and categories. --- @param #SET_AIRBASE self --- @return #SET_AIRBASE self --- @usage --- -- Define a new SET_AIRBASE Object. The DatabaseSet will contain a reference to all Airbases. --- DatabaseSet = SET_AIRBASE:New() -function SET_AIRBASE:New() - -- Inherits from BASE - local self = BASE:Inherit( self, SET_BASE:New( _DATABASE.AIRBASES ) ) - - return self -end - ---- Add AIRBASEs to SET_AIRBASE. --- @param Set#SET_AIRBASE self --- @param #string AddAirbaseNames A single name or an array of AIRBASE names. --- @return self -function SET_AIRBASE:AddAirbasesByName( AddAirbaseNames ) - - local AddAirbaseNamesArray = ( type( AddAirbaseNames ) == "table" ) and AddAirbaseNames or { AddAirbaseNames } - - for AddAirbaseID, AddAirbaseName in pairs( AddAirbaseNamesArray ) do - self:Add( AddAirbaseName, AIRBASE:FindByName( AddAirbaseName ) ) - end - - return self -end - ---- Remove AIRBASEs from SET_AIRBASE. --- @param Set#SET_AIRBASE self --- @param Airbase#AIRBASE RemoveAirbaseNames A single name or an array of AIRBASE names. --- @return self -function SET_AIRBASE:RemoveAirbasesByName( RemoveAirbaseNames ) - - local RemoveAirbaseNamesArray = ( type( RemoveAirbaseNames ) == "table" ) and RemoveAirbaseNames or { RemoveAirbaseNames } - - for RemoveAirbaseID, RemoveAirbaseName in pairs( RemoveAirbaseNamesArray ) do - self:Remove( RemoveAirbaseName.AirbaseName ) - end - - return self -end - - ---- Finds a Airbase based on the Airbase Name. --- @param #SET_AIRBASE self --- @param #string AirbaseName --- @return Airbase#AIRBASE The found Airbase. -function SET_AIRBASE:FindAirbase( AirbaseName ) - - local AirbaseFound = self.Set[AirbaseName] - return AirbaseFound -end - - - ---- Builds a set of airbases of coalitions. --- Possible current coalitions are red, blue and neutral. --- @param #SET_AIRBASE self --- @param #string Coalitions Can take the following values: "red", "blue", "neutral". --- @return #SET_AIRBASE self -function SET_AIRBASE:FilterCoalitions( Coalitions ) - if not self.Filter.Coalitions then - self.Filter.Coalitions = {} - end - if type( Coalitions ) ~= "table" then - Coalitions = { Coalitions } - end - for CoalitionID, Coalition in pairs( Coalitions ) do - self.Filter.Coalitions[Coalition] = Coalition - end - return self -end - - ---- Builds a set of airbases out of categories. --- Possible current categories are plane, helicopter, ground, ship. --- @param #SET_AIRBASE self --- @param #string Categories Can take the following values: "airdrome", "helipad", "ship". --- @return #SET_AIRBASE self -function SET_AIRBASE:FilterCategories( Categories ) - if not self.Filter.Categories then - self.Filter.Categories = {} - end - if type( Categories ) ~= "table" then - Categories = { Categories } - end - for CategoryID, Category in pairs( Categories ) do - self.Filter.Categories[Category] = Category - end - return self -end - ---- Starts the filtering. --- @param #SET_AIRBASE self --- @return #SET_AIRBASE self -function SET_AIRBASE:FilterStart() - - if _DATABASE then - self:_FilterStart() - end - - return self -end - - ---- Handles the Database to check on an event (birth) that the Object was added in the Database. --- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! --- @param #SET_AIRBASE self --- @param Event#EVENTDATA Event --- @return #string The name of the AIRBASE --- @return #table The AIRBASE -function SET_AIRBASE:AddInDatabase( Event ) - self:F3( { Event } ) - - return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] -end - ---- Handles the Database to check on any event that Object exists in the Database. --- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa! --- @param #SET_AIRBASE self --- @param Event#EVENTDATA Event --- @return #string The name of the AIRBASE --- @return #table The AIRBASE -function SET_AIRBASE:FindInDatabase( Event ) - self:F3( { Event } ) - - return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] -end - ---- Iterate the SET_AIRBASE and call an interator function for each AIRBASE, providing the AIRBASE and optional parameters. --- @param #SET_AIRBASE self --- @param #function IteratorFunction The function that will be called when there is an alive AIRBASE in the SET_AIRBASE. The function needs to accept a AIRBASE parameter. --- @return #SET_AIRBASE self -function SET_AIRBASE:ForEachAirbase( IteratorFunction, ... ) - self:F2( arg ) - - self:ForEach( IteratorFunction, arg, self.Set ) - - return self -end - ---- Iterate the SET_AIRBASE while identifying the nearest @{Airbase#AIRBASE} from a @{Point#POINT_VEC2}. --- @param #SET_AIRBASE self --- @param Point#POINT_VEC2 PointVec2 A @{Point#POINT_VEC2} object from where to evaluate the closest @{Airbase#AIRBASE}. --- @return Airbase#AIRBASE The closest @{Airbase#AIRBASE}. -function SET_AIRBASE:FindNearestAirbaseFromPointVec2( PointVec2 ) - self:F2( PointVec2 ) - - local NearestAirbase = self:FindNearestObjectFromPointVec2( PointVec2 ) - return NearestAirbase -end - - - ---- --- @param #SET_AIRBASE self --- @param Airbase#AIRBASE MAirbase --- @return #SET_AIRBASE self -function SET_AIRBASE:IsIncludeObject( MAirbase ) - self:F2( MAirbase ) - - local MAirbaseInclude = true - - if MAirbase then - local MAirbaseName = MAirbase:GetName() - - if self.Filter.Coalitions then - local MAirbaseCoalition = false - for CoalitionID, CoalitionName in pairs( self.Filter.Coalitions ) do - local AirbaseCoalitionID = _DATABASE:GetCoalitionFromAirbase( MAirbaseName ) - self:T3( { "Coalition:", AirbaseCoalitionID, self.FilterMeta.Coalitions[CoalitionName], CoalitionName } ) - if self.FilterMeta.Coalitions[CoalitionName] and self.FilterMeta.Coalitions[CoalitionName] == AirbaseCoalitionID then - MAirbaseCoalition = true - end - end - self:T( { "Evaluated Coalition", MAirbaseCoalition } ) - MAirbaseInclude = MAirbaseInclude and MAirbaseCoalition - end - - if self.Filter.Categories then - local MAirbaseCategory = false - for CategoryID, CategoryName in pairs( self.Filter.Categories ) do - local AirbaseCategoryID = _DATABASE:GetCategoryFromAirbase( MAirbaseName ) - self:T3( { "Category:", AirbaseCategoryID, self.FilterMeta.Categories[CategoryName], CategoryName } ) - if self.FilterMeta.Categories[CategoryName] and self.FilterMeta.Categories[CategoryName] == AirbaseCategoryID then - MAirbaseCategory = true - end - end - self:T( { "Evaluated Category", MAirbaseCategory } ) - MAirbaseInclude = MAirbaseInclude and MAirbaseCategory - end - end - - self:T2( MAirbaseInclude ) - return MAirbaseInclude -end ---- This module contains the POINT classes. --- --- 1) @{Point#POINT_VEC3} class, extends @{Base#BASE} --- =============================================== --- The @{Point#POINT_VEC3} class defines a 3D point in the simulator. --- --- **Important Note:** Most of the functions in this section were taken from MIST, and reworked to OO concepts. --- In order to keep the credibility of the the author, I want to emphasize that the of the MIST framework was created by Grimes, who you can find on the Eagle Dynamics Forums. --- --- 1.1) POINT_VEC3 constructor --- --------------------------- --- --- A new POINT instance can be created with: --- --- * @{#POINT_VEC3.New}(): a 3D point. --- --- 2) @{Point#POINT_VEC2} class, extends @{Point#POINT_VEC3} --- ========================================================= --- The @{Point#POINT_VEC2} class defines a 2D point in the simulator. The height coordinate (if needed) will be the land height + an optional added height specified. --- --- 2.1) POINT_VEC2 constructor --- --------------------------- --- --- A new POINT instance can be created with: --- --- * @{#POINT_VEC2.New}(): a 2D point. --- --- @module Point --- @author FlightControl - ---- The POINT_VEC3 class --- @type POINT_VEC3 --- @extends Base#BASE --- @field DCSTypes#Vec3 PointVec3 --- @field #POINT_VEC3.SmokeColor SmokeColor --- @field #POINT_VEC3.FlareColor FlareColor --- @field #POINT_VEC3.RoutePointAltType RoutePointAltType --- @field #POINT_VEC3.RoutePointType RoutePointType --- @field #POINT_VEC3.RoutePointAction RoutePointAction -POINT_VEC3 = { - ClassName = "POINT_VEC3", - SmokeColor = { - Green = trigger.smokeColor.Green, - Red = trigger.smokeColor.Red, - White = trigger.smokeColor.White, - Orange = trigger.smokeColor.Orange, - Blue = trigger.smokeColor.Blue - }, - FlareColor = { - Green = trigger.flareColor.Green, - Red = trigger.flareColor.Red, - White = trigger.flareColor.White, - Yellow = trigger.flareColor.Yellow - }, - Metric = true, - RoutePointAltType = { - BARO = "BARO", - }, - RoutePointType = { - TurningPoint = "Turning Point", - }, - RoutePointAction = { - TurningPoint = "Turning Point", - }, -} - - ---- SmokeColor --- @type POINT_VEC3.SmokeColor --- @field Green --- @field Red --- @field White --- @field Orange --- @field Blue - - - ---- FlareColor --- @type POINT_VEC3.FlareColor --- @field Green --- @field Red --- @field White --- @field Yellow - - - ---- RoutePoint AltTypes --- @type POINT_VEC3.RoutePointAltType --- @field BARO "BARO" - - - ---- RoutePoint Types --- @type POINT_VEC3.RoutePointType --- @field TurningPoint "Turning Point" - - - ---- RoutePoint Actions --- @type POINT_VEC3.RoutePointAction --- @field TurningPoint "Turning Point" - - - --- Constructor. - ---- Create a new POINT_VEC3 object. --- @param #POINT_VEC3 self --- @param DCSTypes#Distance x The x coordinate of the Vec3 point, pointing to the North. --- @param DCSTypes#Distance y The y coordinate of the Vec3 point, pointing Upwards. --- @param DCSTypes#Distance z The z coordinate of the Vec3 point, pointing to the Right. --- @return Point#POINT_VEC3 self -function POINT_VEC3:New( x, y, z ) - - local self = BASE:Inherit( self, BASE:New() ) - self.PointVec3 = { x = x, y = y, z = z } - self:F2( self.PointVec3 ) - return self -end - ---- Create a new POINT_VEC3 object from Vec3 coordinates. --- @param #POINT_VEC3 self --- @param DCSTypes#Vec3 Vec3 The Vec3 point. --- @return Point#POINT_VEC3 self -function POINT_VEC3:NewFromVec3( Vec3 ) - - local self = BASE:Inherit( self, BASE:New() ) - self.PointVec3 = Vec3 - self:F2( self.PointVec3 ) - return self -end - - ---- Return the coordinates of the POINT_VEC3 in Vec3 format. --- @param #POINT_VEC3 self --- @return DCSTypes#Vec3 The Vec3 coodinate. -function POINT_VEC3:GetVec3() - return self.PointVec3 -end - - ---- Return the x coordinate of the POINT_VEC3. --- @param #POINT_VEC3 self --- @return #number The x coodinate. -function POINT_VEC3:GetX() - return self.PointVec3.x -end - ---- Return the y coordinate of the POINT_VEC3. --- @param #POINT_VEC3 self --- @return #number The y coodinate. -function POINT_VEC3:GetY() - return self.PointVec3.y -end - ---- Return the z coordinate of the POINT_VEC3. --- @param #POINT_VEC3 self --- @return #number The z coodinate. -function POINT_VEC3:GetZ() - return self.PointVec3.z -end - - ---- Return a direction vector Vec3 from POINT_VEC3 to the POINT_VEC3. --- @param #POINT_VEC3 self --- @param #POINT_VEC3 TargetPointVec3 The target PointVec3. --- @return DCSTypes#Vec3 DirectionVec3 The direction vector in Vec3 format. -function POINT_VEC3:GetDirectionVec3( TargetPointVec3 ) - return { x = TargetPointVec3:GetX() - self:GetX(), y = TargetPointVec3:GetY() - self:GetY(), z = TargetPointVec3:GetZ() - self:GetZ() } -end - ---- Get a correction in radians of the real magnetic north of the POINT_VEC3. --- @param #POINT_VEC3 self --- @return #number CorrectionRadians The correction in radians. -function POINT_VEC3:GetNorthCorrectionRadians() - local TargetVec3 = self:GetVec3() - local lat, lon = coord.LOtoLL(TargetVec3) - local north_posit = coord.LLtoLO(lat + 1, lon) - return math.atan2( north_posit.z - TargetVec3.z, north_posit.x - TargetVec3.x ) -end - - ---- Return a direction in radians from the POINT_VEC3 using a direction vector in Vec3 format. --- @param #POINT_VEC3 self --- @param DCSTypes#Vec3 DirectionVec3 The direction vector in Vec3 format. --- @return #number DirectionRadians The direction in radians. -function POINT_VEC3:GetDirectionRadians( DirectionVec3 ) - local DirectionRadians = math.atan2( DirectionVec3.z, DirectionVec3.x ) - --DirectionRadians = DirectionRadians + self:GetNorthCorrectionRadians() - if DirectionRadians < 0 then - DirectionRadians = DirectionRadians + 2 * math.pi -- put dir in range of 0 to 2*pi ( the full circle ) - end - return DirectionRadians -end - ---- Return the 2D distance in meters between the target POINT_VEC3 and the POINT_VEC3. --- @param #POINT_VEC3 self --- @param #POINT_VEC3 TargetPointVec3 The target PointVec3. --- @return DCSTypes#Distance Distance The distance in meters. -function POINT_VEC3:Get2DDistance( TargetPointVec3 ) - local TargetVec3 = TargetPointVec3:GetVec3() - local SourceVec3 = self:GetVec3() - return ( ( TargetVec3.x - SourceVec3.x ) ^ 2 + ( TargetVec3.z - SourceVec3.z ) ^ 2 ) ^ 0.5 -end - ---- Return the 3D distance in meters between the target POINT_VEC3 and the POINT_VEC3. --- @param #POINT_VEC3 self --- @param #POINT_VEC3 TargetPointVec3 The target PointVec3. --- @return DCSTypes#Distance Distance The distance in meters. -function POINT_VEC3:Get3DDistance( TargetPointVec3 ) - local TargetVec3 = TargetPointVec3:GetVec3() - local SourceVec3 = self:GetVec3() - return ( ( TargetVec3.x - SourceVec3.x ) ^ 2 + ( TargetVec3.y - SourceVec3.y ) ^ 2 + ( TargetVec3.z - SourceVec3.z ) ^ 2 ) ^ 0.5 -end - ---- Provides a Bearing / Range string --- @param #POINT_VEC3 self --- @param #number AngleRadians The angle in randians --- @param #number Distance The distance --- @return #string The BR Text -function POINT_VEC3:ToStringBR( AngleRadians, Distance ) - - AngleRadians = UTILS.Round( UTILS.ToDegree( AngleRadians ), 0 ) - if self:IsMetric() then - Distance = UTILS.Round( Distance / 1000, 2 ) - else - Distance = UTILS.Round( UTILS.MetersToNM( Distance ), 2 ) - end - - local s = string.format( '%03d', AngleRadians ) .. ' for ' .. Distance - - s = s .. self:GetAltitudeText() -- When the POINT is a VEC2, there will be no altitude shown. - - return s -end - ---- Provides a Bearing / Range string --- @param #POINT_VEC3 self --- @param #number AngleRadians The angle in randians --- @param #number Distance The distance --- @return #string The BR Text -function POINT_VEC3:ToStringLL( acc, DMS ) - - acc = acc or 3 - local lat, lon = coord.LOtoLL( self.PointVec3 ) - return UTILS.tostringLL(lat, lon, acc, DMS) -end - ---- Return the altitude text of the POINT_VEC3. --- @param #POINT_VEC3 self --- @return #string Altitude text. -function POINT_VEC3:GetAltitudeText() - if self:IsMetric() then - return ' at ' .. UTILS.Round( self:GetY(), 0 ) - else - return ' at ' .. UTILS.Round( UTILS.MetersToFeet( self:GetY() ), 0 ) - end -end - ---- Return a BR string from a POINT_VEC3 to the POINT_VEC3. --- @param #POINT_VEC3 self --- @param #POINT_VEC3 TargetPointVec3 The target PointVec3. --- @return #string The BR text. -function POINT_VEC3:GetBRText( TargetPointVec3 ) - local DirectionVec3 = self:GetDirectionVec3( TargetPointVec3 ) - local AngleRadians = self:GetDirectionRadians( DirectionVec3 ) - local Distance = self:Get2DDistance( TargetPointVec3 ) - return self:ToStringBR( AngleRadians, Distance ) -end - ---- Sets the POINT_VEC3 metric or NM. --- @param #POINT_VEC3 self --- @param #boolean Metric true means metric, false means NM. -function POINT_VEC3:SetMetric( Metric ) - self.Metric = Metric -end - ---- Gets if the POINT_VEC3 is metric or NM. --- @param #POINT_VEC3 self --- @return #boolean Metric true means metric, false means NM. -function POINT_VEC3:IsMetric() - return self.Metric -end - - - - ---- Build an air type route point. --- @param #POINT_VEC3 self --- @param #POINT_VEC3.RoutePointAltType AltType The altitude type. --- @param #POINT_VEC3.RoutePointType Type The route point type. --- @param #POINT_VEC3.RoutePointAction Action The route point action. --- @param DCSTypes#Speed Speed Airspeed in km/h. --- @param #boolean SpeedLocked true means the speed is locked. --- @return #table The route point. -function POINT_VEC3:RoutePointAir( AltType, Type, Action, Speed, SpeedLocked ) - self:F2( { AltType, Type, Action, Speed, SpeedLocked } ) - - local RoutePoint = {} - RoutePoint.x = self.PointVec3.x - RoutePoint.y = self.PointVec3.z - RoutePoint.alt = self.PointVec3.y - RoutePoint.alt_type = AltType - - RoutePoint.type = Type - RoutePoint.action = Action - - RoutePoint.speed = Speed / 3.6 - RoutePoint.speed_locked = true - --- ["task"] = --- { --- ["id"] = "ComboTask", --- ["params"] = --- { --- ["tasks"] = --- { --- }, -- end of ["tasks"] --- }, -- end of ["params"] --- }, -- end of ["task"] - - - RoutePoint.task = {} - RoutePoint.task.id = "ComboTask" - RoutePoint.task.params = {} - RoutePoint.task.params.tasks = {} - - - return RoutePoint -end - - ---- Smokes the point in a color. --- @param #POINT_VEC3 self --- @param Point#POINT_VEC3.SmokeColor SmokeColor -function POINT_VEC3:Smoke( SmokeColor ) - self:F2( { SmokeColor, self.PointVec3 } ) - trigger.action.smoke( self.PointVec3, SmokeColor ) -end - ---- Smoke the POINT_VEC3 Green. --- @param #POINT_VEC3 self -function POINT_VEC3:SmokeGreen() - self:F2() - self:Smoke( POINT_VEC3.SmokeColor.Green ) -end - ---- Smoke the POINT_VEC3 Red. --- @param #POINT_VEC3 self -function POINT_VEC3:SmokeRed() - self:F2() - self:Smoke( POINT_VEC3.SmokeColor.Red ) -end - ---- Smoke the POINT_VEC3 White. --- @param #POINT_VEC3 self -function POINT_VEC3:SmokeWhite() - self:F2() - self:Smoke( POINT_VEC3.SmokeColor.White ) -end - ---- Smoke the POINT_VEC3 Orange. --- @param #POINT_VEC3 self -function POINT_VEC3:SmokeOrange() - self:F2() - self:Smoke( POINT_VEC3.SmokeColor.Orange ) -end - ---- Smoke the POINT_VEC3 Blue. --- @param #POINT_VEC3 self -function POINT_VEC3:SmokeBlue() - self:F2() - self:Smoke( POINT_VEC3.SmokeColor.Blue ) -end - ---- Flares the point in a color. --- @param #POINT_VEC3 self --- @param Point#POINT_VEC3.FlareColor --- @param DCSTypes#Azimuth (optional) Azimuth The azimuth of the flare direction. The default azimuth is 0. -function POINT_VEC3:Flare( FlareColor, Azimuth ) - self:F2( { FlareColor, self.PointVec3 } ) - trigger.action.signalFlare( self.PointVec3, FlareColor, Azimuth and Azimuth or 0 ) -end - ---- Flare the POINT_VEC3 White. --- @param #POINT_VEC3 self --- @param DCSTypes#Azimuth (optional) Azimuth The azimuth of the flare direction. The default azimuth is 0. -function POINT_VEC3:FlareWhite( Azimuth ) - self:F2( Azimuth ) - self:Flare( POINT_VEC3.FlareColor.White, Azimuth ) -end - ---- Flare the POINT_VEC3 Yellow. --- @param #POINT_VEC3 self --- @param DCSTypes#Azimuth (optional) Azimuth The azimuth of the flare direction. The default azimuth is 0. -function POINT_VEC3:FlareYellow( Azimuth ) - self:F2( Azimuth ) - self:Flare( POINT_VEC3.FlareColor.Yellow, Azimuth ) -end - ---- Flare the POINT_VEC3 Green. --- @param #POINT_VEC3 self --- @param DCSTypes#Azimuth (optional) Azimuth The azimuth of the flare direction. The default azimuth is 0. -function POINT_VEC3:FlareGreen( Azimuth ) - self:F2( Azimuth ) - self:Flare( POINT_VEC3.FlareColor.Green, Azimuth ) -end - ---- Flare the POINT_VEC3 Red. --- @param #POINT_VEC3 self -function POINT_VEC3:FlareRed( Azimuth ) - self:F2( Azimuth ) - self:Flare( POINT_VEC3.FlareColor.Red, Azimuth ) -end - - ---- The POINT_VEC2 class --- @type POINT_VEC2 --- @field DCSTypes#Vec2 PointVec2 --- @extends Point#POINT_VEC3 -POINT_VEC2 = { - ClassName = "POINT_VEC2", - } - ---- Create a new POINT_VEC2 object. --- @param #POINT_VEC2 self --- @param DCSTypes#Distance x The x coordinate of the Vec3 point, pointing to the North. --- @param DCSTypes#Distance y The y coordinate of the Vec3 point, pointing to the Right. --- @param DCSTypes#Distance LandHeightAdd (optional) The default height if required to be evaluated will be the land height of the x, y coordinate. You can specify an extra height to be added to the land height. --- @return Point#POINT_VEC2 -function POINT_VEC2:New( x, y, LandHeightAdd ) - - local LandHeight = land.getHeight( { ["x"] = x, ["y"] = y } ) - if LandHeightAdd then - LandHeight = LandHeight + LandHeightAdd - end - - local self = BASE:Inherit( self, POINT_VEC3:New( x, LandHeight, y ) ) - self:F2( { x, y, LandHeightAdd } ) - - self.PointVec2 = { x = x, y = y } - - return self -end - ---- Calculate the distance from a reference @{Point#POINT_VEC2}. --- @param #POINT_VEC2 self --- @param #POINT_VEC2 PointVec2Reference The reference @{Point#POINT_VEC2}. --- @return DCSTypes#Distance The distance from the reference @{Point#POINT_VEC2} in meters. -function POINT_VEC2:DistanceFromPointVec2( PointVec2Reference ) - self:F2( PointVec2Reference ) - - local Distance = ( ( PointVec2Reference.PointVec2.x - self.PointVec2.x ) ^ 2 + ( PointVec2Reference.PointVec2.y - self.PointVec2.y ) ^2 ) ^0.5 - - self:T2( Distance ) - return Distance -end - ---- Calculate the distance from a reference @{DCSTypes#Vec2}. --- @param #POINT_VEC2 self --- @param DCSTypes#Vec2 Vec2Reference The reference @{DCSTypes#Vec2}. --- @return DCSTypes#Distance The distance from the reference @{DCSTypes#Vec2} in meters. -function POINT_VEC2:DistanceFromVec2( Vec2Reference ) - self:F2( Vec2Reference ) - - local Distance = ( ( Vec2Reference.x - self.PointVec2.x ) ^ 2 + ( Vec2Reference.y - self.PointVec2.y ) ^2 ) ^0.5 - - self:T2( Distance ) - return Distance -end - - ---- Return no text for the altitude of the POINT_VEC2. --- @param #POINT_VEC2 self --- @return #string Empty string. -function POINT_VEC2:GetAltitudeText() - return '' -end - ---- The main include file for the MOOSE system. - -Include.File( "Routines" ) -Include.File( "Utils" ) -Include.File( "Base" ) -Include.File( "Object" ) -Include.File( "Identifiable" ) -Include.File( "Positionable" ) -Include.File( "Controllable" ) -Include.File( "Scheduler" ) -Include.File( "Event" ) -Include.File( "Menu" ) -Include.File( "Group" ) -Include.File( "Unit" ) -Include.File( "Zone" ) -Include.File( "Client" ) -Include.File( "Static" ) -Include.File( "Airbase" ) -Include.File( "Database" ) -Include.File( "Set" ) -Include.File( "Point" ) -Include.File( "Moose" ) -Include.File( "Scoring" ) -Include.File( "Cargo" ) -Include.File( "Message" ) -Include.File( "Stage" ) -Include.File( "Task" ) -Include.File( "GoHomeTask" ) -Include.File( "DestroyBaseTask" ) -Include.File( "DestroyGroupsTask" ) -Include.File( "DestroyRadarsTask" ) -Include.File( "DestroyUnitTypesTask" ) -Include.File( "PickupTask" ) -Include.File( "DeployTask" ) -Include.File( "NoTask" ) -Include.File( "RouteTask" ) -Include.File( "Mission" ) -Include.File( "CleanUp" ) -Include.File( "Spawn" ) -Include.File( "Movement" ) -Include.File( "Sead" ) -Include.File( "Escort" ) -Include.File( "MissileTrainer" ) -Include.File( "PatrolZone" ) -Include.File( "AIBalancer" ) -Include.File( "AirbasePolice" ) - -Include.File( "Detection" ) -Include.File( "DetectionManager" ) - -Include.File( "StateMachine" ) - -Include.File( "Process" ) -Include.File( "Process_Assign" ) -Include.File( "Process_Route" ) -Include.File( "Process_Smoke" ) -Include.File( "Process_Destroy" ) -Include.File( "Process_JTAC" ) - -Include.File( "Task" ) -Include.File( "Task_SEAD" ) -Include.File( "Task_A2G" ) - --- The order of the declarations is important here. Don't touch it. - ---- Declare the event dispatcher based on the EVENT class -_EVENTDISPATCHER = EVENT:New() -- Event#EVENT - ---- Declare the main database object, which is used internally by the MOOSE classes. -_DATABASE = DATABASE:New() -- Database#DATABASE - ---- Scoring system for MOOSE. --- This scoring class calculates the hits and kills that players make within a simulation session. --- Scoring is calculated using a defined algorithm. --- With a small change in MissionScripting.lua, the scoring can also be logged in a CSV file, that can then be uploaded --- to a database or a BI tool to publish the scoring results to the player community. --- @module Scoring --- @author FlightControl - - ---- The Scoring class --- @type SCORING --- @field Players A collection of the current players that have joined the game. --- @extends Base#BASE -SCORING = { - ClassName = "SCORING", - ClassID = 0, - Players = {}, -} - -local _SCORINGCoalition = - { - [1] = "Red", - [2] = "Blue", - } - -local _SCORINGCategory = - { - [Unit.Category.AIRPLANE] = "Plane", - [Unit.Category.HELICOPTER] = "Helicopter", - [Unit.Category.GROUND_UNIT] = "Vehicle", - [Unit.Category.SHIP] = "Ship", - [Unit.Category.STRUCTURE] = "Structure", - } - ---- Creates a new SCORING object to administer the scoring achieved by players. --- @param #SCORING self --- @param #string GameName The name of the game. This name is also logged in the CSV score file. --- @return #SCORING self --- @usage --- -- Define a new scoring object for the mission Gori Valley. --- ScoringObject = SCORING:New( "Gori Valley" ) -function SCORING:New( GameName ) - - -- Inherits from BASE - local self = BASE:Inherit( self, BASE:New() ) - - if GameName then - self.GameName = GameName - else - error( "A game name must be given to register the scoring results" ) - end - - - _EVENTDISPATCHER:OnDead( self._EventOnDeadOrCrash, self ) - _EVENTDISPATCHER:OnCrash( self._EventOnDeadOrCrash, self ) - _EVENTDISPATCHER:OnHit( self._EventOnHit, self ) - - --self.SchedulerId = routines.scheduleFunction( SCORING._FollowPlayersScheduled, { self }, 0, 5 ) - self.SchedulerId = SCHEDULER:New( self, self._FollowPlayersScheduled, {}, 0, 5 ) - - self:ScoreMenu() - - self:OpenCSV( GameName) - - return self - -end - ---- Creates a score radio menu. Can be accessed using Radio -> F10. --- @param #SCORING self --- @return #SCORING self -function SCORING:ScoreMenu() - self.Menu = SUBMENU:New( 'Scoring' ) - self.AllScoresMenu = COMMANDMENU:New( 'Score All Active Players', self.Menu, SCORING.ReportScoreAll, self ) - --- = COMMANDMENU:New('Your Current Score', ReportScore, SCORING.ReportScorePlayer, self ) - return self -end - ---- Follows new players entering Clients within the DCSRTE. --- TODO: Need to see if i can catch this also with an event. It will eliminate the schedule ... -function SCORING:_FollowPlayersScheduled() - self:F3( "_FollowPlayersScheduled" ) - - local ClientUnit = 0 - local CoalitionsData = { AlivePlayersRed = coalition.getPlayers(coalition.side.RED), AlivePlayersBlue = coalition.getPlayers(coalition.side.BLUE) } - local unitId - local unitData - local AlivePlayerUnits = {} - - for CoalitionId, CoalitionData in pairs( CoalitionsData ) do - self:T3( { "_FollowPlayersScheduled", CoalitionData } ) - for UnitId, UnitData in pairs( CoalitionData ) do - self:_AddPlayerFromUnit( UnitData ) - end - end - - return true -end - - ---- Track DEAD or CRASH events for the scoring. --- @param #SCORING self --- @param Event#EVENTDATA Event -function SCORING:_EventOnDeadOrCrash( Event ) - self:F( { Event } ) - - local TargetUnit = nil - local TargetGroup = nil - local TargetUnitName = "" - local TargetGroupName = "" - local TargetPlayerName = "" - local TargetCoalition = nil - local TargetCategory = nil - local TargetType = nil - local TargetUnitCoalition = nil - local TargetUnitCategory = nil - local TargetUnitType = nil - - if Event.IniDCSUnit then - - TargetUnit = Event.IniDCSUnit - TargetUnitName = Event.IniDCSUnitName - TargetGroup = Event.IniDCSGroup - TargetGroupName = Event.IniDCSGroupName - TargetPlayerName = TargetUnit:getPlayerName() - - TargetCoalition = TargetUnit:getCoalition() - --TargetCategory = TargetUnit:getCategory() - TargetCategory = TargetUnit:getDesc().category -- Workaround - TargetType = TargetUnit:getTypeName() - - TargetUnitCoalition = _SCORINGCoalition[TargetCoalition] - TargetUnitCategory = _SCORINGCategory[TargetCategory] - TargetUnitType = TargetType - - self:T( { TargetUnitName, TargetGroupName, TargetPlayerName, TargetCoalition, TargetCategory, TargetType } ) - end - - for PlayerName, PlayerData in pairs( self.Players ) do - if PlayerData then -- This should normally not happen, but i'll test it anyway. - self:T( "Something got killed" ) - - -- Some variables - local InitUnitName = PlayerData.UnitName - local InitUnitType = PlayerData.UnitType - local InitCoalition = PlayerData.UnitCoalition - local InitCategory = PlayerData.UnitCategory - local InitUnitCoalition = _SCORINGCoalition[InitCoalition] - local InitUnitCategory = _SCORINGCategory[InitCategory] - - self:T( { InitUnitName, InitUnitType, InitUnitCoalition, InitCoalition, InitUnitCategory, InitCategory } ) - - -- What is he hitting? - if TargetCategory then - if PlayerData and PlayerData.Hit and PlayerData.Hit[TargetCategory] and PlayerData.Hit[TargetCategory][TargetUnitName] then -- Was there a hit for this unit for this player before registered??? - if not PlayerData.Kill[TargetCategory] then - PlayerData.Kill[TargetCategory] = {} - end - if not PlayerData.Kill[TargetCategory][TargetType] then - PlayerData.Kill[TargetCategory][TargetType] = {} - PlayerData.Kill[TargetCategory][TargetType].Score = 0 - PlayerData.Kill[TargetCategory][TargetType].ScoreKill = 0 - PlayerData.Kill[TargetCategory][TargetType].Penalty = 0 - PlayerData.Kill[TargetCategory][TargetType].PenaltyKill = 0 - end - - if InitCoalition == TargetCoalition then - PlayerData.Penalty = PlayerData.Penalty + 25 - PlayerData.Kill[TargetCategory][TargetType].Penalty = PlayerData.Kill[TargetCategory][TargetType].Penalty + 25 - PlayerData.Kill[TargetCategory][TargetType].PenaltyKill = PlayerData.Kill[TargetCategory][TargetType].PenaltyKill + 1 - MESSAGE:New( "Player '" .. PlayerName .. "' killed a friendly " .. TargetUnitCategory .. " ( " .. TargetType .. " ) " .. - PlayerData.Kill[TargetCategory][TargetType].PenaltyKill .. " times. Penalty: -" .. PlayerData.Kill[TargetCategory][TargetType].Penalty .. - ". Score Total:" .. PlayerData.Score - PlayerData.Penalty, - 5 ):ToAll() - self:ScoreCSV( PlayerName, "KILL_PENALTY", 1, -125, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType ) - else - PlayerData.Score = PlayerData.Score + 10 - PlayerData.Kill[TargetCategory][TargetType].Score = PlayerData.Kill[TargetCategory][TargetType].Score + 10 - PlayerData.Kill[TargetCategory][TargetType].ScoreKill = PlayerData.Kill[TargetCategory][TargetType].ScoreKill + 1 - MESSAGE:New( "Player '" .. PlayerName .. "' killed an enemy " .. TargetUnitCategory .. " ( " .. TargetType .. " ) " .. - PlayerData.Kill[TargetCategory][TargetType].ScoreKill .. " times. Score: " .. PlayerData.Kill[TargetCategory][TargetType].Score .. - ". Score Total:" .. PlayerData.Score - PlayerData.Penalty, - 5 ):ToAll() - self:ScoreCSV( PlayerName, "KILL_SCORE", 1, 10, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType ) - end - end - end - end - end -end - - - ---- Add a new player entering a Unit. -function SCORING:_AddPlayerFromUnit( UnitData ) - self:F( UnitData ) - - if UnitData and UnitData:isExist() then - local UnitName = UnitData:getName() - local PlayerName = UnitData:getPlayerName() - local UnitDesc = UnitData:getDesc() - local UnitCategory = UnitDesc.category - local UnitCoalition = UnitData:getCoalition() - local UnitTypeName = UnitData:getTypeName() - - self:T( { PlayerName, UnitName, UnitCategory, UnitCoalition, UnitTypeName } ) - - if self.Players[PlayerName] == nil then -- I believe this is the place where a Player gets a life in a mission when he enters a unit ... - self.Players[PlayerName] = {} - self.Players[PlayerName].Hit = {} - self.Players[PlayerName].Kill = {} - self.Players[PlayerName].Mission = {} - - -- for CategoryID, CategoryName in pairs( SCORINGCategory ) do - -- self.Players[PlayerName].Hit[CategoryID] = {} - -- self.Players[PlayerName].Kill[CategoryID] = {} - -- end - self.Players[PlayerName].HitPlayers = {} - self.Players[PlayerName].HitUnits = {} - self.Players[PlayerName].Score = 0 - self.Players[PlayerName].Penalty = 0 - self.Players[PlayerName].PenaltyCoalition = 0 - self.Players[PlayerName].PenaltyWarning = 0 - end - - if not self.Players[PlayerName].UnitCoalition then - self.Players[PlayerName].UnitCoalition = UnitCoalition - else - if self.Players[PlayerName].UnitCoalition ~= UnitCoalition then - self.Players[PlayerName].Penalty = self.Players[PlayerName].Penalty + 50 - self.Players[PlayerName].PenaltyCoalition = self.Players[PlayerName].PenaltyCoalition + 1 - MESSAGE:New( "Player '" .. PlayerName .. "' changed coalition from " .. _SCORINGCoalition[self.Players[PlayerName].UnitCoalition] .. " to " .. _SCORINGCoalition[UnitCoalition] .. - "(changed " .. self.Players[PlayerName].PenaltyCoalition .. " times the coalition). 50 Penalty points added.", - 2 - ):ToAll() - self:ScoreCSV( PlayerName, "COALITION_PENALTY", 1, -50, self.Players[PlayerName].UnitName, _SCORINGCoalition[self.Players[PlayerName].UnitCoalition], _SCORINGCategory[self.Players[PlayerName].UnitCategory], self.Players[PlayerName].UnitType, - UnitName, _SCORINGCoalition[UnitCoalition], _SCORINGCategory[UnitCategory], UnitData:getTypeName() ) - end - end - self.Players[PlayerName].UnitName = UnitName - self.Players[PlayerName].UnitCoalition = UnitCoalition - self.Players[PlayerName].UnitCategory = UnitCategory - self.Players[PlayerName].UnitType = UnitTypeName - - if self.Players[PlayerName].Penalty > 100 then - if self.Players[PlayerName].PenaltyWarning < 1 then - MESSAGE:New( "Player '" .. PlayerName .. "': WARNING! If you continue to commit FRATRICIDE and have a PENALTY score higher than 150, you will be COURT MARTIALED and DISMISSED from this mission! \nYour total penalty is: " .. self.Players[PlayerName].Penalty, - 30 - ):ToAll() - self.Players[PlayerName].PenaltyWarning = self.Players[PlayerName].PenaltyWarning + 1 - end - end - - if self.Players[PlayerName].Penalty > 150 then - ClientGroup = GROUP:NewFromDCSUnit( UnitData ) - ClientGroup:Destroy() - MESSAGE:New( "Player '" .. PlayerName .. "' committed FRATRICIDE, he will be COURT MARTIALED and is DISMISSED from this mission!", - 10 - ):ToAll() - end - - end -end - - ---- Registers Scores the players completing a Mission Task. --- @param #SCORING self --- @param Mission#MISSION Mission --- @param Unit#UNIT PlayerUnit --- @param #string Text --- @param #number Score -function SCORING:_AddMissionTaskScore( Mission, PlayerUnit, Text, Score ) - - local PlayerName = PlayerUnit:GetPlayerName() - local MissionName = Mission:GetName() - - self:F( { Mission:GetName(), PlayerUnit.UnitName, PlayerName, Text, Score } ) - - if not self.Players[PlayerName].Mission[MissionName] then - self.Players[PlayerName].Mission[MissionName] = {} - self.Players[PlayerName].Mission[MissionName].ScoreTask = 0 - self.Players[PlayerName].Mission[MissionName].ScoreMission = 0 - end - - self:T( PlayerName ) - self:T( self.Players[PlayerName].Mission[MissionName] ) - - self.Players[PlayerName].Score = self.Players[PlayerName].Score + Score - self.Players[PlayerName].Mission[MissionName].ScoreTask = self.Players[PlayerName].Mission[MissionName].ScoreTask + Score - - MESSAGE:New( "Player '" .. PlayerName .. "' has " .. Text .. " in Mission '" .. MissionName .. "'. " .. - Score .. " task score!", - 30 ):ToAll() - - self:ScoreCSV( PlayerName, "TASK_" .. MissionName:gsub( ' ', '_' ), 1, Score, PlayerUnit:GetName() ) -end - - ---- Registers Mission Scores for possible multiple players that contributed in the Mission. --- @param #SCORING self --- @param Mission#MISSION Mission --- @param Unit#UNIT PlayerUnit --- @param #string Text --- @param #number Score -function SCORING:_AddMissionScore( Mission, Text, Score ) - - local MissionName = Mission:GetName() - - self:F( { Mission, Text, Score } ) - - for PlayerName, PlayerData in pairs( self.Players ) do - - if PlayerData.Mission[MissionName] then - - PlayerData.Score = PlayerData.Score + Score - PlayerData.Mission[MissionName].ScoreMission = PlayerData.Mission[MissionName].ScoreMission + Score - - MESSAGE:New( "Player '" .. PlayerName .. "' has " .. Text .. " in Mission '" .. MissionName .. "'. " .. - Score .. " mission score!", - 60 ):ToAll() - - self:ScoreCSV( PlayerName, "MISSION_" .. MissionName:gsub( ' ', '_' ), 1, Score ) - end - end -end - ---- Handles the OnHit event for the scoring. --- @param #SCORING self --- @param Event#EVENTDATA Event -function SCORING:_EventOnHit( Event ) - self:F( { Event } ) - - local InitUnit = nil - local InitUnitName = "" - local InitGroup = nil - local InitGroupName = "" - local InitPlayerName = nil - - local InitCoalition = nil - local InitCategory = nil - local InitType = nil - local InitUnitCoalition = nil - local InitUnitCategory = nil - local InitUnitType = nil - - local TargetUnit = nil - local TargetUnitName = "" - local TargetGroup = nil - local TargetGroupName = "" - local TargetPlayerName = "" - - local TargetCoalition = nil - local TargetCategory = nil - local TargetType = nil - local TargetUnitCoalition = nil - local TargetUnitCategory = nil - local TargetUnitType = nil - - if Event.IniDCSUnit then - - InitUnit = Event.IniDCSUnit - InitUnitName = Event.IniDCSUnitName - InitGroup = Event.IniDCSGroup - InitGroupName = Event.IniDCSGroupName - InitPlayerName = InitUnit:getPlayerName() - - InitCoalition = InitUnit:getCoalition() - --TODO: Workaround Client DCS Bug - --InitCategory = InitUnit:getCategory() - InitCategory = InitUnit:getDesc().category - InitType = InitUnit:getTypeName() - - InitUnitCoalition = _SCORINGCoalition[InitCoalition] - InitUnitCategory = _SCORINGCategory[InitCategory] - InitUnitType = InitType - - self:T( { InitUnitName, InitGroupName, InitPlayerName, InitCoalition, InitCategory, InitType , InitUnitCoalition, InitUnitCategory, InitUnitType } ) - end - - - if Event.TgtDCSUnit then - - TargetUnit = Event.TgtDCSUnit - TargetUnitName = Event.TgtDCSUnitName - TargetGroup = Event.TgtDCSGroup - TargetGroupName = Event.TgtDCSGroupName - TargetPlayerName = TargetUnit:getPlayerName() - - TargetCoalition = TargetUnit:getCoalition() - --TODO: Workaround Client DCS Bug - --TargetCategory = TargetUnit:getCategory() - TargetCategory = TargetUnit:getDesc().category - TargetType = TargetUnit:getTypeName() - - TargetUnitCoalition = _SCORINGCoalition[TargetCoalition] - TargetUnitCategory = _SCORINGCategory[TargetCategory] - TargetUnitType = TargetType - - self:T( { TargetUnitName, TargetGroupName, TargetPlayerName, TargetCoalition, TargetCategory, TargetType, TargetUnitCoalition, TargetUnitCategory, TargetUnitType } ) - end - - if InitPlayerName ~= nil then -- It is a player that is hitting something - self:_AddPlayerFromUnit( InitUnit ) - if self.Players[InitPlayerName] then -- This should normally not happen, but i'll test it anyway. - if TargetPlayerName ~= nil then -- It is a player hitting another player ... - self:_AddPlayerFromUnit( TargetUnit ) - self.Players[InitPlayerName].HitPlayers = self.Players[InitPlayerName].HitPlayers + 1 - end - - self:T( "Hitting Something" ) - -- What is he hitting? - if TargetCategory then - if not self.Players[InitPlayerName].Hit[TargetCategory] then - self.Players[InitPlayerName].Hit[TargetCategory] = {} - end - if not self.Players[InitPlayerName].Hit[TargetCategory][TargetUnitName] then - self.Players[InitPlayerName].Hit[TargetCategory][TargetUnitName] = {} - self.Players[InitPlayerName].Hit[TargetCategory][TargetUnitName].Score = 0 - self.Players[InitPlayerName].Hit[TargetCategory][TargetUnitName].Penalty = 0 - self.Players[InitPlayerName].Hit[TargetCategory][TargetUnitName].ScoreHit = 0 - self.Players[InitPlayerName].Hit[TargetCategory][TargetUnitName].PenaltyHit = 0 - end - local Score = 0 - if InitCoalition == TargetCoalition then - self.Players[InitPlayerName].Penalty = self.Players[InitPlayerName].Penalty + 10 - self.Players[InitPlayerName].Hit[TargetCategory][TargetUnitName].Penalty = self.Players[InitPlayerName].Hit[TargetCategory][TargetUnitName].Penalty + 10 - self.Players[InitPlayerName].Hit[TargetCategory][TargetUnitName].PenaltyHit = self.Players[InitPlayerName].Hit[TargetCategory][TargetUnitName].PenaltyHit + 1 - MESSAGE:New( "Player '" .. InitPlayerName .. "' hit a friendly " .. TargetUnitCategory .. " ( " .. TargetType .. " ) " .. - self.Players[InitPlayerName].Hit[TargetCategory][TargetUnitName].PenaltyHit .. " times. Penalty: -" .. self.Players[InitPlayerName].Hit[TargetCategory][TargetUnitName].Penalty .. - ". Score Total:" .. self.Players[InitPlayerName].Score - self.Players[InitPlayerName].Penalty, - 2 - ):ToAll() - self:ScoreCSV( InitPlayerName, "HIT_PENALTY", 1, -25, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType ) - else - self.Players[InitPlayerName].Score = self.Players[InitPlayerName].Score + 10 - self.Players[InitPlayerName].Hit[TargetCategory][TargetUnitName].Score = self.Players[InitPlayerName].Hit[TargetCategory][TargetUnitName].Score + 1 - self.Players[InitPlayerName].Hit[TargetCategory][TargetUnitName].ScoreHit = self.Players[InitPlayerName].Hit[TargetCategory][TargetUnitName].ScoreHit + 1 - MESSAGE:New( "Player '" .. InitPlayerName .. "' hit a target " .. TargetUnitCategory .. " ( " .. TargetType .. " ) " .. - self.Players[InitPlayerName].Hit[TargetCategory][TargetUnitName].ScoreHit .. " times. Score: " .. self.Players[InitPlayerName].Hit[TargetCategory][TargetUnitName].Score .. - ". Score Total:" .. self.Players[InitPlayerName].Score - self.Players[InitPlayerName].Penalty, - 2 - ):ToAll() - self:ScoreCSV( InitPlayerName, "HIT_SCORE", 1, 1, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType ) - end - end - end - elseif InitPlayerName == nil then -- It is an AI hitting a player??? - - end -end - - -function SCORING:ReportScoreAll() - - env.info( "Hello World " ) - - local ScoreMessage = "" - local PlayerMessage = "" - - self:T( "Score Report" ) - - for PlayerName, PlayerData in pairs( self.Players ) do - if PlayerData then -- This should normally not happen, but i'll test it anyway. - self:T( "Score Player: " .. PlayerName ) - - -- Some variables - local InitUnitCoalition = _SCORINGCoalition[PlayerData.UnitCoalition] - local InitUnitCategory = _SCORINGCategory[PlayerData.UnitCategory] - local InitUnitType = PlayerData.UnitType - local InitUnitName = PlayerData.UnitName - - local PlayerScore = 0 - local PlayerPenalty = 0 - - ScoreMessage = ":\n" - - local ScoreMessageHits = "" - - for CategoryID, CategoryName in pairs( _SCORINGCategory ) do - self:T( CategoryName ) - if PlayerData.Hit[CategoryID] then - local Score = 0 - local ScoreHit = 0 - local Penalty = 0 - local PenaltyHit = 0 - self:T( "Hit scores exist for player " .. PlayerName ) - for UnitName, UnitData in pairs( PlayerData.Hit[CategoryID] ) do - Score = Score + UnitData.Score - ScoreHit = ScoreHit + UnitData.ScoreHit - Penalty = Penalty + UnitData.Penalty - PenaltyHit = UnitData.PenaltyHit - end - local ScoreMessageHit = string.format( "%s:%d ", CategoryName, Score - Penalty ) - self:T( ScoreMessageHit ) - ScoreMessageHits = ScoreMessageHits .. ScoreMessageHit - PlayerScore = PlayerScore + Score - PlayerPenalty = PlayerPenalty + Penalty - else - --ScoreMessageHits = ScoreMessageHits .. string.format( "%s:%d ", string.format(CategoryName, 1, 1), 0 ) - end - end - if ScoreMessageHits ~= "" then - ScoreMessage = ScoreMessage .. " Hits: " .. ScoreMessageHits .. "\n" - end - - local ScoreMessageKills = "" - for CategoryID, CategoryName in pairs( _SCORINGCategory ) do - self:T( "Kill scores exist for player " .. PlayerName ) - if PlayerData.Kill[CategoryID] then - local Score = 0 - local ScoreKill = 0 - local Penalty = 0 - local PenaltyKill = 0 - - for UnitName, UnitData in pairs( PlayerData.Kill[CategoryID] ) do - Score = Score + UnitData.Score - ScoreKill = ScoreKill + UnitData.ScoreKill - Penalty = Penalty + UnitData.Penalty - PenaltyKill = PenaltyKill + UnitData.PenaltyKill - end - - local ScoreMessageKill = string.format( " %s:%d ", CategoryName, Score - Penalty ) - self:T( ScoreMessageKill ) - ScoreMessageKills = ScoreMessageKills .. ScoreMessageKill - - PlayerScore = PlayerScore + Score - PlayerPenalty = PlayerPenalty + Penalty - else - --ScoreMessageKills = ScoreMessageKills .. string.format( "%s:%d ", string.format(CategoryName, 1, 1), 0 ) - end - end - if ScoreMessageKills ~= "" then - ScoreMessage = ScoreMessage .. " Kills: " .. ScoreMessageKills .. "\n" - end - - local ScoreMessageCoalitionChangePenalties = "" - if PlayerData.PenaltyCoalition ~= 0 then - ScoreMessageCoalitionChangePenalties = ScoreMessageCoalitionChangePenalties .. string.format( " -%d (%d changed)", PlayerData.Penalty, PlayerData.PenaltyCoalition ) - PlayerPenalty = PlayerPenalty + PlayerData.Penalty - end - if ScoreMessageCoalitionChangePenalties ~= "" then - ScoreMessage = ScoreMessage .. " Coalition Penalties: " .. ScoreMessageCoalitionChangePenalties .. "\n" - end - - local ScoreMessageMission = "" - local ScoreMission = 0 - local ScoreTask = 0 - for MissionName, MissionData in pairs( PlayerData.Mission ) do - ScoreMission = ScoreMission + MissionData.ScoreMission - ScoreTask = ScoreTask + MissionData.ScoreTask - ScoreMessageMission = ScoreMessageMission .. "'" .. MissionName .. "'; " - end - PlayerScore = PlayerScore + ScoreMission + ScoreTask - - if ScoreMessageMission ~= "" then - ScoreMessage = ScoreMessage .. " Tasks: " .. ScoreTask .. " Mission: " .. ScoreMission .. " ( " .. ScoreMessageMission .. ")\n" - end - - PlayerMessage = PlayerMessage .. string.format( "Player '%s' Score:%d (%d Score -%d Penalties)%s", PlayerName, PlayerScore - PlayerPenalty, PlayerScore, PlayerPenalty, ScoreMessage ) - end - end - MESSAGE:New( PlayerMessage, 30, "Player Scores" ):ToAll() -end - - -function SCORING:ReportScorePlayer() - - env.info( "Hello World " ) - - local ScoreMessage = "" - local PlayerMessage = "" - - self:T( "Score Report" ) - - for PlayerName, PlayerData in pairs( self.Players ) do - if PlayerData then -- This should normally not happen, but i'll test it anyway. - self:T( "Score Player: " .. PlayerName ) - - -- Some variables - local InitUnitCoalition = _SCORINGCoalition[PlayerData.UnitCoalition] - local InitUnitCategory = _SCORINGCategory[PlayerData.UnitCategory] - local InitUnitType = PlayerData.UnitType - local InitUnitName = PlayerData.UnitName - - local PlayerScore = 0 - local PlayerPenalty = 0 - - ScoreMessage = "" - - local ScoreMessageHits = "" - - for CategoryID, CategoryName in pairs( _SCORINGCategory ) do - self:T( CategoryName ) - if PlayerData.Hit[CategoryID] then - local Score = 0 - local ScoreHit = 0 - local Penalty = 0 - local PenaltyHit = 0 - self:T( "Hit scores exist for player " .. PlayerName ) - for UnitName, UnitData in pairs( PlayerData.Hit[CategoryID] ) do - Score = Score + UnitData.Score - ScoreHit = ScoreHit + UnitData.ScoreHit - Penalty = Penalty + UnitData.Penalty - PenaltyHit = UnitData.PenaltyHit - end - local ScoreMessageHit = string.format( "\n %s = %d score(%d;-%d) hits(#%d;#-%d)", CategoryName, Score - Penalty, Score, Penalty, ScoreHit, PenaltyHit ) - self:T( ScoreMessageHit ) - ScoreMessageHits = ScoreMessageHits .. ScoreMessageHit - PlayerScore = PlayerScore + Score - PlayerPenalty = PlayerPenalty + Penalty - else - --ScoreMessageHits = ScoreMessageHits .. string.format( "%s:%d ", string.format(CategoryName, 1, 1), 0 ) - end - end - if ScoreMessageHits ~= "" then - ScoreMessage = ScoreMessage .. "\n Hits: " .. ScoreMessageHits .. " " - end - - local ScoreMessageKills = "" - for CategoryID, CategoryName in pairs( _SCORINGCategory ) do - self:T( "Kill scores exist for player " .. PlayerName ) - if PlayerData.Kill[CategoryID] then - local Score = 0 - local ScoreKill = 0 - local Penalty = 0 - local PenaltyKill = 0 - - for UnitName, UnitData in pairs( PlayerData.Kill[CategoryID] ) do - Score = Score + UnitData.Score - ScoreKill = ScoreKill + UnitData.ScoreKill - Penalty = Penalty + UnitData.Penalty - PenaltyKill = PenaltyKill + UnitData.PenaltyKill - end - - local ScoreMessageKill = string.format( "\n %s = %d score(%d;-%d) hits(#%d;#-%d)", CategoryName, Score - Penalty, Score, Penalty, ScoreKill, PenaltyKill ) - self:T( ScoreMessageKill ) - ScoreMessageKills = ScoreMessageKills .. ScoreMessageKill - - PlayerScore = PlayerScore + Score - PlayerPenalty = PlayerPenalty + Penalty - else - --ScoreMessageKills = ScoreMessageKills .. string.format( "%s:%d ", string.format(CategoryName, 1, 1), 0 ) - end - end - if ScoreMessageKills ~= "" then - ScoreMessage = ScoreMessage .. "\n Kills: " .. ScoreMessageKills .. " " - end - - local ScoreMessageCoalitionChangePenalties = "" - if PlayerData.PenaltyCoalition ~= 0 then - ScoreMessageCoalitionChangePenalties = ScoreMessageCoalitionChangePenalties .. string.format( " -%d (%d changed)", PlayerData.Penalty, PlayerData.PenaltyCoalition ) - PlayerPenalty = PlayerPenalty + PlayerData.Penalty - end - if ScoreMessageCoalitionChangePenalties ~= "" then - ScoreMessage = ScoreMessage .. "\n Coalition: " .. ScoreMessageCoalitionChangePenalties .. " " - end - - local ScoreMessageMission = "" - local ScoreMission = 0 - local ScoreTask = 0 - for MissionName, MissionData in pairs( PlayerData.Mission ) do - ScoreMission = ScoreMission + MissionData.ScoreMission - ScoreTask = ScoreTask + MissionData.ScoreTask - ScoreMessageMission = ScoreMessageMission .. "'" .. MissionName .. "'; " - end - PlayerScore = PlayerScore + ScoreMission + ScoreTask - - if ScoreMessageMission ~= "" then - ScoreMessage = ScoreMessage .. "\n Tasks: " .. ScoreTask .. " Mission: " .. ScoreMission .. " ( " .. ScoreMessageMission .. ") " - end - - PlayerMessage = PlayerMessage .. string.format( "Player '%s' Score = %d ( %d Score, -%d Penalties ):%s", PlayerName, PlayerScore - PlayerPenalty, PlayerScore, PlayerPenalty, ScoreMessage ) - end - end - MESSAGE:New( PlayerMessage, 30, "Player Scores" ):ToAll() - -end - - -function SCORING:SecondsToClock(sSeconds) - local nSeconds = sSeconds - if nSeconds == 0 then - --return nil; - return "00:00:00"; - else - nHours = string.format("%02.f", math.floor(nSeconds/3600)); - nMins = string.format("%02.f", math.floor(nSeconds/60 - (nHours*60))); - nSecs = string.format("%02.f", math.floor(nSeconds - nHours*3600 - nMins *60)); - return nHours..":"..nMins..":"..nSecs - end -end - ---- Opens a score CSV file to log the scores. --- @param #SCORING self --- @param #string ScoringCSV --- @return #SCORING self --- @usage --- -- Open a new CSV file to log the scores of the game Gori Valley. Let the name of the CSV file begin with "Player Scores". --- ScoringObject = SCORING:New( "Gori Valley" ) --- ScoringObject:OpenCSV( "Player Scores" ) -function SCORING:OpenCSV( ScoringCSV ) - self:F( ScoringCSV ) - - if lfs and io and os then - if ScoringCSV then - self.ScoringCSV = ScoringCSV - local fdir = lfs.writedir() .. [[Logs\]] .. self.ScoringCSV .. " " .. os.date( "%Y-%m-%d %H-%M-%S" ) .. ".csv" - - self.CSVFile, self.err = io.open( fdir, "w+" ) - if not self.CSVFile then - error( "Error: Cannot open CSV file in " .. lfs.writedir() ) - end - - self.CSVFile:write( '"GameName","RunTime","Time","PlayerName","ScoreType","PlayerUnitCoaltion","PlayerUnitCategory","PlayerUnitType","PlayerUnitName","TargetUnitCoalition","TargetUnitCategory","TargetUnitType","TargetUnitName","Times","Score"\n' ) - - self.RunTime = os.date("%y-%m-%d_%H-%M-%S") - else - error( "A string containing the CSV file name must be given." ) - end - else - self:E( "The MissionScripting.lua file has not been changed to allow lfs, io and os modules to be used..." ) - end - return self -end - - ---- Registers a score for a player. --- @param #SCORING self --- @param #string PlayerName The name of the player. --- @param #string ScoreType The type of the score. --- @param #string ScoreTimes The amount of scores achieved. --- @param #string ScoreAmount The score given. --- @param #string PlayerUnitName The unit name of the player. --- @param #string PlayerUnitCoalition The coalition of the player unit. --- @param #string PlayerUnitCategory The category of the player unit. --- @param #string PlayerUnitType The type of the player unit. --- @param #string TargetUnitName The name of the target unit. --- @param #string TargetUnitCoalition The coalition of the target unit. --- @param #string TargetUnitCategory The category of the target unit. --- @param #string TargetUnitType The type of the target unit. --- @return #SCORING self -function SCORING:ScoreCSV( PlayerName, ScoreType, ScoreTimes, ScoreAmount, PlayerUnitName, PlayerUnitCoalition, PlayerUnitCategory, PlayerUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType ) - --write statistic information to file - local ScoreTime = self:SecondsToClock( timer.getTime() ) - PlayerName = PlayerName:gsub( '"', '_' ) - - if PlayerUnitName and PlayerUnitName ~= '' then - local PlayerUnit = Unit.getByName( PlayerUnitName ) - - if PlayerUnit then - if not PlayerUnitCategory then - --PlayerUnitCategory = SCORINGCategory[PlayerUnit:getCategory()] - PlayerUnitCategory = _SCORINGCategory[PlayerUnit:getDesc().category] - end - - if not PlayerUnitCoalition then - PlayerUnitCoalition = _SCORINGCoalition[PlayerUnit:getCoalition()] - end - - if not PlayerUnitType then - PlayerUnitType = PlayerUnit:getTypeName() - end - else - PlayerUnitName = '' - PlayerUnitCategory = '' - PlayerUnitCoalition = '' - PlayerUnitType = '' - end - else - PlayerUnitName = '' - PlayerUnitCategory = '' - PlayerUnitCoalition = '' - PlayerUnitType = '' - end - - if not TargetUnitCoalition then - TargetUnitCoalition = '' - end - - if not TargetUnitCategory then - TargetUnitCategory = '' - end - - if not TargetUnitType then - TargetUnitType = '' - end - - if not TargetUnitName then - TargetUnitName = '' - end - - if lfs and io and os then - self.CSVFile:write( - '"' .. self.GameName .. '"' .. ',' .. - '"' .. self.RunTime .. '"' .. ',' .. - '' .. ScoreTime .. '' .. ',' .. - '"' .. PlayerName .. '"' .. ',' .. - '"' .. ScoreType .. '"' .. ',' .. - '"' .. PlayerUnitCoalition .. '"' .. ',' .. - '"' .. PlayerUnitCategory .. '"' .. ',' .. - '"' .. PlayerUnitType .. '"' .. ',' .. - '"' .. PlayerUnitName .. '"' .. ',' .. - '"' .. TargetUnitCoalition .. '"' .. ',' .. - '"' .. TargetUnitCategory .. '"' .. ',' .. - '"' .. TargetUnitType .. '"' .. ',' .. - '"' .. TargetUnitName .. '"' .. ',' .. - '' .. ScoreTimes .. '' .. ',' .. - '' .. ScoreAmount - ) - - self.CSVFile:write( "\n" ) - end -end - - -function SCORING:CloseCSV() - if lfs and io and os then - self.CSVFile:close() - end -end - ---- CARGO Classes --- @module CARGO - - - - - - - ---- Clients are those Groups defined within the Mission Editor that have the skillset defined as "Client" or "Player". --- These clients are defined within the Mission Orchestration Framework (MOF) - -CARGOS = {} - - -CARGO_ZONE = { - ClassName="CARGO_ZONE", - CargoZoneName = '', - CargoHostUnitName = '', - SIGNAL = { - TYPE = { - SMOKE = { ID = 1, TEXT = "smoke" }, - FLARE = { ID = 2, TEXT = "flare" } - }, - COLOR = { - GREEN = { ID = 1, TRIGGERCOLOR = trigger.smokeColor.Green, TEXT = "A green" }, - RED = { ID = 2, TRIGGERCOLOR = trigger.smokeColor.Red, TEXT = "A red" }, - WHITE = { ID = 3, TRIGGERCOLOR = trigger.smokeColor.White, TEXT = "A white" }, - ORANGE = { ID = 4, TRIGGERCOLOR = trigger.smokeColor.Orange, TEXT = "An orange" }, - BLUE = { ID = 5, TRIGGERCOLOR = trigger.smokeColor.Blue, TEXT = "A blue" }, - YELLOW = { ID = 6, TRIGGERCOLOR = trigger.flareColor.Yellow, TEXT = "A yellow" } - } - } -} - ---- Creates a new zone where cargo can be collected or deployed. --- The zone functionality is useful to smoke or indicate routes for cargo pickups or deployments. --- Provide the zone name as declared in the mission file into the CargoZoneName in the :New method. --- An optional parameter is the CargoHostName, which is a Group declared with Late Activation switched on in the mission file. --- The CargoHostName is the "host" of the cargo zone: --- --- * It will smoke the zone position when a client is approaching the zone. --- * Depending on the cargo type, it will assist in the delivery of the cargo by driving to and from the client. --- --- @param #CARGO_ZONE self --- @param #string CargoZoneName The name of the zone as declared within the mission editor. --- @param #string CargoHostName The name of the Group "hosting" the zone. The Group MUST NOT be a static, and must be a "mobile" unit. -function CARGO_ZONE:New( CargoZoneName, CargoHostName ) local self = BASE:Inherit( self, ZONE:New( CargoZoneName ) ) - self:F( { CargoZoneName, CargoHostName } ) - - self.CargoZoneName = CargoZoneName - self.SignalHeight = 2 - --self.CargoZone = trigger.misc.getZone( CargoZoneName ) - - - if CargoHostName then - self.CargoHostName = CargoHostName - end - - self:T( self.CargoZoneName ) - - return self -end - -function CARGO_ZONE:Spawn() - self:F( self.CargoHostName ) - - if self.CargoHostName then -- Only spawn a host in the zone when there is one given as a parameter in the New function. - if self.CargoHostSpawn then - local CargoHostGroup = self.CargoHostSpawn:GetGroupFromIndex() - if CargoHostGroup and CargoHostGroup:IsAlive() then - else - self.CargoHostSpawn:ReSpawn( 1 ) - end - else - self:T( "Initialize CargoHostSpawn" ) - self.CargoHostSpawn = SPAWN:New( self.CargoHostName ):Limit( 1, 1 ) - self.CargoHostSpawn:ReSpawn( 1 ) - end - end - - return self -end - -function CARGO_ZONE:GetHostUnit() - self:F( self ) - - if self.CargoHostName then - - -- A Host has been given, signal the host - local CargoHostGroup = self.CargoHostSpawn:GetGroupFromIndex() - local CargoHostUnit - if CargoHostGroup and CargoHostGroup:IsAlive() then - CargoHostUnit = CargoHostGroup:GetUnit(1) - else - CargoHostUnit = StaticObject.getByName( self.CargoHostName ) - end - - return CargoHostUnit - end - - return nil -end - -function CARGO_ZONE:ReportCargosToClient( Client, CargoType ) - self:F() - - local SignalUnit = self:GetHostUnit() - - if SignalUnit then - - local SignalUnitTypeName = SignalUnit:getTypeName() - - local HostMessage = "" - - local IsCargo = false - for CargoID, Cargo in pairs( CARGOS ) do - if Cargo.CargoType == Task.CargoType then - if Cargo:IsStatusNone() then - HostMessage = HostMessage .. " - " .. Cargo.CargoName .. " - " .. Cargo.CargoType .. " (" .. Cargo.Weight .. "kg)" .. "\n" - IsCargo = true - end - end - end - - if not IsCargo then - HostMessage = "No Cargo Available." - end - - Client:Message( HostMessage, 20, SignalUnitTypeName .. ": Reporting Cargo", 10 ) - end -end - - -function CARGO_ZONE:Signal() - self:F() - - local Signalled = false - - if self.SignalType then - - if self.CargoHostName then - - -- A Host has been given, signal the host - - local SignalUnit = self:GetHostUnit() - - if SignalUnit then - - self:T( 'Signalling Unit' ) - local SignalVehiclePos = SignalUnit:GetPointVec3() - SignalVehiclePos.y = SignalVehiclePos.y + 2 - - if self.SignalType.ID == CARGO_ZONE.SIGNAL.TYPE.SMOKE.ID then - - trigger.action.smoke( SignalVehiclePos, self.SignalColor.TRIGGERCOLOR ) - Signalled = true - - elseif self.SignalType.ID == CARGO_ZONE.SIGNAL.TYPE.FLARE.ID then - - trigger.action.signalFlare( SignalVehiclePos, self.SignalColor.TRIGGERCOLOR , 0 ) - Signalled = false - - end - end - - else - - local ZonePointVec3 = self:GetPointVec3( self.SignalHeight ) -- Get the zone position + the landheight + 2 meters - - if self.SignalType.ID == CARGO_ZONE.SIGNAL.TYPE.SMOKE.ID then - - trigger.action.smoke( ZonePointVec3, self.SignalColor.TRIGGERCOLOR ) - Signalled = true - - elseif self.SignalType.ID == CARGO_ZONE.SIGNAL.TYPE.FLARE.ID then - trigger.action.signalFlare( ZonePointVec3, self.SignalColor.TRIGGERCOLOR, 0 ) - Signalled = false - - end - end - end - - return Signalled - -end - -function CARGO_ZONE:WhiteSmoke( SignalHeight ) - self:F() - - self.SignalType = CARGO_ZONE.SIGNAL.TYPE.SMOKE - self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.WHITE - - if SignalHeight then - self.SignalHeight = SignalHeight - end - - return self -end - -function CARGO_ZONE:BlueSmoke( SignalHeight ) - self:F() - - self.SignalType = CARGO_ZONE.SIGNAL.TYPE.SMOKE - self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.BLUE - - if SignalHeight then - self.SignalHeight = SignalHeight - end - - return self -end - -function CARGO_ZONE:RedSmoke( SignalHeight ) - self:F() - - self.SignalType = CARGO_ZONE.SIGNAL.TYPE.SMOKE - self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.RED - - if SignalHeight then - self.SignalHeight = SignalHeight - end - - return self -end - -function CARGO_ZONE:OrangeSmoke( SignalHeight ) - self:F() - - self.SignalType = CARGO_ZONE.SIGNAL.TYPE.SMOKE - self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.ORANGE - - if SignalHeight then - self.SignalHeight = SignalHeight - end - - return self -end - -function CARGO_ZONE:GreenSmoke( SignalHeight ) - self:F() - - self.SignalType = CARGO_ZONE.SIGNAL.TYPE.SMOKE - self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.GREEN - - if SignalHeight then - self.SignalHeight = SignalHeight - end - - return self -end - - -function CARGO_ZONE:WhiteFlare( SignalHeight ) - self:F() - - self.SignalType = CARGO_ZONE.SIGNAL.TYPE.FLARE - self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.WHITE - - if SignalHeight then - self.SignalHeight = SignalHeight - end - - return self -end - -function CARGO_ZONE:RedFlare( SignalHeight ) - self:F() - - self.SignalType = CARGO_ZONE.SIGNAL.TYPE.FLARE - self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.RED - - if SignalHeight then - self.SignalHeight = SignalHeight - end - - return self -end - -function CARGO_ZONE:GreenFlare( SignalHeight ) - self:F() - - self.SignalType = CARGO_ZONE.SIGNAL.TYPE.FLARE - self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.GREEN - - if SignalHeight then - self.SignalHeight = SignalHeight - end - - return self -end - -function CARGO_ZONE:YellowFlare( SignalHeight ) - self:F() - - self.SignalType = CARGO_ZONE.SIGNAL.TYPE.FLARE - self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.YELLOW - - if SignalHeight then - self.SignalHeight = SignalHeight - end - - return self -end - - -function CARGO_ZONE:GetCargoHostUnit() - self:F( self ) - - if self.CargoHostSpawn then - local CargoHostGroup = self.CargoHostSpawn:GetGroupFromIndex(1) - if CargoHostGroup and CargoHostGroup:IsAlive() then - local CargoHostUnit = CargoHostGroup:GetUnit(1) - if CargoHostUnit and CargoHostUnit:IsAlive() then - return CargoHostUnit - end - end - end - - return nil -end - -function CARGO_ZONE:GetCargoZoneName() - self:F() - - return self.CargoZoneName -end - -CARGO = { - ClassName = "CARGO", - STATUS = { - NONE = 0, - LOADED = 1, - UNLOADED = 2, - LOADING = 3 - }, - CargoClient = nil -} - ---- Add Cargo to the mission... Cargo functionality needs to be reworked a bit, so this is still under construction. I need to make a CARGO Class... -function CARGO:New( CargoType, CargoName, CargoWeight ) local self = BASE:Inherit( self, BASE:New() ) - self:F( { CargoType, CargoName, CargoWeight } ) - - - self.CargoType = CargoType - self.CargoName = CargoName - self.CargoWeight = CargoWeight - - self:StatusNone() - - return self -end - -function CARGO:Spawn( Client ) - self:F() - - return self - -end - -function CARGO:IsNear( Client, LandingZone ) - self:F() - - local Near = true - - return Near - -end - - -function CARGO:IsLoadingToClient() - self:F() - - if self:IsStatusLoading() then - return self.CargoClient - end - - return nil - -end - - -function CARGO:IsLoadedInClient() - self:F() - - if self:IsStatusLoaded() then - return self.CargoClient - end - - return nil - -end - - -function CARGO:UnLoad( Client, TargetZoneName ) - self:F() - - self:StatusUnLoaded() - - return self -end - -function CARGO:OnBoard( Client, LandingZone ) - self:F() - - local Valid = true - - self.CargoClient = Client - local ClientUnit = Client:GetClientGroupDCSUnit() - - return Valid -end - -function CARGO:OnBoarded( Client, LandingZone ) - self:F() - - local OnBoarded = true - - return OnBoarded -end - -function CARGO:Load( Client ) - self:F() - - self:StatusLoaded( Client ) - - return self -end - -function CARGO:IsLandingRequired() - self:F() - return true -end - -function CARGO:IsSlingLoad() - self:F() - return false -end - - -function CARGO:StatusNone() - self:F() - - self.CargoClient = nil - self.CargoStatus = CARGO.STATUS.NONE - - return self -end - -function CARGO:StatusLoading( Client ) - self:F() - - self.CargoClient = Client - self.CargoStatus = CARGO.STATUS.LOADING - self:T( "Cargo " .. self.CargoName .. " loading to Client: " .. self.CargoClient:GetClientGroupName() ) - - return self -end - -function CARGO:StatusLoaded( Client ) - self:F() - - self.CargoClient = Client - self.CargoStatus = CARGO.STATUS.LOADED - self:T( "Cargo " .. self.CargoName .. " loaded in Client: " .. self.CargoClient:GetClientGroupName() ) - - return self -end - -function CARGO:StatusUnLoaded() - self:F() - - self.CargoClient = nil - self.CargoStatus = CARGO.STATUS.UNLOADED - - return self -end - - -function CARGO:IsStatusNone() - self:F() - - return self.CargoStatus == CARGO.STATUS.NONE -end - -function CARGO:IsStatusLoading() - self:F() - - return self.CargoStatus == CARGO.STATUS.LOADING -end - -function CARGO:IsStatusLoaded() - self:F() - - return self.CargoStatus == CARGO.STATUS.LOADED -end - -function CARGO:IsStatusUnLoaded() - self:F() - - return self.CargoStatus == CARGO.STATUS.UNLOADED -end - - -CARGO_GROUP = { - ClassName = "CARGO_GROUP" -} - - -function CARGO_GROUP:New( CargoType, CargoName, CargoWeight, CargoGroupTemplate, CargoZone ) local self = BASE:Inherit( self, CARGO:New( CargoType, CargoName, CargoWeight ) ) - self:F( { CargoType, CargoName, CargoWeight, CargoGroupTemplate, CargoZone } ) - - self.CargoSpawn = SPAWN:NewWithAlias( CargoGroupTemplate, CargoName ) - self.CargoZone = CargoZone - - CARGOS[self.CargoName] = self - - return self - -end - -function CARGO_GROUP:Spawn( Client ) - self:F( { Client } ) - - local SpawnCargo = true - - if self:IsStatusNone() then - local CargoGroup = Group.getByName( self.CargoName ) - if CargoGroup and CargoGroup:isExist() then - SpawnCargo = false - end - - elseif self:IsStatusLoading() then - - local Client = self:IsLoadingToClient() - if Client and Client:GetDCSGroup() then - SpawnCargo = false - else - local CargoGroup = Group.getByName( self.CargoName ) - if CargoGroup and CargoGroup:isExist() then - SpawnCargo = false - end - end - - elseif self:IsStatusLoaded() then - - local ClientLoaded = self:IsLoadedInClient() - -- Now test if another Client is alive (not this one), and it has the CARGO, then this cargo does not need to be initialized and spawned. - if ClientLoaded and ClientLoaded ~= Client then - local ClientGroup = Client:GetDCSGroup() - if ClientLoaded:GetClientGroupDCSUnit() and ClientLoaded:GetClientGroupDCSUnit():isExist() then - SpawnCargo = false - else - self:StatusNone() - end - else - -- Same Client, but now in initialize, so set back the status to None. - self:StatusNone() - end - - elseif self:IsStatusUnLoaded() then - - SpawnCargo = false - - end - - if SpawnCargo then - if self.CargoZone:GetCargoHostUnit() then - --- ReSpawn the Cargo from the CargoHost - self.CargoGroupName = self.CargoSpawn:SpawnFromUnit( self.CargoZone:GetCargoHostUnit(), 60, 30, 1 ):GetName() - else - --- ReSpawn the Cargo in the CargoZone without a host ... - self:T( self.CargoZone ) - self.CargoGroupName = self.CargoSpawn:SpawnInZone( self.CargoZone, true, 1 ):GetName() - end - self:StatusNone() - end - - self:T( { self.CargoGroupName, CARGOS[self.CargoName].CargoGroupName } ) - - return self -end - -function CARGO_GROUP:IsNear( Client, LandingZone ) - self:F() - - local Near = false - - if self.CargoGroupName then - local CargoGroup = Group.getByName( self.CargoGroupName ) - if routines.IsPartOfGroupInRadius( CargoGroup, Client:GetPositionVec3(), 250 ) then - Near = true - end - end - - return Near - -end - - -function CARGO_GROUP:OnBoard( Client, LandingZone, OnBoardSide ) - self:F() - - local Valid = true - - local ClientUnit = Client:GetClientGroupDCSUnit() - - local CarrierPos = ClientUnit:getPoint() - local CarrierPosMove = ClientUnit:getPoint() - local CarrierPosOnBoard = ClientUnit:getPoint() - - local CargoGroup = Group.getByName( self.CargoGroupName ) - - local CargoUnit = CargoGroup:getUnit(1) - local CargoPos = CargoUnit:getPoint() - - self.CargoInAir = CargoUnit:inAir() - - self:T( self.CargoInAir ) - - -- Only move the group to the carrier when the cargo is not in the air - -- (eg. cargo can be on a oil derrick, moving the cargo on the oil derrick will drop the cargo on the sea). - if not self.CargoInAir then - - local Points = {} - - self:T( 'CargoPos x = ' .. CargoPos.x .. " z = " .. CargoPos.z ) - self:T( 'CarrierPosMove x = ' .. CarrierPosMove.x .. " z = " .. CarrierPosMove.z ) - - Points[#Points+1] = routines.ground.buildWP( CargoPos, "Cone", 10 ) - - self:T( 'Points[1] x = ' .. Points[1].x .. " y = " .. Points[1].y ) - - if OnBoardSide == nil then - OnBoardSide = CLIENT.ONBOARDSIDE.NONE - end - - if OnBoardSide == CLIENT.ONBOARDSIDE.LEFT then - - self:T( "TransportCargoOnBoard: Onboarding LEFT" ) - CarrierPosMove.z = CarrierPosMove.z - 25 - CarrierPosOnBoard.z = CarrierPosOnBoard.z - 5 - Points[#Points+1] = routines.ground.buildWP( CarrierPosMove, "Cone", 10 ) - Points[#Points+1] = routines.ground.buildWP( CarrierPosOnBoard, "Cone", 10 ) - - elseif OnBoardSide == CLIENT.ONBOARDSIDE.RIGHT then - - self:T( "TransportCargoOnBoard: Onboarding RIGHT" ) - CarrierPosMove.z = CarrierPosMove.z + 25 - CarrierPosOnBoard.z = CarrierPosOnBoard.z + 5 - Points[#Points+1] = routines.ground.buildWP( CarrierPosMove, "Cone", 10 ) - Points[#Points+1] = routines.ground.buildWP( CarrierPosOnBoard, "Cone", 10 ) - - elseif OnBoardSide == CLIENT.ONBOARDSIDE.BACK then - - self:T( "TransportCargoOnBoard: Onboarding BACK" ) - CarrierPosMove.x = CarrierPosMove.x - 25 - CarrierPosOnBoard.x = CarrierPosOnBoard.x - 5 - Points[#Points+1] = routines.ground.buildWP( CarrierPosMove, "Cone", 10 ) - Points[#Points+1] = routines.ground.buildWP( CarrierPosOnBoard, "Cone", 10 ) - - elseif OnBoardSide == CLIENT.ONBOARDSIDE.FRONT then - - self:T( "TransportCargoOnBoard: Onboarding FRONT" ) - CarrierPosMove.x = CarrierPosMove.x + 25 - CarrierPosOnBoard.x = CarrierPosOnBoard.x + 5 - Points[#Points+1] = routines.ground.buildWP( CarrierPosMove, "Cone", 10 ) - Points[#Points+1] = routines.ground.buildWP( CarrierPosOnBoard, "Cone", 10 ) - - elseif OnBoardSide == CLIENT.ONBOARDSIDE.NONE then - - self:T( "TransportCargoOnBoard: Onboarding CENTRAL" ) - Points[#Points+1] = routines.ground.buildWP( CarrierPos, "Cone", 10 ) - - end - self:T( "TransportCargoOnBoard: Routing " .. self.CargoGroupName ) - - --routines.scheduleFunction( routines.goRoute, { self.CargoGroupName, Points}, timer.getTime() + 4 ) - SCHEDULER:New( self, routines.goRoute, { self.CargoGroupName, Points}, 4 ) - end - - self:StatusLoading( Client ) - - return Valid - -end - - -function CARGO_GROUP:OnBoarded( Client, LandingZone ) - self:F() - - local OnBoarded = false - - local CargoGroup = Group.getByName( self.CargoGroupName ) - - if not self.CargoInAir then - if routines.IsPartOfGroupInRadius( CargoGroup, Client:GetPositionVec3(), 25 ) then - CargoGroup:destroy() - self:StatusLoaded( Client ) - OnBoarded = true - end - else - CargoGroup:destroy() - self:StatusLoaded( Client ) - OnBoarded = true - end - - return OnBoarded -end - - -function CARGO_GROUP:UnLoad( Client, TargetZoneName ) - self:F() - - self:T( 'self.CargoName = ' .. self.CargoName ) - - local CargoGroup = self.CargoSpawn:SpawnFromUnit( Client:GetClientGroupUnit(), 60, 30 ) - - self.CargoGroupName = CargoGroup:GetName() - self:T( 'self.CargoGroupName = ' .. self.CargoGroupName ) - - CargoGroup:TaskRouteToZone( ZONE:New( TargetZoneName ), true ) - - self:StatusUnLoaded() - - return self -end - - -CARGO_PACKAGE = { - ClassName = "CARGO_PACKAGE" -} - - -function CARGO_PACKAGE:New( CargoType, CargoName, CargoWeight, CargoClient ) local self = BASE:Inherit( self, CARGO:New( CargoType, CargoName, CargoWeight ) ) - self:F( { CargoType, CargoName, CargoWeight, CargoClient } ) - - self.CargoClient = CargoClient - - CARGOS[self.CargoName] = self - - return self - -end - - -function CARGO_PACKAGE:Spawn( Client ) - self:F( { self, Client } ) - - -- this needs to be checked thoroughly - - local CargoClientGroup = self.CargoClient:GetDCSGroup() - if not CargoClientGroup then - if not self.CargoClientSpawn then - self.CargoClientSpawn = SPAWN:New( self.CargoClient:GetClientGroupName() ):Limit( 1, 1 ) - end - self.CargoClientSpawn:ReSpawn( 1 ) - end - - local SpawnCargo = true - - if self:IsStatusNone() then - - elseif self:IsStatusLoading() or self:IsStatusLoaded() then - - local CargoClientLoaded = self:IsLoadedInClient() - if CargoClientLoaded and CargoClientLoaded:GetDCSGroup() then - SpawnCargo = false - end - - elseif self:IsStatusUnLoaded() then - - SpawnCargo = false - - else - - end - - if SpawnCargo then - self:StatusLoaded( self.CargoClient ) - end - - return self -end - - -function CARGO_PACKAGE:IsNear( Client, LandingZone ) - self:F() - - local Near = false - - if self.CargoClient and self.CargoClient:GetDCSGroup() then - self:T( self.CargoClient.ClientName ) - self:T( 'Client Exists.' ) - - if routines.IsUnitInRadius( self.CargoClient:GetClientGroupDCSUnit(), Client:GetPositionVec3(), 150 ) then - Near = true - end - end - - return Near - -end - - -function CARGO_PACKAGE:OnBoard( Client, LandingZone, OnBoardSide ) - self:F() - - local Valid = true - - local ClientUnit = Client:GetClientGroupDCSUnit() - - local CarrierPos = ClientUnit:getPoint() - local CarrierPosMove = ClientUnit:getPoint() - local CarrierPosOnBoard = ClientUnit:getPoint() - local CarrierPosMoveAway = ClientUnit:getPoint() - - local CargoHostGroup = self.CargoClient:GetDCSGroup() - local CargoHostName = self.CargoClient:GetDCSGroup():getName() - - local CargoHostUnits = CargoHostGroup:getUnits() - local CargoPos = CargoHostUnits[1]:getPoint() - - local Points = {} - - self:T( 'CargoPos x = ' .. CargoPos.x .. " z = " .. CargoPos.z ) - self:T( 'CarrierPosMove x = ' .. CarrierPosMove.x .. " z = " .. CarrierPosMove.z ) - - Points[#Points+1] = routines.ground.buildWP( CargoPos, "Cone", 10 ) - - self:T( 'Points[1] x = ' .. Points[1].x .. " y = " .. Points[1].y ) - - if OnBoardSide == nil then - OnBoardSide = CLIENT.ONBOARDSIDE.NONE - end - - if OnBoardSide == CLIENT.ONBOARDSIDE.LEFT then - - self:T( "TransportCargoOnBoard: Onboarding LEFT" ) - CarrierPosMove.z = CarrierPosMove.z - 25 - CarrierPosOnBoard.z = CarrierPosOnBoard.z - 5 - CarrierPosMoveAway.z = CarrierPosMoveAway.z - 20 - Points[#Points+1] = routines.ground.buildWP( CarrierPosMove, "Cone", 10 ) - Points[#Points+1] = routines.ground.buildWP( CarrierPosOnBoard, "Cone", 10 ) - Points[#Points+1] = routines.ground.buildWP( CarrierPosMoveAway, "Cone", 10 ) - - elseif OnBoardSide == CLIENT.ONBOARDSIDE.RIGHT then - - self:T( "TransportCargoOnBoard: Onboarding RIGHT" ) - CarrierPosMove.z = CarrierPosMove.z + 25 - CarrierPosOnBoard.z = CarrierPosOnBoard.z + 5 - CarrierPosMoveAway.z = CarrierPosMoveAway.z + 20 - Points[#Points+1] = routines.ground.buildWP( CarrierPosMove, "Cone", 10 ) - Points[#Points+1] = routines.ground.buildWP( CarrierPosOnBoard, "Cone", 10 ) - Points[#Points+1] = routines.ground.buildWP( CarrierPosMoveAway, "Cone", 10 ) - - elseif OnBoardSide == CLIENT.ONBOARDSIDE.BACK then - - self:T( "TransportCargoOnBoard: Onboarding BACK" ) - CarrierPosMove.x = CarrierPosMove.x - 25 - CarrierPosOnBoard.x = CarrierPosOnBoard.x - 5 - CarrierPosMoveAway.x = CarrierPosMoveAway.x - 20 - Points[#Points+1] = routines.ground.buildWP( CarrierPosMove, "Cone", 10 ) - Points[#Points+1] = routines.ground.buildWP( CarrierPosOnBoard, "Cone", 10 ) - Points[#Points+1] = routines.ground.buildWP( CarrierPosMoveAway, "Cone", 10 ) - - elseif OnBoardSide == CLIENT.ONBOARDSIDE.FRONT then - - self:T( "TransportCargoOnBoard: Onboarding FRONT" ) - CarrierPosMove.x = CarrierPosMove.x + 25 - CarrierPosOnBoard.x = CarrierPosOnBoard.x + 5 - CarrierPosMoveAway.x = CarrierPosMoveAway.x + 20 - Points[#Points+1] = routines.ground.buildWP( CarrierPosMove, "Cone", 10 ) - Points[#Points+1] = routines.ground.buildWP( CarrierPosOnBoard, "Cone", 10 ) - Points[#Points+1] = routines.ground.buildWP( CarrierPosMoveAway, "Cone", 10 ) - - elseif OnBoardSide == CLIENT.ONBOARDSIDE.NONE then - - self:T( "TransportCargoOnBoard: Onboarding FRONT" ) - CarrierPosMove.x = CarrierPosMove.x + 25 - CarrierPosOnBoard.x = CarrierPosOnBoard.x + 5 - CarrierPosMoveAway.x = CarrierPosMoveAway.x + 20 - Points[#Points+1] = routines.ground.buildWP( CarrierPosMove, "Cone", 10 ) - Points[#Points+1] = routines.ground.buildWP( CarrierPosOnBoard, "Cone", 10 ) - Points[#Points+1] = routines.ground.buildWP( CarrierPosMoveAway, "Cone", 10 ) - - end - self:T( "Routing " .. CargoHostName ) - - SCHEDULER:New( self, routines.goRoute, { CargoHostName, Points }, 4 ) - - return Valid - -end - - -function CARGO_PACKAGE:OnBoarded( Client, LandingZone ) - self:F() - - local OnBoarded = false - - if self.CargoClient and self.CargoClient:GetDCSGroup() then - if routines.IsUnitInRadius( self.CargoClient:GetClientGroupDCSUnit(), self.CargoClient:GetPositionVec3(), 10 ) then - - -- Switch Cargo from self.CargoClient to Client ... Each cargo can have only one client. So assigning the new client for the cargo is enough. - self:StatusLoaded( Client ) - - -- All done, onboarded the Cargo to the new Client. - OnBoarded = true - end - end - - return OnBoarded -end - - -function CARGO_PACKAGE:UnLoad( Client, TargetZoneName ) - self:F() - - self:T( 'self.CargoName = ' .. self.CargoName ) - --self:T( 'self.CargoHostName = ' .. self.CargoHostName ) - - --self.CargoSpawn:FromCarrier( Client:GetDCSGroup(), TargetZoneName, self.CargoHostName ) - self:StatusUnLoaded() - - return Cargo -end - - -CARGO_SLINGLOAD = { - ClassName = "CARGO_SLINGLOAD" -} - - -function CARGO_SLINGLOAD:New( CargoType, CargoName, CargoWeight, CargoZone, CargoHostName, CargoCountryID ) - local self = BASE:Inherit( self, CARGO:New( CargoType, CargoName, CargoWeight ) ) - self:F( { CargoType, CargoName, CargoWeight, CargoZone, CargoHostName, CargoCountryID } ) - - self.CargoHostName = CargoHostName - - -- Cargo will be initialized around the CargoZone position. - self.CargoZone = CargoZone - - self.CargoCount = 0 - self.CargoStaticName = string.format( "%s#%03d", self.CargoName, self.CargoCount ) - - -- The country ID needs to be correctly set. - self.CargoCountryID = CargoCountryID - - CARGOS[self.CargoName] = self - - return self - -end - - -function CARGO_SLINGLOAD:IsLandingRequired() - self:F() - return false -end - - -function CARGO_SLINGLOAD:IsSlingLoad() - self:F() - return true -end - - -function CARGO_SLINGLOAD:Spawn( Client ) - self:F( { self, Client } ) - - local Zone = trigger.misc.getZone( self.CargoZone ) - - local ZonePos = {} - ZonePos.x = Zone.point.x + math.random( Zone.radius / 2 * -1, Zone.radius / 2 ) - ZonePos.y = Zone.point.z + math.random( Zone.radius / 2 * -1, Zone.radius / 2 ) - - self:T( "Cargo Location = " .. ZonePos.x .. ", " .. ZonePos.y ) - - --[[ - -- This does not work in 1.5.2. - CargoStatic = StaticObject.getByName( self.CargoName ) - if CargoStatic then - CargoStatic:destroy() - end - --]] - - CargoStatic = StaticObject.getByName( self.CargoStaticName ) - - if CargoStatic and CargoStatic:isExist() then - CargoStatic:destroy() - end - - -- I need to make every time a new cargo due to bugs in 1.5.2. - - self.CargoCount = self.CargoCount + 1 - self.CargoStaticName = string.format( "%s#%03d", self.CargoName, self.CargoCount ) - - local CargoTemplate = { - ["category"] = "Cargo", - ["shape_name"] = "ab-212_cargo", - ["type"] = "Cargo1", - ["x"] = ZonePos.x, - ["y"] = ZonePos.y, - ["mass"] = self.CargoWeight, - ["name"] = self.CargoStaticName, - ["canCargo"] = true, - ["heading"] = 0, - } - - coalition.addStaticObject( self.CargoCountryID, CargoTemplate ) - --- end - - return self -end - - -function CARGO_SLINGLOAD:IsNear( Client, LandingZone ) - self:F() - - local Near = false - - return Near -end - - -function CARGO_SLINGLOAD:IsInLandingZone( Client, LandingZone ) - self:F() - - local Near = false - - local CargoStaticUnit = StaticObject.getByName( self.CargoName ) - if CargoStaticUnit then - if routines.IsStaticInZones( CargoStaticUnit, LandingZone ) then - Near = true - end - end - - return Near -end - - -function CARGO_SLINGLOAD:OnBoard( Client, LandingZone, OnBoardSide ) - self:F() - - local Valid = true - - - return Valid -end - - -function CARGO_SLINGLOAD:OnBoarded( Client, LandingZone ) - self:F() - - local OnBoarded = false - - local CargoStaticUnit = StaticObject.getByName( self.CargoName ) - if CargoStaticUnit then - if not routines.IsStaticInZones( CargoStaticUnit, LandingZone ) then - OnBoarded = true - end - end - - return OnBoarded -end - - -function CARGO_SLINGLOAD:UnLoad( Client, TargetZoneName ) - self:F() - - self:T( 'self.CargoName = ' .. self.CargoName ) - self:T( 'self.CargoGroupName = ' .. self.CargoGroupName ) - - self:StatusUnLoaded() - - return Cargo -end ---- This module contains the MESSAGE class. --- --- 1) @{Message#MESSAGE} class, extends @{Base#BASE} --- ================================================= --- Message System to display Messages to Clients, Coalitions or All. --- Messages are shown on the display panel for an amount of seconds, and will then disappear. --- Messages can contain a category which is indicating the category of the message. --- --- 1.1) MESSAGE construction methods --- --------------------------------- --- Messages are created with @{Message#MESSAGE.New}. Note that when the MESSAGE object is created, no message is sent yet. --- To send messages, you need to use the To functions. --- --- 1.2) Send messages with MESSAGE To methods --- ------------------------------------------ --- Messages are sent to: --- --- * Clients with @{Message#MESSAGE.ToClient}. --- * Coalitions with @{Message#MESSAGE.ToCoalition}. --- * All Players with @{Message#MESSAGE.ToAll}. --- --- @module Message --- @author FlightControl - ---- The MESSAGE class --- @type MESSAGE --- @extends Base#BASE -MESSAGE = { - ClassName = "MESSAGE", - MessageCategory = 0, - MessageID = 0, -} - - ---- Creates a new MESSAGE object. Note that these MESSAGE objects are not yet displayed on the display panel. You must use the functions @{ToClient} or @{ToCoalition} or @{ToAll} to send these Messages to the respective recipients. --- @param self --- @param #string MessageText is the text of the Message. --- @param #number MessageDuration is a number in seconds of how long the MESSAGE should be shown on the display panel. --- @param #string MessageCategory (optional) is a string expressing the "category" of the Message. The category will be shown as the first text in the message followed by a ": ". --- @return #MESSAGE --- @usage --- -- Create a series of new Messages. --- -- MessageAll is meant to be sent to all players, for 25 seconds, and is classified as "Score". --- -- MessageRED is meant to be sent to the RED players only, for 10 seconds, and is classified as "End of Mission", with ID "Win". --- -- MessageClient1 is meant to be sent to a Client, for 25 seconds, and is classified as "Score", with ID "Score". --- -- MessageClient1 is meant to be sent to a Client, for 25 seconds, and is classified as "Score", with ID "Score". --- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", 25, "End of Mission" ) --- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", 25, "Penalty" ) --- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", 25, "Score" ) --- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", 25, "Score") -function MESSAGE:New( MessageText, MessageDuration, MessageCategory ) - local self = BASE:Inherit( self, BASE:New() ) - self:F( { MessageText, MessageDuration, MessageCategory } ) - - -- When no MessageCategory is given, we don't show it as a title... - if MessageCategory and MessageCategory ~= "" then - self.MessageCategory = MessageCategory .. ": " - else - self.MessageCategory = "" - end - - self.MessageDuration = MessageDuration or 5 - self.MessageTime = timer.getTime() - self.MessageText = MessageText - - self.MessageSent = false - self.MessageGroup = false - self.MessageCoalition = false - - return self -end - ---- Sends a MESSAGE to a Client Group. Note that the Group needs to be defined within the ME with the skillset "Client" or "Player". --- @param #MESSAGE self --- @param Client#CLIENT Client is the Group of the Client. --- @return #MESSAGE --- @usage --- -- Send the 2 messages created with the @{New} method to the Client Group. --- -- Note that the Message of MessageClient2 is overwriting the Message of MessageClient1. --- ClientGroup = Group.getByName( "ClientGroup" ) --- --- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ):ToClient( ClientGroup ) --- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ):ToClient( ClientGroup ) --- or --- MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ):ToClient( ClientGroup ) --- MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ):ToClient( ClientGroup ) --- or --- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ) --- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ) --- MessageClient1:ToClient( ClientGroup ) --- MessageClient2:ToClient( ClientGroup ) -function MESSAGE:ToClient( Client ) - self:F( Client ) - - if Client and Client:GetClientGroupID() then - - local ClientGroupID = Client:GetClientGroupID() - self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration ) - trigger.action.outTextForGroup( ClientGroupID, self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration ) - end - - return self -end - ---- Sends a MESSAGE to a Group. --- @param #MESSAGE self --- @param Group#GROUP Group is the Group. --- @return #MESSAGE -function MESSAGE:ToGroup( Group ) - self:F( Group.GroupName ) - - if Group then - - self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration ) - trigger.action.outTextForGroup( Group:GetID(), self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration ) - end - - return self -end ---- Sends a MESSAGE to the Blue coalition. --- @param #MESSAGE self --- @return #MESSAGE --- @usage --- -- Send a message created with the @{New} method to the BLUE coalition. --- MessageBLUE = MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToBlue() --- or --- MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToBlue() --- or --- MessageBLUE = MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ) --- MessageBLUE:ToBlue() -function MESSAGE:ToBlue() - self:F() - - self:ToCoalition( coalition.side.BLUE ) - - return self -end - ---- Sends a MESSAGE to the Red Coalition. --- @param #MESSAGE self --- @return #MESSAGE --- @usage --- -- Send a message created with the @{New} method to the RED coalition. --- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToRed() --- or --- MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToRed() --- or --- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ) --- MessageRED:ToRed() -function MESSAGE:ToRed( ) - self:F() - - self:ToCoalition( coalition.side.RED ) - - return self -end - ---- Sends a MESSAGE to a Coalition. --- @param #MESSAGE self --- @param CoalitionSide needs to be filled out by the defined structure of the standard scripting engine @{coalition.side}. --- @return #MESSAGE --- @usage --- -- Send a message created with the @{New} method to the RED coalition. --- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToCoalition( coalition.side.RED ) --- or --- MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToCoalition( coalition.side.RED ) --- or --- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ) --- MessageRED:ToCoalition( coalition.side.RED ) -function MESSAGE:ToCoalition( CoalitionSide ) - self:F( CoalitionSide ) - - if CoalitionSide then - self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration ) - trigger.action.outTextForCoalition( CoalitionSide, self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration ) - end - - return self -end - ---- Sends a MESSAGE to all players. --- @param #MESSAGE self --- @return #MESSAGE --- @usage --- -- Send a message created to all players. --- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ):ToAll() --- or --- MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ):ToAll() --- or --- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ) --- MessageAll:ToAll() -function MESSAGE:ToAll() - self:F() - - self:ToCoalition( coalition.side.RED ) - self:ToCoalition( coalition.side.BLUE ) - - return self -end - - - ------ The MESSAGEQUEUE class ----- @type MESSAGEQUEUE ---MESSAGEQUEUE = { --- ClientGroups = {}, --- CoalitionSides = {} ---} --- ---function MESSAGEQUEUE:New( RefreshInterval ) --- local self = BASE:Inherit( self, BASE:New() ) --- self:F( { RefreshInterval } ) --- --- self.RefreshInterval = RefreshInterval --- --- --self.DisplayFunction = routines.scheduleFunction( self._DisplayMessages, { self }, 0, RefreshInterval ) --- self.DisplayFunction = SCHEDULER:New( self, self._DisplayMessages, {}, 0, RefreshInterval ) --- --- return self ---end --- ------ This function is called automatically by the MESSAGEQUEUE scheduler. ---function MESSAGEQUEUE:_DisplayMessages() --- --- -- First we display all messages that a coalition needs to receive... Also those who are not in a client (CA module clients...). --- for CoalitionSideID, CoalitionSideData in pairs( self.CoalitionSides ) do --- for MessageID, MessageData in pairs( CoalitionSideData.Messages ) do --- if MessageData.MessageSent == false then --- --trigger.action.outTextForCoalition( CoalitionSideID, MessageData.MessageCategory .. '\n' .. MessageData.MessageText:gsub("\n$",""):gsub("\n$",""), MessageData.MessageDuration ) --- MessageData.MessageSent = true --- end --- local MessageTimeLeft = ( MessageData.MessageTime + MessageData.MessageDuration ) - timer.getTime() --- if MessageTimeLeft <= 0 then --- MessageData = nil --- end --- end --- end --- --- -- Then we send the messages for each individual client, but also to be included are those Coalition messages for the Clients who belong to a coalition. --- -- Because the Client messages will overwrite the Coalition messages (for that Client). --- for ClientGroupName, ClientGroupData in pairs( self.ClientGroups ) do --- for MessageID, MessageData in pairs( ClientGroupData.Messages ) do --- if MessageData.MessageGroup == false then --- trigger.action.outTextForGroup( Group.getByName(ClientGroupName):getID(), MessageData.MessageCategory .. '\n' .. MessageData.MessageText:gsub("\n$",""):gsub("\n$",""), MessageData.MessageDuration ) --- MessageData.MessageGroup = true --- end --- local MessageTimeLeft = ( MessageData.MessageTime + MessageData.MessageDuration ) - timer.getTime() --- if MessageTimeLeft <= 0 then --- MessageData = nil --- end --- end --- --- -- Now check if the Client also has messages that belong to the Coalition of the Client... --- for CoalitionSideID, CoalitionSideData in pairs( self.CoalitionSides ) do --- for MessageID, MessageData in pairs( CoalitionSideData.Messages ) do --- local CoalitionGroup = Group.getByName( ClientGroupName ) --- if CoalitionGroup and CoalitionGroup:getCoalition() == CoalitionSideID then --- if MessageData.MessageCoalition == false then --- trigger.action.outTextForGroup( Group.getByName(ClientGroupName):getID(), MessageData.MessageCategory .. '\n' .. MessageData.MessageText:gsub("\n$",""):gsub("\n$",""), MessageData.MessageDuration ) --- MessageData.MessageCoalition = true --- end --- end --- local MessageTimeLeft = ( MessageData.MessageTime + MessageData.MessageDuration ) - timer.getTime() --- if MessageTimeLeft <= 0 then --- MessageData = nil --- end --- end --- end --- end --- --- return true ---end --- ------ The _MessageQueue object is created when the MESSAGE class module is loaded. -----_MessageQueue = MESSAGEQUEUE:New( 0.5 ) --- ---- Stages within a @{TASK} within a @{MISSION}. All of the STAGE functionality is considered internally administered and not to be used by any Mission designer. --- @module STAGE --- @author Flightcontrol - - - - - - - ---- The STAGE class --- @type -STAGE = { - ClassName = "STAGE", - MSG = { ID = "None", TIME = 10 }, - FREQUENCY = { NONE = 0, ONCE = 1, REPEAT = -1 }, - - Name = "NoStage", - StageType = '', - WaitTime = 1, - Frequency = 1, - MessageCount = 0, - MessageInterval = 15, - MessageShown = {}, - MessageShow = false, - MessageFlash = false -} - - -function STAGE:New() - local self = BASE:Inherit( self, BASE:New() ) - self:F() - return self -end - -function STAGE:Execute( Mission, Client, Task ) - - local Valid = true - - return Valid -end - -function STAGE:Executing( Mission, Client, Task ) - -end - -function STAGE:Validate( Mission, Client, Task ) - local Valid = true - - return Valid -end - - -STAGEBRIEF = { - ClassName = "BRIEF", - MSG = { ID = "Brief", TIME = 1 }, - Name = "Brief", - StageBriefingTime = 0, - StageBriefingDuration = 1 -} - -function STAGEBRIEF:New() - local self = BASE:Inherit( self, STAGE:New() ) - self:F() - self.StageType = 'CLIENT' - return self -end - ---- Execute --- @param #STAGEBRIEF self --- @param Mission#MISSION Mission --- @param Client#CLIENT Client --- @param Task#TASK Task --- @return #boolean -function STAGEBRIEF:Execute( Mission, Client, Task ) - local Valid = BASE:Inherited(self):Execute( Mission, Client, Task ) - self:F() - Client:ShowMissionBriefing( Mission.MissionBriefing ) - self.StageBriefingTime = timer.getTime() - return Valid -end - -function STAGEBRIEF:Validate( Mission, Client, Task ) - local Valid = STAGE:Validate( Mission, Client, Task ) - self:T() - - if timer.getTime() - self.StageBriefingTime <= self.StageBriefingDuration then - return 0 - else - self.StageBriefingTime = timer.getTime() - return 1 - end - -end - - -STAGESTART = { - ClassName = "START", - MSG = { ID = "Start", TIME = 1 }, - Name = "Start", - StageStartTime = 0, - StageStartDuration = 1 -} - -function STAGESTART:New() - local self = BASE:Inherit( self, STAGE:New() ) - self:F() - self.StageType = 'CLIENT' - return self -end - -function STAGESTART:Execute( Mission, Client, Task ) - self:F() - local Valid = BASE:Inherited(self):Execute( Mission, Client, Task ) - if Task.TaskBriefing then - Client:Message( Task.TaskBriefing, 30, "Command" ) - else - Client:Message( 'Task ' .. Task.TaskNumber .. '.', 30, "Command" ) - end - self.StageStartTime = timer.getTime() - return Valid -end - -function STAGESTART:Validate( Mission, Client, Task ) - self:F() - local Valid = STAGE:Validate( Mission, Client, Task ) - - if timer.getTime() - self.StageStartTime <= self.StageStartDuration then - return 0 - else - self.StageStartTime = timer.getTime() - return 1 - end - - return 1 - -end - -STAGE_CARGO_LOAD = { - ClassName = "STAGE_CARGO_LOAD" -} - -function STAGE_CARGO_LOAD:New() - local self = BASE:Inherit( self, STAGE:New() ) - self:F() - self.StageType = 'CLIENT' - return self -end - -function STAGE_CARGO_LOAD:Execute( Mission, Client, Task ) - self:F() - local Valid = BASE:Inherited(self):Execute( Mission, Client, Task ) - - for LoadCargoID, LoadCargo in pairs( Task.Cargos.LoadCargos ) do - LoadCargo:Load( Client ) - end - - if Mission.MissionReportFlash and Client:IsTransport() then - Client:ShowCargo() - end - - return Valid -end - -function STAGE_CARGO_LOAD:Validate( Mission, Client, Task ) - self:F() - local Valid = STAGE:Validate( Mission, Client, Task ) - - return 1 -end - - -STAGE_CARGO_INIT = { - ClassName = "STAGE_CARGO_INIT" -} - -function STAGE_CARGO_INIT:New() - local self = BASE:Inherit( self, STAGE:New() ) - self:F() - self.StageType = 'CLIENT' - return self -end - -function STAGE_CARGO_INIT:Execute( Mission, Client, Task ) - self:F() - local Valid = BASE:Inherited(self):Execute( Mission, Client, Task ) - - for InitLandingZoneID, InitLandingZone in pairs( Task.LandingZones.LandingZones ) do - self:T( InitLandingZone ) - InitLandingZone:Spawn() - end - - - self:T( Task.Cargos.InitCargos ) - for InitCargoID, InitCargoData in pairs( Task.Cargos.InitCargos ) do - self:T( { InitCargoData } ) - InitCargoData:Spawn( Client ) - end - - return Valid -end - - -function STAGE_CARGO_INIT:Validate( Mission, Client, Task ) - self:F() - local Valid = STAGE:Validate( Mission, Client, Task ) - - return 1 -end - - - -STAGEROUTE = { - ClassName = "STAGEROUTE", - MSG = { ID = "Route", TIME = 5 }, - Frequency = STAGE.FREQUENCY.REPEAT, - Name = "Route" -} - -function STAGEROUTE:New() - local self = BASE:Inherit( self, STAGE:New() ) - self:F() - self.StageType = 'CLIENT' - self.MessageSwitch = true - return self -end - - ---- Execute the routing. --- @param #STAGEROUTE self --- @param Mission#MISSION Mission --- @param Client#CLIENT Client --- @param Task#TASK Task -function STAGEROUTE:Execute( Mission, Client, Task ) - self:F() - local Valid = BASE:Inherited(self):Execute( Mission, Client, Task ) - - local RouteMessage = "Fly to: " - self:T( Task.LandingZones ) - for LandingZoneID, LandingZoneName in pairs( Task.LandingZones.LandingZoneNames ) do - RouteMessage = RouteMessage .. "\n " .. LandingZoneName .. ' at ' .. routines.getBRStringZone( { zone = LandingZoneName, ref = Client:GetClientGroupDCSUnit():getPoint(), true, true } ) .. ' km.' - end - - if Client:IsMultiSeated() then - Client:Message( RouteMessage, self.MSG.TIME, "Co-Pilot", 20, "Route" ) - else - Client:Message( RouteMessage, self.MSG.TIME, "Command", 20, "Route" ) - end - - - if Mission.MissionReportFlash and Client:IsTransport() then - Client:ShowCargo() - end - - return Valid -end - -function STAGEROUTE:Validate( Mission, Client, Task ) - self:F() - local Valid = STAGE:Validate( Mission, Client, Task ) - - -- check if the Client is in the landing zone - self:T( Task.LandingZones.LandingZoneNames ) - Task.CurrentLandingZoneName = routines.IsUnitNearZonesRadius( Client:GetClientGroupDCSUnit(), Task.LandingZones.LandingZoneNames, 500 ) - - if Task.CurrentLandingZoneName then - - Task.CurrentLandingZone = Task.LandingZones.LandingZones[Task.CurrentLandingZoneName].CargoZone - Task.CurrentCargoZone = Task.LandingZones.LandingZones[Task.CurrentLandingZoneName] - - if Task.CurrentCargoZone then - if not Task.Signalled then - Task.Signalled = Task.CurrentCargoZone:Signal() - end - end - - self:T( 1 ) - return 1 - end - - self:T( 0 ) - return 0 -end - - - -STAGELANDING = { - ClassName = "STAGELANDING", - MSG = { ID = "Landing", TIME = 10 }, - Name = "Landing", - Signalled = false -} - -function STAGELANDING:New() - local self = BASE:Inherit( self, STAGE:New() ) - self:F() - self.StageType = 'CLIENT' - return self -end - ---- Execute the landing coordination. --- @param #STAGELANDING self --- @param Mission#MISSION Mission --- @param Client#CLIENT Client --- @param Task#TASK Task -function STAGELANDING:Execute( Mission, Client, Task ) - self:F() - - if Client:IsMultiSeated() then - Client:Message( "We have arrived at the landing zone.", self.MSG.TIME, "Co-Pilot" ) - else - Client:Message( "You have arrived at the landing zone.", self.MSG.TIME, "Command" ) - end - - Task.HostUnit = Task.CurrentCargoZone:GetHostUnit() - - self:T( { Task.HostUnit } ) - - if Task.HostUnit then - - Task.HostUnitName = Task.HostUnit:GetPrefix() - Task.HostUnitTypeName = Task.HostUnit:GetTypeName() - - local HostMessage = "" - Task.CargoNames = "" - - local IsFirst = true - - for CargoID, Cargo in pairs( CARGOS ) do - if Cargo.CargoType == Task.CargoType then - - if Cargo:IsLandingRequired() then - self:T( "Task for cargo " .. Cargo.CargoType .. " requires landing.") - Task.IsLandingRequired = true - end - - if Cargo:IsSlingLoad() then - self:T( "Task for cargo " .. Cargo.CargoType .. " is a slingload.") - Task.IsSlingLoad = true - end - - if IsFirst then - IsFirst = false - Task.CargoNames = Task.CargoNames .. Cargo.CargoName .. "( " .. Cargo.CargoWeight .. " )" - else - Task.CargoNames = Task.CargoNames .. "; " .. Cargo.CargoName .. "( " .. Cargo.CargoWeight .. " )" - end - end - end - - if Task.IsLandingRequired then - HostMessage = "Land the helicopter to " .. Task.TEXT[1] .. " " .. Task.CargoNames .. "." - else - HostMessage = "Use the Radio menu and F6 to find the cargo, then fly or land near the cargo and " .. Task.TEXT[1] .. " " .. Task.CargoNames .. "." - end - - local Host = "Command" - if Task.HostUnitName then - Host = Task.HostUnitName .. " (" .. Task.HostUnitTypeName .. ")" - else - if Client:IsMultiSeated() then - Host = "Co-Pilot" - end - end - - Client:Message( HostMessage, self.MSG.TIME, Host ) - - end -end - -function STAGELANDING:Validate( Mission, Client, Task ) - self:F() - - Task.CurrentLandingZoneName = routines.IsUnitNearZonesRadius( Client:GetClientGroupDCSUnit(), Task.LandingZones.LandingZoneNames, 500 ) - if Task.CurrentLandingZoneName then - - -- Client is in de landing zone. - self:T( Task.CurrentLandingZoneName ) - - Task.CurrentLandingZone = Task.LandingZones.LandingZones[Task.CurrentLandingZoneName].CargoZone - Task.CurrentCargoZone = Task.LandingZones.LandingZones[Task.CurrentLandingZoneName] - - if Task.CurrentCargoZone then - if not Task.Signalled then - Task.Signalled = Task.CurrentCargoZone:Signal() - end - end - else - if Task.CurrentLandingZone then - Task.CurrentLandingZone = nil - end - if Task.CurrentCargoZone then - Task.CurrentCargoZone = nil - end - Task.Signalled = false - Task:RemoveCargoMenus( Client ) - self:T( -1 ) - return -1 - end - - - local DCSUnitVelocityVec3 = Client:GetClientGroupDCSUnit():getVelocity() - local DCSUnitVelocity = ( DCSUnitVelocityVec3.x ^2 + DCSUnitVelocityVec3.y ^2 + DCSUnitVelocityVec3.z ^2 ) ^ 0.5 - - local DCSUnitPointVec3 = Client:GetClientGroupDCSUnit():getPoint() - local LandHeight = land.getHeight( { x = DCSUnitPointVec3.x, y = DCSUnitPointVec3.z } ) - local DCSUnitHeight = DCSUnitPointVec3.y - LandHeight - - self:T( { Task.IsLandingRequired, Client:GetClientGroupDCSUnit():inAir() } ) - if Task.IsLandingRequired and not Client:GetClientGroupDCSUnit():inAir() then - self:T( 1 ) - Task.IsInAirTestRequired = true - return 1 - end - - self:T( { DCSUnitVelocity, DCSUnitHeight, LandHeight, Task.CurrentCargoZone.SignalHeight } ) - if Task.IsLandingRequired and DCSUnitVelocity <= 0.05 and DCSUnitHeight <= Task.CurrentCargoZone.SignalHeight then - self:T( 1 ) - Task.IsInAirTestRequired = false - return 1 - end - - self:T( 0 ) - return 0 -end - -STAGELANDED = { - ClassName = "STAGELANDED", - MSG = { ID = "Land", TIME = 10 }, - Name = "Landed", - MenusAdded = false -} - -function STAGELANDED:New() - local self = BASE:Inherit( self, STAGE:New() ) - self:F() - self.StageType = 'CLIENT' - return self -end - -function STAGELANDED:Execute( Mission, Client, Task ) - self:F() - - if Task.IsLandingRequired then - - local Host = "Command" - if Task.HostUnitName then - Host = Task.HostUnitName .. " (" .. Task.HostUnitTypeName .. ")" - else - if Client:IsMultiSeated() then - Host = "Co-Pilot" - end - end - - Client:Message( 'You have landed within the landing zone. Use the radio menu (F10) to ' .. Task.TEXT[1] .. ' the ' .. Task.CargoType .. '.', - self.MSG.TIME, Host ) - - if not self.MenusAdded then - Task.Cargo = nil - Task:RemoveCargoMenus( Client ) - Task:AddCargoMenus( Client, CARGOS, 250 ) - end - end -end - - - -function STAGELANDED:Validate( Mission, Client, Task ) - self:F() - - if not routines.IsUnitNearZonesRadius( Client:GetClientGroupDCSUnit(), Task.CurrentLandingZoneName, 500 ) then - self:T( "Client is not anymore in the landing zone, go back to stage Route, and remove cargo menus." ) - Task.Signalled = false - Task:RemoveCargoMenus( Client ) - self:T( -2 ) - return -2 - end - - local DCSUnitVelocityVec3 = Client:GetClientGroupDCSUnit():getVelocity() - local DCSUnitVelocity = ( DCSUnitVelocityVec3.x ^2 + DCSUnitVelocityVec3.y ^2 + DCSUnitVelocityVec3.z ^2 ) ^ 0.5 - - local DCSUnitPointVec3 = Client:GetClientGroupDCSUnit():getPoint() - local LandHeight = land.getHeight( { x = DCSUnitPointVec3.x, y = DCSUnitPointVec3.z } ) - local DCSUnitHeight = DCSUnitPointVec3.y - LandHeight - - self:T( { Task.IsLandingRequired, Client:GetClientGroupDCSUnit():inAir() } ) - if Task.IsLandingRequired and Task.IsInAirTestRequired == true and Client:GetClientGroupDCSUnit():inAir() then - self:T( "Client went back in the air. Go back to stage Landing." ) - self:T( -1 ) - return -1 - end - - self:T( { DCSUnitVelocity, DCSUnitHeight, LandHeight, Task.CurrentCargoZone.SignalHeight } ) - if Task.IsLandingRequired and Task.IsInAirTestRequired == false and DCSUnitVelocity >= 2 and DCSUnitHeight >= Task.CurrentCargoZone.SignalHeight then - self:T( "It seems the Client went back in the air and over the boundary limits. Go back to stage Landing." ) - self:T( -1 ) - return -1 - end - - -- Wait until cargo is selected from the menu. - if Task.IsLandingRequired then - if not Task.Cargo then - self:T( 0 ) - return 0 - end - end - - self:T( 1 ) - return 1 -end - -STAGEUNLOAD = { - ClassName = "STAGEUNLOAD", - MSG = { ID = "Unload", TIME = 10 }, - Name = "Unload" -} - -function STAGEUNLOAD:New() - local self = BASE:Inherit( self, STAGE:New() ) - self:F() - self.StageType = 'CLIENT' - return self -end - ---- Coordinate UnLoading --- @param #STAGEUNLOAD self --- @param Mission#MISSION Mission --- @param Client#CLIENT Client --- @param Task#TASK Task -function STAGEUNLOAD:Execute( Mission, Client, Task ) - self:F() - - if Client:IsMultiSeated() then - Client:Message( 'The ' .. Task.CargoType .. ' are being ' .. Task.TEXT[2] .. ' within the landing zone. Wait until the helicopter is ' .. Task.TEXT[3] .. '.', - "Co-Pilot" ) - else - Client:Message( 'You are unloading the ' .. Task.CargoType .. ' ' .. Task.TEXT[2] .. ' within the landing zone. Wait until the helicopter is ' .. Task.TEXT[3] .. '.', - "Command" ) - end - Task:RemoveCargoMenus( Client ) -end - -function STAGEUNLOAD:Executing( Mission, Client, Task ) - self:F() - env.info( 'STAGEUNLOAD:Executing() Task.Cargo.CargoName = ' .. Task.Cargo.CargoName ) - - local TargetZoneName - - if Task.TargetZoneName then - TargetZoneName = Task.TargetZoneName - else - TargetZoneName = Task.CurrentLandingZoneName - end - - if Task.Cargo:UnLoad( Client, TargetZoneName ) then - Task.ExecuteStage = _TransportExecuteStage.SUCCESS - if Mission.MissionReportFlash then - Client:ShowCargo() - end - end -end - ---- Validate UnLoading --- @param #STAGEUNLOAD self --- @param Mission#MISSION Mission --- @param Client#CLIENT Client --- @param Task#TASK Task -function STAGEUNLOAD:Validate( Mission, Client, Task ) - self:F() - env.info( 'STAGEUNLOAD:Validate()' ) - - if routines.IsUnitNearZonesRadius( Client:GetClientGroupDCSUnit(), Task.CurrentLandingZoneName, 500 ) then - else - Task.ExecuteStage = _TransportExecuteStage.FAILED - Task:RemoveCargoMenus( Client ) - if Client:IsMultiSeated() then - Client:Message( 'The ' .. Task.CargoType .. " haven't been successfully " .. Task.TEXT[3] .. ' within the landing zone. Task and mission has failed.', - _TransportStageMsgTime.DONE, "Co-Pilot" ) - else - Client:Message( 'The ' .. Task.CargoType .. " haven't been successfully " .. Task.TEXT[3] .. ' within the landing zone. Task and mission has failed.', - _TransportStageMsgTime.DONE, "Command" ) - end - return 1 - end - - if not Client:GetClientGroupDCSUnit():inAir() then - else - Task.ExecuteStage = _TransportExecuteStage.FAILED - Task:RemoveCargoMenus( Client ) - if Client:IsMultiSeated() then - Client:Message( 'The ' .. Task.CargoType .. " haven't been successfully " .. Task.TEXT[3] .. ' within the landing zone. Task and mission has failed.', - _TransportStageMsgTime.DONE, "Co-Pilot" ) - else - Client:Message( 'The ' .. Task.CargoType .. " haven't been successfully " .. Task.TEXT[3] .. ' within the landing zone. Task and mission has failed.', - _TransportStageMsgTime.DONE, "Command" ) - end - return 1 - end - - if Task.ExecuteStage == _TransportExecuteStage.SUCCESS then - if Client:IsMultiSeated() then - Client:Message( 'The ' .. Task.CargoType .. ' have been sucessfully ' .. Task.TEXT[3] .. ' within the landing zone.', _TransportStageMsgTime.DONE, "Co-Pilot" ) - else - Client:Message( 'The ' .. Task.CargoType .. ' have been sucessfully ' .. Task.TEXT[3] .. ' within the landing zone.', _TransportStageMsgTime.DONE, "Command" ) - end - Task:RemoveCargoMenus( Client ) - Task.MissionTask:AddGoalCompletion( Task.MissionTask.GoalVerb, Task.CargoName, 1 ) -- We set the cargo as one more goal completed in the mission. - return 1 - end - - return 1 -end - -STAGELOAD = { - ClassName = "STAGELOAD", - MSG = { ID = "Load", TIME = 10 }, - Name = "Load" -} - -function STAGELOAD:New() - local self = BASE:Inherit( self, STAGE:New() ) - self:F() - self.StageType = 'CLIENT' - return self -end - -function STAGELOAD:Execute( Mission, Client, Task ) - self:F() - - if not Task.IsSlingLoad then - - local Host = "Command" - if Task.HostUnitName then - Host = Task.HostUnitName .. " (" .. Task.HostUnitTypeName .. ")" - else - if Client:IsMultiSeated() then - Host = "Co-Pilot" - end - end - - Client:Message( 'The ' .. Task.CargoType .. ' are being ' .. Task.TEXT[2] .. ' within the landing zone. Wait until the helicopter is ' .. Task.TEXT[3] .. '.', - _TransportStageMsgTime.EXECUTING, Host ) - - -- Route the cargo to the Carrier - - Task.Cargo:OnBoard( Client, Task.CurrentCargoZone, Task.OnBoardSide ) - Task.ExecuteStage = _TransportExecuteStage.EXECUTING - else - Task.ExecuteStage = _TransportExecuteStage.EXECUTING - end -end - -function STAGELOAD:Executing( Mission, Client, Task ) - self:F() - - -- If the Cargo is ready to be loaded, load it into the Client. - - local Host = "Command" - if Task.HostUnitName then - Host = Task.HostUnitName .. " (" .. Task.HostUnitTypeName .. ")" - else - if Client:IsMultiSeated() then - Host = "Co-Pilot" - end - end - - if not Task.IsSlingLoad then - self:T( Task.Cargo.CargoName) - - if Task.Cargo:OnBoarded( Client, Task.CurrentCargoZone ) then - - -- Load the Cargo onto the Client - Task.Cargo:Load( Client ) - - -- Message to the pilot that cargo has been loaded. - Client:Message( "The cargo " .. Task.Cargo.CargoName .. " has been loaded in our helicopter.", - 20, Host ) - Task.ExecuteStage = _TransportExecuteStage.SUCCESS - - Client:ShowCargo() - end - else - Client:Message( "Hook the " .. Task.CargoNames .. " onto the helicopter " .. Task.TEXT[3] .. " within the landing zone.", - _TransportStageMsgTime.EXECUTING, Host ) - for CargoID, Cargo in pairs( CARGOS ) do - self:T( "Cargo.CargoName = " .. Cargo.CargoName ) - - if Cargo:IsSlingLoad() then - local CargoStatic = StaticObject.getByName( Cargo.CargoStaticName ) - if CargoStatic then - self:T( "Cargo is found in the DCS simulator.") - local CargoStaticPosition = CargoStatic:getPosition().p - self:T( "Cargo Position x = " .. CargoStaticPosition.x .. ", y = " .. CargoStaticPosition.y .. ", z = " .. CargoStaticPosition.z ) - local CargoStaticHeight = routines.GetUnitHeight( CargoStatic ) - if CargoStaticHeight > 5 then - self:T( "Cargo is airborne.") - Cargo:StatusLoaded() - Task.Cargo = Cargo - Client:Message( 'The Cargo has been successfully hooked onto the helicopter and is now being sling loaded. Fly outside the landing zone.', - self.MSG.TIME, Host ) - Task.ExecuteStage = _TransportExecuteStage.SUCCESS - break - end - else - self:T( "Cargo not found in the DCS simulator." ) - end - end - end - end - -end - -function STAGELOAD:Validate( Mission, Client, Task ) - self:F() - - self:T( "Task.CurrentLandingZoneName = " .. Task.CurrentLandingZoneName ) - - local Host = "Command" - if Task.HostUnitName then - Host = Task.HostUnitName .. " (" .. Task.HostUnitTypeName .. ")" - else - if Client:IsMultiSeated() then - Host = "Co-Pilot" - end - end - - if not Task.IsSlingLoad then - if not routines.IsUnitNearZonesRadius( Client:GetClientGroupDCSUnit(), Task.CurrentLandingZoneName, 500 ) then - Task:RemoveCargoMenus( Client ) - Task.ExecuteStage = _TransportExecuteStage.FAILED - Task.CargoName = nil - Client:Message( "The " .. Task.CargoType .. " loading has been aborted. You flew outside the pick-up zone while loading. ", - self.MSG.TIME, Host ) - self:T( -1 ) - return -1 - end - - local DCSUnitVelocityVec3 = Client:GetClientGroupDCSUnit():getVelocity() - local DCSUnitVelocity = ( DCSUnitVelocityVec3.x ^2 + DCSUnitVelocityVec3.y ^2 + DCSUnitVelocityVec3.z ^2 ) ^ 0.5 - - local DCSUnitPointVec3 = Client:GetClientGroupDCSUnit():getPoint() - local LandHeight = land.getHeight( { x = DCSUnitPointVec3.x, y = DCSUnitPointVec3.z } ) - local DCSUnitHeight = DCSUnitPointVec3.y - LandHeight - - self:T( { Task.IsLandingRequired, Client:GetClientGroupDCSUnit():inAir() } ) - if Task.IsLandingRequired and Task.IsInAirTestRequired == true and Client:GetClientGroupDCSUnit():inAir() then - Task:RemoveCargoMenus( Client ) - Task.ExecuteStage = _TransportExecuteStage.FAILED - Task.CargoName = nil - Client:Message( "The " .. Task.CargoType .. " loading has been aborted. Re-start the " .. Task.TEXT[3] .. " process. Don't fly outside the pick-up zone.", - self.MSG.TIME, Host ) - self:T( -1 ) - return -1 - end - - self:T( { DCSUnitVelocity, DCSUnitHeight, LandHeight, Task.CurrentCargoZone.SignalHeight } ) - if Task.IsLandingRequired and Task.IsInAirTestRequired == false and DCSUnitVelocity >= 2 and DCSUnitHeight >= Task.CurrentCargoZone.SignalHeight then - Task:RemoveCargoMenus( Client ) - Task.ExecuteStage = _TransportExecuteStage.FAILED - Task.CargoName = nil - Client:Message( "The " .. Task.CargoType .. " loading has been aborted. Re-start the " .. Task.TEXT[3] .. " process. Don't fly outside the pick-up zone.", - self.MSG.TIME, Host ) - self:T( -1 ) - return -1 - end - - if Task.ExecuteStage == _TransportExecuteStage.SUCCESS then - Task:RemoveCargoMenus( Client ) - Client:Message( "Good Job. The " .. Task.CargoType .. " has been sucessfully " .. Task.TEXT[3] .. " within the landing zone.", - self.MSG.TIME, Host ) - Task.MissionTask:AddGoalCompletion( Task.MissionTask.GoalVerb, Task.CargoName, 1 ) - self:T( 1 ) - return 1 - end - - else - if Task.ExecuteStage == _TransportExecuteStage.SUCCESS then - CargoStatic = StaticObject.getByName( Task.Cargo.CargoStaticName ) - if CargoStatic and not routines.IsStaticInZones( CargoStatic, Task.CurrentLandingZoneName ) then - Client:Message( "Good Job. The " .. Task.CargoType .. " has been sucessfully " .. Task.TEXT[3] .. " and flown outside of the landing zone.", - self.MSG.TIME, Host ) - Task.MissionTask:AddGoalCompletion( Task.MissionTask.GoalVerb, Task.Cargo.CargoName, 1 ) - self:T( 1 ) - return 1 - end - end - - end - - - self:T( 0 ) - return 0 -end - - -STAGEDONE = { - ClassName = "STAGEDONE", - MSG = { ID = "Done", TIME = 10 }, - Name = "Done" -} - -function STAGEDONE:New() - local self = BASE:Inherit( self, STAGE:New() ) - self:F() - self.StageType = 'AI' - return self -end - -function STAGEDONE:Execute( Mission, Client, Task ) - self:F() - -end - -function STAGEDONE:Validate( Mission, Client, Task ) - self:F() - - Task:Done() - - return 0 -end - -STAGEARRIVE = { - ClassName = "STAGEARRIVE", - MSG = { ID = "Arrive", TIME = 10 }, - Name = "Arrive" -} - -function STAGEARRIVE:New() - local self = BASE:Inherit( self, STAGE:New() ) - self:F() - self.StageType = 'CLIENT' - return self -end - - ---- Execute Arrival --- @param #STAGEARRIVE self --- @param Mission#MISSION Mission --- @param Client#CLIENT Client --- @param Task#TASK Task -function STAGEARRIVE:Execute( Mission, Client, Task ) - self:F() - - if Client:IsMultiSeated() then - Client:Message( 'We have arrived at ' .. Task.CurrentLandingZoneName .. ".", self.MSG.TIME, "Co-Pilot" ) - else - Client:Message( 'We have arrived at ' .. Task.CurrentLandingZoneName .. ".", self.MSG.TIME, "Command" ) - end - -end - -function STAGEARRIVE:Validate( Mission, Client, Task ) - self:F() - - Task.CurrentLandingZoneID = routines.IsUnitInZones( Client:GetClientGroupDCSUnit(), Task.LandingZones ) - if ( Task.CurrentLandingZoneID ) then - else - return -1 - end - - return 1 -end - -STAGEGROUPSDESTROYED = { - ClassName = "STAGEGROUPSDESTROYED", - DestroyGroupSize = -1, - Frequency = STAGE.FREQUENCY.REPEAT, - MSG = { ID = "DestroyGroup", TIME = 10 }, - Name = "GroupsDestroyed" -} - -function STAGEGROUPSDESTROYED:New() - local self = BASE:Inherit( self, STAGE:New() ) - self:F() - self.StageType = 'AI' - return self -end - ---function STAGEGROUPSDESTROYED:Execute( Mission, Client, Task ) --- --- Client:Message( 'Task: Still ' .. DestroyGroupSize .. " of " .. Task.DestroyGroupCount .. " " .. Task.DestroyGroupType .. " to be destroyed!", self.MSG.TIME, Mission.Name .. "/Stage" ) --- ---end - -function STAGEGROUPSDESTROYED:Validate( Mission, Client, Task ) - self:F() - - if Task.MissionTask:IsGoalReached() then - return 1 - else - return 0 - end -end - -function STAGEGROUPSDESTROYED:Execute( Mission, Client, Task ) - self:F() - self:T( { Task.ClassName, Task.Destroyed } ) - --env.info( 'Event Table Task = ' .. tostring(Task) ) - -end - - - - - - - - - - - - - ---[[ - _TransportStage: Defines the different stages of which of transport missions can be in. This table is internal and is used to control the sequence of messages, actions and flow. - - - _TransportStage.START - - _TransportStage.ROUTE - - _TransportStage.LAND - - _TransportStage.EXECUTE - - _TransportStage.DONE - - _TransportStage.REMOVE ---]] -_TransportStage = { - HOLD = "HOLD", - START = "START", - ROUTE = "ROUTE", - LANDING = "LANDING", - LANDED = "LANDED", - EXECUTING = "EXECUTING", - LOAD = "LOAD", - UNLOAD = "UNLOAD", - DONE = "DONE", - NEXT = "NEXT" -} - -_TransportStageMsgTime = { - HOLD = 10, - START = 60, - ROUTE = 5, - LANDING = 10, - LANDED = 30, - EXECUTING = 30, - LOAD = 30, - UNLOAD = 30, - DONE = 30, - NEXT = 0 -} - -_TransportStageTime = { - HOLD = 10, - START = 5, - ROUTE = 5, - LANDING = 1, - LANDED = 1, - EXECUTING = 5, - LOAD = 5, - UNLOAD = 5, - DONE = 1, - NEXT = 0 -} - -_TransportStageAction = { - REPEAT = -1, - NONE = 0, - ONCE = 1 -} ---- This module contains the TASK_BASE class. --- --- 1) @{#TASK_BASE} class, extends @{Base#BASE} --- ============================================ --- 1.1) The @{#TASK_BASE} class implements the methods for task orchestration within MOOSE. --- ---------------------------------------------------------------------------------------- --- The class provides a couple of methods to: --- --- * @{#TASK_BASE.AssignToGroup}():Assign a task to a group (of players). --- * @{#TASK_BASE.AddProcess}():Add a @{Process} to a task. --- * @{#TASK_BASE.RemoveProcesses}():Remove a running @{Process} from a running task. --- * @{#TASK_BASE.AddStateMachine}():Add a @{StateMachine} to a task. --- * @{#TASK_BASE.RemoveStateMachines}():Remove @{StateMachine}s from a task. --- * @{#TASK_BASE.HasStateMachine}():Enquire if the task has a @{StateMachine} --- * @{#TASK_BASE.AssignToUnit}(): Assign a task to a unit. (Needs to be implemented in the derived classes from @{#TASK_BASE}. --- * @{#TASK_BASE.UnAssignFromUnit}(): Unassign the task from a unit. --- --- 1.2) Set and enquire task status (beyond the task state machine processing). --- ---------------------------------------------------------------------------- --- A task needs to implement as a minimum the following task states: --- --- * **Success**: Expresses the successful execution and finalization of the task. --- * **Failed**: Expresses the failure of a task. --- * **Planned**: Expresses that the task is created, but not yet in execution and is not assigned yet. --- * **Assigned**: Expresses that the task is assigned to a Group of players, and that the task is in execution mode. --- --- A task may also implement the following task states: --- --- * **Rejected**: Expresses that the task is rejected by a player, who was requested to accept the task. --- * **Cancelled**: Expresses that the task is cancelled by HQ or through a logical situation where a cancellation of the task is required. --- --- A task can implement more statusses than the ones outlined above. Please consult the documentation of the specific tasks to understand the different status modelled. --- --- The status of tasks can be set by the methods **State** followed by the task status. An example is `StateAssigned()`. --- The status of tasks can be enquired by the methods **IsState** followed by the task status name. An example is `if IsStateAssigned() then`. --- --- 1.3) Add scoring when reaching a certain task status: --- ----------------------------------------------------- --- Upon reaching a certain task status in a task, additional scoring can be given. If the Mission has a scoring system attached, the scores will be added to the mission scoring. --- Use the method @{#TASK_BASE.AddScore}() to add scores when a status is reached. --- --- 1.4) Task briefing: --- ------------------- --- A task briefing can be given that is shown to the player when he is assigned to the task. --- --- === --- --- ### Authors: FlightControl - Design and Programming --- --- @module Task - ---- The TASK_BASE class --- @type TASK_BASE --- @field Scheduler#SCHEDULER TaskScheduler --- @field Mission#MISSION Mission --- @field StateMachine#STATEMACHINE Fsm --- @field Set#SET_GROUP SetGroup The Set of Groups assigned to the Task --- @extends Base#BASE -TASK_BASE = { - ClassName = "TASK_BASE", - TaskScheduler = nil, - Processes = {}, - Players = nil, - Scores = {}, - Menu = {}, - SetGroup = nil, -} - - ---- Instantiates a new TASK_BASE. Should never be used. Interface Class. --- @param #TASK_BASE self --- @param Mission#MISSION The mission wherein the Task is registered. --- @param Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. --- @param #string TaskName The name of the Task --- @param #string TaskType The type of the Task --- @param #string TaskCategory The category of the Task (A2G, A2A, Transport, ... ) --- @return #TASK_BASE self -function TASK_BASE:New( Mission, SetGroup, TaskName, TaskType, TaskCategory ) - - local self = BASE:Inherit( self, BASE:New() ) - self:E( "New TASK " .. TaskName ) - - self.Processes = {} - self.Fsm = {} - - self.Mission = Mission - self.SetGroup = SetGroup - - self:SetCategory( TaskCategory ) - self:SetType( TaskType ) - self:SetName( TaskName ) - self:SetID( Mission:GetNextTaskID( self ) ) -- The Mission orchestrates the task sequences .. - - self.TaskBriefing = "You are assigned to the task: " .. self.TaskName .. "." - - return self -end - ---- Cleans all references of a TASK_BASE. --- @param #TASK_BASE self --- @return #nil -function TASK_BASE:CleanUp() - - _EVENTDISPATCHER:OnPlayerLeaveRemove( self ) - _EVENTDISPATCHER:OnDeadRemove( self ) - _EVENTDISPATCHER:OnCrashRemove( self ) - _EVENTDISPATCHER:OnPilotDeadRemove( self ) - - return nil -end - - ---- Assign the @{Task}to a @{Group}. --- @param #TASK_BASE self --- @param Group#GROUP TaskGroup -function TASK_BASE:AssignToGroup( TaskGroup ) - self:F2( TaskGroup:GetName() ) - - local TaskGroupName = TaskGroup:GetName() - - TaskGroup:SetState( TaskGroup, "Assigned", self ) - - self:RemoveMenuForGroup( TaskGroup ) - self:SetAssignedMenuForGroup( TaskGroup ) - - local TaskUnits = TaskGroup:GetUnits() - for UnitID, UnitData in pairs( TaskUnits ) do - local TaskUnit = UnitData -- Unit#UNIT - local PlayerName = TaskUnit:GetPlayerName() - if PlayerName ~= nil or PlayerName ~= "" then - self:AssignToUnit( TaskUnit ) - end - end -end - ---- Send the briefng message of the @{Task} to the assigned @{Group}s. --- @param #TASK_BASE self -function TASK_BASE:SendBriefingToAssignedGroups() - self:F2() - - for TaskGroupName, TaskGroup in pairs( self.SetGroup:GetSet() ) do - - if self:IsAssignedToGroup( TaskGroup ) then - TaskGroup:Message( self.TaskBriefing, 60 ) - end - end -end - - ---- Assign the @{Task} from the @{Group}s. --- @param #TASK_BASE self -function TASK_BASE:UnAssignFromGroups() - self:F2() - - for TaskGroupName, TaskGroup in pairs( self.SetGroup:GetSet() ) do - - TaskGroup:SetState( TaskGroup, "Assigned", nil ) - local TaskUnits = TaskGroup:GetUnits() - for UnitID, UnitData in pairs( TaskUnits ) do - local TaskUnit = UnitData -- Unit#UNIT - local PlayerName = TaskUnit:GetPlayerName() - if PlayerName ~= nil or PlayerName ~= "" then - self:UnAssignFromUnit( TaskUnit ) - end - end - end -end - ---- Returns if the @{Task} is assigned to the Group. --- @param #TASK_BASE self --- @param Group#GROUP TaskGroup --- @return #boolean -function TASK_BASE:IsAssignedToGroup( TaskGroup ) - - local TaskGroupName = TaskGroup:GetName() - - if self:IsStateAssigned() then - if TaskGroup:GetState( TaskGroup, "Assigned" ) == self then - return true - end - end - - return false -end - ---- Assign the @{Task}to an alive @{Unit}. --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @return #TASK_BASE self -function TASK_BASE:AssignToUnit( TaskUnit ) - self:F( TaskUnit:GetName() ) - - return nil -end - ---- UnAssign the @{Task} from an alive @{Unit}. --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @return #TASK_BASE self -function TASK_BASE:UnAssignFromUnit( TaskUnitName ) - self:F( TaskUnitName ) - - if self:HasStateMachine( TaskUnitName ) == true then - self:RemoveStateMachines( TaskUnitName ) - self:RemoveProcesses( TaskUnitName ) - end - - return self -end - ---- Set the menu options of the @{Task} to all the groups in the SetGroup. --- @param #TASK_BASE self --- @return #TASK_BASE self -function TASK_BASE:SetPlannedMenu() - - local MenuText = self:GetPlannedMenuText() - for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - if not self:IsAssignedToGroup( TaskGroup ) then - self:SetPlannedMenuForGroup( TaskGroup, MenuText ) - end - end -end - ---- Set the menu options of the @{Task} to all the groups in the SetGroup. --- @param #TASK_BASE self --- @return #TASK_BASE self -function TASK_BASE:SetAssignedMenu() - - for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - if self:IsAssignedToGroup( TaskGroup ) then - self:SetAssignedMenuForGroup( TaskGroup ) - end - end -end - ---- Remove the menu options of the @{Task} to all the groups in the SetGroup. --- @param #TASK_BASE self --- @return #TASK_BASE self -function TASK_BASE:RemoveMenu() - - for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - self:RemoveMenuForGroup( TaskGroup ) - end -end - ---- Set the planned menu option of the @{Task}. --- @param #TASK_BASE self --- @param Group#GROUP TaskGroup --- @param #string MenuText The menu text. --- @return #TASK_BASE self -function TASK_BASE:SetPlannedMenuForGroup( TaskGroup, MenuText ) - self:E( TaskGroup:GetName() ) - - local TaskMission = self.Mission:GetName() - local TaskCategory = self:GetCategory() - local TaskType = self:GetType() - - local Mission = self.Mission - - Mission.MenuMission = Mission.MenuMission or {} - local MenuMission = Mission.MenuMission - - Mission.MenuCategory = Mission.MenuCategory or {} - local MenuCategory = Mission.MenuCategory - - Mission.MenuType = Mission.MenuType or {} - local MenuType = Mission.MenuType - - self.Menu = self.Menu or {} - local Menu = self.Menu - - local TaskGroupName = TaskGroup:GetName() - MenuMission[TaskGroupName] = MenuMission[TaskGroupName] or MENU_GROUP:New( TaskGroup, TaskMission, nil ) - - MenuCategory[TaskGroupName] = MenuCategory[TaskGroupName] or {} - MenuCategory[TaskGroupName][TaskCategory] = MenuCategory[TaskGroupName][TaskCategory] or MENU_GROUP:New( TaskGroup, TaskCategory, MenuMission[TaskGroupName] ) - - MenuType[TaskGroupName] = MenuType[TaskGroupName] or {} - MenuType[TaskGroupName][TaskType] = MenuType[TaskGroupName][TaskType] or MENU_GROUP:New( TaskGroup, TaskType, MenuCategory[TaskGroupName][TaskCategory] ) - - if Menu[TaskGroupName] then - Menu[TaskGroupName]:Remove() - end - Menu[TaskGroupName] = MENU_GROUP_COMMAND:New( TaskGroup, MenuText, MenuType[TaskGroupName][TaskType], self.MenuAssignToGroup, { self = self, TaskGroup = TaskGroup } ) - - return self -end - ---- Set the assigned menu options of the @{Task}. --- @param #TASK_BASE self --- @param Group#GROUP TaskGroup --- @return #TASK_BASE self -function TASK_BASE:SetAssignedMenuForGroup( TaskGroup ) - self:E( TaskGroup:GetName() ) - - local TaskMission = self.Mission:GetName() - - local Mission = self.Mission - - Mission.MenuMission = Mission.MenuMission or {} - local MenuMission = Mission.MenuMission - - self.MenuStatus = self.MenuStatus or {} - local MenuStatus = self.MenuStatus - - - self.MenuAbort = self.MenuAbort or {} - local MenuAbort = self.MenuAbort - - local TaskGroupName = TaskGroup:GetName() - MenuMission[TaskGroupName] = MenuMission[TaskGroupName] or MENU_GROUP:New( TaskGroup, TaskMission, nil ) - MenuStatus[TaskGroupName] = MENU_GROUP_COMMAND:New( TaskGroup, "Task Status", MenuMission[TaskGroupName], self.MenuTaskStatus, { self = self, TaskGroup = TaskGroup } ) - MenuAbort[TaskGroupName] = MENU_GROUP_COMMAND:New( TaskGroup, "Abort Task", MenuMission[TaskGroupName], self.MenuTaskAbort, { self = self, TaskGroup = TaskGroup } ) - - return self -end - ---- Remove the menu option of the @{Task} for a @{Group}. --- @param #TASK_BASE self --- @param Group#GROUP TaskGroup --- @return #TASK_BASE self -function TASK_BASE:RemoveMenuForGroup( TaskGroup ) - - local TaskGroupName = TaskGroup:GetName() - - local Mission = self.Mission - local MenuMission = Mission.MenuMission - local MenuCategory = Mission.MenuCategory - local MenuType = Mission.MenuType - local MenuStatus = self.MenuStatus - local MenuAbort = self.MenuAbort - local Menu = self.Menu - - Menu = Menu or {} - if Menu[TaskGroupName] then - Menu[TaskGroupName]:Remove() - Menu[TaskGroupName] = nil - end - - MenuType = MenuType or {} - if MenuType[TaskGroupName] then - for _, Menu in pairs( MenuType[TaskGroupName] ) do - Menu:Remove() - end - MenuType[TaskGroupName] = nil - end - - MenuCategory = MenuCategory or {} - if MenuCategory[TaskGroupName] then - for _, Menu in pairs( MenuCategory[TaskGroupName] ) do - Menu:Remove() - end - MenuCategory[TaskGroupName] = nil - end - - MenuStatus = MenuStatus or {} - if MenuStatus[TaskGroupName] then - MenuStatus[TaskGroupName]:Remove() - MenuStatus[TaskGroupName] = nil - end - - MenuAbort = MenuAbort or {} - if MenuAbort[TaskGroupName] then - MenuAbort[TaskGroupName]:Remove() - MenuAbort[TaskGroupName] = nil - end - -end - -function TASK_BASE.MenuAssignToGroup( MenuParam ) - - local self = MenuParam.self - local TaskGroup = MenuParam.TaskGroup - - self:AssignToGroup( TaskGroup ) -end - -function TASK_BASE.MenuTaskStatus( MenuParam ) - - local self = MenuParam.self - local TaskGroup = MenuParam.TaskGroup - - --self:AssignToGroup( TaskGroup ) -end - -function TASK_BASE.MenuTaskAbort( MenuParam ) - - local self = MenuParam.self - local TaskGroup = MenuParam.TaskGroup - - --self:AssignToGroup( TaskGroup ) -end - - - ---- Returns the @{Task} name. --- @param #TASK_BASE self --- @return #string TaskName -function TASK_BASE:GetTaskName() - return self.TaskName -end - - ---- Add Process to @{Task} with key @{Unit}. --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @return #TASK_BASE self -function TASK_BASE:AddProcess( TaskUnit, Process ) - local TaskUnitName = TaskUnit:GetName() - self.Processes = self.Processes or {} - self.Processes[TaskUnitName] = self.Processes[TaskUnitName] or {} - self.Processes[TaskUnitName][#self.Processes[TaskUnitName]+1] = Process - return Process -end - - ---- Remove Processes from @{Task} with key @{Unit} --- @param #TASK_BASE self --- @param #string TaskUnitName --- @return #TASK_BASE self -function TASK_BASE:RemoveProcesses( TaskUnitName ) - - for ProcessID, ProcessData in pairs( self.Processes[TaskUnitName] ) do - local Process = ProcessData -- Process#PROCESS - Process:StopEvents() - Process = nil - self.Processes[TaskUnitName][ProcessID] = nil - self:E( self.Processes[TaskUnitName][ProcessID] ) - end - self.Processes[TaskUnitName] = nil -end - ---- Fail processes from @{Task} with key @{Unit} --- @param #TASK_BASE self --- @param #string TaskUnitName --- @return #TASK_BASE self -function TASK_BASE:FailProcesses( TaskUnitName ) - - for ProcessID, ProcessData in pairs( self.Processes[TaskUnitName] ) do - local Process = ProcessData -- Process#PROCESS - self:E( { "Failing process: ", Process } ) - Process.Fsm:Fail() - end -end - ---- Add a FiniteStateMachine to @{Task} with key @{Unit} --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @return #TASK_BASE self -function TASK_BASE:AddStateMachine( TaskUnit, Fsm ) - local TaskUnitName = TaskUnit:GetName() - self.Fsm[TaskUnitName] = self.Fsm[TaskUnitName] or {} - self.Fsm[TaskUnitName][#self.Fsm[TaskUnitName]+1] = Fsm - return Fsm -end - ---- Remove FiniteStateMachines from @{Task} with key @{Unit} --- @param #TASK_BASE self --- @param #string TaskUnitName --- @return #TASK_BASE self -function TASK_BASE:RemoveStateMachines( TaskUnitName ) - - for _, Fsm in pairs( self.Fsm[TaskUnitName] ) do - Fsm = nil - self.Fsm[TaskUnitName][_] = nil - self:E( self.Fsm[TaskUnitName][_] ) - end - self.Fsm[TaskUnitName] = nil -end - ---- Checks if there is a FiniteStateMachine assigned to @{Unit} for @{Task} --- @param #TASK_BASE self --- @param #string TaskUnitName --- @return #TASK_BASE self -function TASK_BASE:HasStateMachine( TaskUnitName ) - - self:F( { TaskUnitName, self.Fsm[TaskUnitName] ~= nil } ) - return ( self.Fsm[TaskUnitName] ~= nil ) -end - - - - - ---- Register a potential new assignment for a new spawned @{Unit}. --- Tasks only get assigned if there are players in it. --- @param #TASK_BASE self --- @param Event#EVENTDATA Event --- @return #TASK_BASE self -function TASK_BASE:_EventAssignUnit( Event ) - if Event.IniUnit then - self:F( Event ) - local TaskUnit = Event.IniUnit - if TaskUnit:IsAlive() then - local TaskPlayerName = TaskUnit:GetPlayerName() - if TaskPlayerName ~= nil then - if not self:HasStateMachine( TaskUnit ) then - -- Check if the task was assigned to the group, if it was assigned to the group, assign to the unit just spawned and initiate the processes. - local TaskGroup = TaskUnit:GetGroup() - if self:IsAssignedToGroup( TaskGroup ) then - self:AssignToUnit( TaskUnit ) - end - end - end - end - end - return nil -end - ---- Catches the "player leave unit" event for a @{Unit} .... --- When a player is an air unit, and leaves the unit: --- --- * and he is not at an airbase runway on the ground, he will fail its task. --- * and he is on an airbase and on the ground, the process for him will just continue to work, he can switch airplanes, and take-off again. --- This is important to model the change from plane types for a player during mission assignment. --- @param #TASK_BASE self --- @param Event#EVENTDATA Event --- @return #TASK_BASE self -function TASK_BASE:_EventPlayerLeaveUnit( Event ) - self:F( Event ) - if Event.IniUnit then - local TaskUnit = Event.IniUnit - local TaskUnitName = Event.IniUnitName - - -- Check if for this unit in the task there is a process ongoing. - if self:HasStateMachine( TaskUnitName ) then - if TaskUnit:IsAir() then - if TaskUnit:IsAboveRunway() then - -- do nothing - else - self:E( "IsNotAboveRunway" ) - -- Player left airplane during an assigned task and was not at an airbase. - self:FailProcesses( TaskUnitName ) - self:UnAssignFromUnit( TaskUnitName ) - end - end - end - - end - return nil -end - ---- UnAssigns a @{Unit} that is left by a player, crashed, dead, .... --- There are only assignments if there are players in it. --- @param #TASK_BASE self --- @param Event#EVENTDATA Event --- @return #TASK_BASE self -function TASK_BASE:_EventDead( Event ) - self:F( Event ) - if Event.IniUnit then - local TaskUnit = Event.IniUnit - local TaskUnitName = Event.IniUnitName - - -- Check if for this unit in the task there is a process ongoing. - if self:HasStateMachine( TaskUnitName ) then - self:FailProcesses( TaskUnitName ) - self:UnAssignFromUnit( TaskUnitName ) - end - - local TaskGroup = Event.IniUnit:GetGroup() - TaskGroup:SetState( TaskGroup, "Assigned", nil ) - end - return nil -end - ---- Gets the Scoring of the task --- @param #TASK_BASE self --- @return Scoring#SCORING Scoring -function TASK_BASE:GetScoring() - return self.Mission:GetScoring() -end - - ---- Gets the Task Index, which is a combination of the Task category, the Task type, the Task name. --- @param #TASK_BASE self --- @return #string The Task ID -function TASK_BASE:GetTaskIndex() - - local TaskCategory = self:GetCategory() - local TaskType = self:GetType() - local TaskName = self:GetName() - - return TaskCategory .. "." ..TaskType .. "." .. TaskName -end - ---- Sets the Name of the Task --- @param #TASK_BASE self --- @param #string TaskName -function TASK_BASE:SetName( TaskName ) - self.TaskName = TaskName -end - ---- Gets the Name of the Task --- @param #TASK_BASE self --- @return #string The Task Name -function TASK_BASE:GetName() - return self.TaskName -end - ---- Sets the Type of the Task --- @param #TASK_BASE self --- @param #string TaskType -function TASK_BASE:SetType( TaskType ) - self.TaskType = TaskType -end - ---- Gets the Type of the Task --- @param #TASK_BASE self --- @return #string TaskType -function TASK_BASE:GetType() - return self.TaskType -end - ---- Sets the Category of the Task --- @param #TASK_BASE self --- @param #string TaskCategory -function TASK_BASE:SetCategory( TaskCategory ) - self.TaskCategory = TaskCategory -end - ---- Gets the Category of the Task --- @param #TASK_BASE self --- @return #string TaskCategory -function TASK_BASE:GetCategory() - return self.TaskCategory -end - ---- Sets the ID of the Task --- @param #TASK_BASE self --- @param #string TaskID -function TASK_BASE:SetID( TaskID ) - self.TaskID = TaskID -end - ---- Gets the ID of the Task --- @param #TASK_BASE self --- @return #string TaskID -function TASK_BASE:GetID() - return self.TaskID -end - - ---- Sets a @{Task} to status **Success**. --- @param #TASK_BASE self -function TASK_BASE:StateSuccess() - self:SetState( self, "State", "Success" ) - return self -end - ---- Is the @{Task} status **Success**. --- @param #TASK_BASE self -function TASK_BASE:IsStateSuccess() - return self:GetStateString() == "Success" -end - ---- Sets a @{Task} to status **Failed**. --- @param #TASK_BASE self -function TASK_BASE:StateFailed() - self:SetState( self, "State", "Failed" ) - return self -end - ---- Is the @{Task} status **Failed**. --- @param #TASK_BASE self -function TASK_BASE:IsStateFailed() - return self:GetStateString() == "Failed" -end - ---- Sets a @{Task} to status **Planned**. --- @param #TASK_BASE self -function TASK_BASE:StatePlanned() - self:SetState( self, "State", "Planned" ) - return self -end - ---- Is the @{Task} status **Planned**. --- @param #TASK_BASE self -function TASK_BASE:IsStatePlanned() - return self:GetStateString() == "Planned" -end - ---- Sets a @{Task} to status **Assigned**. --- @param #TASK_BASE self -function TASK_BASE:StateAssigned() - self:SetState( self, "State", "Assigned" ) - return self -end - ---- Is the @{Task} status **Assigned**. --- @param #TASK_BASE self -function TASK_BASE:IsStateAssigned() - return self:GetStateString() == "Assigned" -end - ---- Sets a @{Task} to status **Hold**. --- @param #TASK_BASE self -function TASK_BASE:StateHold() - self:SetState( self, "State", "Hold" ) - return self -end - ---- Is the @{Task} status **Hold**. --- @param #TASK_BASE self -function TASK_BASE:IsStateHold() - return self:GetStateString() == "Hold" -end - ---- Sets a @{Task} to status **Replanned**. --- @param #TASK_BASE self -function TASK_BASE:StateReplanned() - self:SetState( self, "State", "Replanned" ) - return self -end - ---- Is the @{Task} status **Replanned**. --- @param #TASK_BASE self -function TASK_BASE:IsStateReplanned() - return self:GetStateString() == "Replanned" -end - ---- Gets the @{Task} status. --- @param #TASK_BASE self -function TASK_BASE:GetStateString() - return self:GetState( self, "State" ) -end - ---- Sets a @{Task} briefing. --- @param #TASK_BASE self --- @param #string TaskBriefing --- @return #TASK_BASE self -function TASK_BASE:SetBriefing( TaskBriefing ) - self.TaskBriefing = TaskBriefing - return self -end - - - ---- Adds a score for the TASK to be achieved. --- @param #TASK_BASE self --- @param #string TaskStatus is the status of the TASK when the score needs to be given. --- @param #string ScoreText is a text describing the score that is given according the status. --- @param #number Score is a number providing the score of the status. --- @return #TASK_BASE self -function TASK_BASE:AddScore( TaskStatus, ScoreText, Score ) - self:F2( { TaskStatus, ScoreText, Score } ) - - self.Scores[TaskStatus] = self.Scores[TaskStatus] or {} - self.Scores[TaskStatus].ScoreText = ScoreText - self.Scores[TaskStatus].Score = Score - return self -end - ---- StateMachine callback function for a TASK --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @param StateMachine#STATEMACHINE_TASK Fsm --- @param #string Event --- @param #string From --- @param #string To --- @param Event#EVENTDATA Event -function TASK_BASE:OnAssigned( TaskUnit, Fsm, Event, From, To ) - - self:E("Assigned") - - local TaskGroup = TaskUnit:GetGroup() - - TaskGroup:Message( self.TaskBriefing, 20 ) - - self:RemoveMenuForGroup( TaskGroup ) - self:SetAssignedMenuForGroup( TaskGroup ) - -end - - ---- StateMachine callback function for a TASK --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @param StateMachine#STATEMACHINE_TASK Fsm --- @param #string Event --- @param #string From --- @param #string To --- @param Event#EVENTDATA Event -function TASK_BASE:OnSuccess( TaskUnit, Fsm, Event, From, To ) - - self:E("Success") - - self:UnAssignFromGroups() - - local TaskGroup = TaskUnit:GetGroup() - self.Mission:SetPlannedMenu() - - self:StateSuccess() - - -- The task has become successful, the event catchers can be cleaned. - self:CleanUp() - -end - ---- StateMachine callback function for a TASK --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @param StateMachine#STATEMACHINE_TASK Fsm --- @param #string Event --- @param #string From --- @param #string To --- @param Event#EVENTDATA Event -function TASK_BASE:OnFailed( TaskUnit, Fsm, Event, From, To ) - - self:E( { "Failed for unit ", TaskUnit:GetName(), TaskUnit:GetPlayerName() } ) - - -- A task cannot be "failed", so a task will always be there waiting for players to join. - -- When the player leaves its unit, we will need to check whether he was on the ground or not at an airbase. - -- When the player crashes, we will need to check whether in the group there are other players still active. It not, we reset the task from Assigned to Planned, otherwise, we just leave as Assigned. - - self:UnAssignFromGroups() - self:StatePlanned() - -end - ---- StateMachine callback function for a TASK --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @param StateMachine#STATEMACHINE_TASK Fsm --- @param #string Event --- @param #string From --- @param #string To --- @param Event#EVENTDATA Event -function TASK_BASE:OnStateChange( TaskUnit, Fsm, Event, From, To ) - - if self:IsTrace() then - MESSAGE:New( "Task " .. self.TaskName .. " : " .. Event .. " changed to state " .. To, 15 ):ToAll() - end - - self:E( { Event, From, To } ) - self:SetState( self, "State", To ) - - if self.Scores[To] then - local Scoring = self:GetScoring() - if Scoring then - Scoring:_AddMissionScore( self.Mission, self.Scores[To].ScoreText, self.Scores[To].Score ) - end - end - -end - - ---- @param #TASK_BASE self -function TASK_BASE:_Schedule() - self:F2() - - self.TaskScheduler = SCHEDULER:New( self, _Scheduler, {}, 15, 15 ) - return self -end - - ---- @param #TASK_BASE self -function TASK_BASE._Scheduler() - self:F2() - - return true -end - - - - ---- A GOHOMETASK orchestrates the travel back to the home base, which is a specific zone defined within the ME. --- @module GOHOMETASK - ---- The GOHOMETASK class --- @type -GOHOMETASK = { - ClassName = "GOHOMETASK", -} - ---- Creates a new GOHOMETASK. --- @param table{string,...}|string LandingZones Table of Landing Zone names where Home(s) are located. --- @return GOHOMETASK -function GOHOMETASK:New( LandingZones ) - local self = BASE:Inherit( self, TASK:New() ) - self:F( { LandingZones } ) - local Valid = true - - Valid = routines.ValidateZone( LandingZones, "LandingZones", Valid ) - - if Valid then - self.Name = 'Fly Home' - self.TaskBriefing = "Task: Fly back to your home base. Your co-pilot will provide you with the directions (required flight angle in degrees) and the distance (in km) to your home base." - if type( LandingZones ) == "table" then - self.LandingZones = LandingZones - else - self.LandingZones = { LandingZones } - end - self.Stages = { STAGEBRIEF:New(), STAGESTART:New(), STAGEROUTE:New(), STAGEARRIVE:New(), STAGEDONE:New() } - self.SetStage( self, 1 ) - end - - return self -end ---- A DESTROYBASETASK will monitor the destruction of Groups and Units. This is a BASE class, other classes are derived from this class. --- @module DESTROYBASETASK --- @see DESTROYGROUPSTASK --- @see DESTROYUNITTYPESTASK --- @see DESTROY_RADARS_TASK - - - ---- The DESTROYBASETASK class --- @type DESTROYBASETASK -DESTROYBASETASK = { - ClassName = "DESTROYBASETASK", - Destroyed = 0, - GoalVerb = "Destroy", - DestroyPercentage = 100, -} - ---- Creates a new DESTROYBASETASK. --- @param #DESTROYBASETASK self --- @param #string DestroyGroupType Text describing the group to be destroyed. f.e. "Radar Installations", "Ships", "Vehicles", "Command Centers". --- @param #string DestroyUnitType Text describing the unit types to be destroyed. f.e. "SA-6", "Row Boats", "Tanks", "Tents". --- @param #list<#string> DestroyGroupPrefixes Table of Prefixes of the Groups to be destroyed before task is completed. --- @param #number DestroyPercentage defines the %-tage that needs to be destroyed to achieve mission success. eg. If in the Group there are 10 units, then a value of 75 would require 8 units to be destroyed from the Group to complete the @{TASK}. --- @return DESTROYBASETASK -function DESTROYBASETASK:New( DestroyGroupType, DestroyUnitType, DestroyGroupPrefixes, DestroyPercentage ) - local self = BASE:Inherit( self, TASK:New() ) - self:F() - - self.Name = 'Destroy' - self.Destroyed = 0 - self.DestroyGroupPrefixes = DestroyGroupPrefixes - self.DestroyGroupType = DestroyGroupType - self.DestroyUnitType = DestroyUnitType - if DestroyPercentage then - self.DestroyPercentage = DestroyPercentage - end - self.TaskBriefing = "Task: Destroy " .. DestroyGroupType .. "." - self.Stages = { STAGEBRIEF:New(), STAGESTART:New(), STAGEGROUPSDESTROYED:New(), STAGEDONE:New() } - self.SetStage( self, 1 ) - - return self -end - ---- Handle the S_EVENT_DEAD events to validate the destruction of units for the task monitoring. --- @param #DESTROYBASETASK self --- @param Event#EVENTDATA Event structure of MOOSE. -function DESTROYBASETASK:EventDead( Event ) - self:F( { Event } ) - - if Event.IniDCSUnit then - local DestroyUnit = Event.IniDCSUnit - local DestroyUnitName = Event.IniDCSUnitName - local DestroyGroup = Event.IniDCSGroup - local DestroyGroupName = Event.IniDCSGroupName - - --TODO: I need to fix here if 2 groups in the mission have a similar name with GroupPrefix equal, then i should differentiate for which group the goal was reached! - --I may need to test if for the goalverb that group goal was reached or something. Need to think about it a bit more ... - local UnitsDestroyed = 0 - for DestroyGroupPrefixID, DestroyGroupPrefix in pairs( self.DestroyGroupPrefixes ) do - self:T( DestroyGroupPrefix ) - if string.find( DestroyGroupName, DestroyGroupPrefix, 1, true ) then - self:T( BASE:Inherited(self).ClassName ) - UnitsDestroyed = self:ReportGoalProgress( DestroyGroup, DestroyUnit ) - self:T( UnitsDestroyed ) - end - end - - self:T( { UnitsDestroyed } ) - self:IncreaseGoalCount( UnitsDestroyed, self.GoalVerb ) - end - -end - ---- Validate task completeness of DESTROYBASETASK. --- @param DestroyGroup Group structure describing the group to be evaluated. --- @param DestroyUnit Unit structure describing the Unit to be evaluated. -function DESTROYBASETASK:ReportGoalProgress( DestroyGroup, DestroyUnit ) - self:F() - - return 0 -end ---- DESTROYGROUPSTASK --- @module DESTROYGROUPSTASK - - - ---- The DESTROYGROUPSTASK class --- @type -DESTROYGROUPSTASK = { - ClassName = "DESTROYGROUPSTASK", - GoalVerb = "Destroy Groups", -} - ---- Creates a new DESTROYGROUPSTASK. --- @param #DESTROYGROUPSTASK self --- @param #string DestroyGroupType String describing the group to be destroyed. --- @param #string DestroyUnitType String describing the unit to be destroyed. --- @param #list<#string> DestroyGroupNames Table of string containing the name of the groups to be destroyed before task is completed. --- @param #number DestroyPercentage defines the %-tage that needs to be destroyed to achieve mission success. eg. If in the Group there are 10 units, then a value of 75 would require 8 units to be destroyed from the Group to complete the @{TASK}. ----@return DESTROYGROUPSTASK -function DESTROYGROUPSTASK:New( DestroyGroupType, DestroyUnitType, DestroyGroupNames, DestroyPercentage ) - local self = BASE:Inherit( self, DESTROYBASETASK:New( DestroyGroupType, DestroyUnitType, DestroyGroupNames, DestroyPercentage ) ) - self:F() - - self.Name = 'Destroy Groups' - self.GoalVerb = "Destroy " .. DestroyGroupType - - _EVENTDISPATCHER:OnDead( self.EventDead , self ) - _EVENTDISPATCHER:OnCrash( self.EventDead , self ) - - return self -end - ---- Report Goal Progress. --- @param #DESTROYGROUPSTASK self --- @param DCSGroup#Group DestroyGroup Group structure describing the group to be evaluated. --- @param DCSUnit#Unit DestroyUnit Unit structure describing the Unit to be evaluated. --- @return #number The DestroyCount reflecting the amount of units destroyed within the group. -function DESTROYGROUPSTASK:ReportGoalProgress( DestroyGroup, DestroyUnit ) - self:F( { DestroyGroup, DestroyUnit, self.DestroyPercentage } ) - - local DestroyGroupSize = DestroyGroup:getSize() - 1 -- When a DEAD event occurs, the getSize is still one larger than the destroyed unit. - local DestroyGroupInitialSize = DestroyGroup:getInitialSize() - self:T( { DestroyGroupSize, DestroyGroupInitialSize - ( DestroyGroupInitialSize * self.DestroyPercentage / 100 ) } ) - - local DestroyCount = 0 - if DestroyGroup then - if DestroyGroupSize <= DestroyGroupInitialSize - ( DestroyGroupInitialSize * self.DestroyPercentage / 100 ) then - DestroyCount = 1 - end - else - DestroyCount = 1 - end - - self:T( DestroyCount ) - - return DestroyCount -end ---- Task class to destroy radar installations. --- @module DESTROYRADARSTASK - - - ---- The DESTROYRADARS class --- @type -DESTROYRADARSTASK = { - ClassName = "DESTROYRADARSTASK", - GoalVerb = "Destroy Radars" -} - ---- Creates a new DESTROYRADARSTASK. --- @param table{string,...} DestroyGroupNames Table of string containing the group names of which the radars are be destroyed. --- @return DESTROYRADARSTASK -function DESTROYRADARSTASK:New( DestroyGroupNames ) - local self = BASE:Inherit( self, DESTROYGROUPSTASK:New( 'radar installations', 'radars', DestroyGroupNames ) ) - self:F() - - self.Name = 'Destroy Radars' - - _EVENTDISPATCHER:OnDead( self.EventDead , self ) - - return self -end - ---- Report Goal Progress. --- @param Group DestroyGroup Group structure describing the group to be evaluated. --- @param Unit DestroyUnit Unit structure describing the Unit to be evaluated. -function DESTROYRADARSTASK:ReportGoalProgress( DestroyGroup, DestroyUnit ) - self:F( { DestroyGroup, DestroyUnit } ) - - local DestroyCount = 0 - if DestroyUnit and DestroyUnit:hasSensors( Unit.SensorType.RADAR, Unit.RadarType.AS ) then - if DestroyUnit and DestroyUnit:getLife() <= 1.0 then - self:T( 'Destroyed a radar' ) - DestroyCount = 1 - end - end - return DestroyCount -end ---- Set TASK to destroy certain unit types. --- @module DESTROYUNITTYPESTASK - - - ---- The DESTROYUNITTYPESTASK class --- @type -DESTROYUNITTYPESTASK = { - ClassName = "DESTROYUNITTYPESTASK", - GoalVerb = "Destroy", -} - ---- Creates a new DESTROYUNITTYPESTASK. --- @param string DestroyGroupType String describing the group to be destroyed. f.e. "Radar Installations", "Fleet", "Batallion", "Command Centers". --- @param string DestroyUnitType String describing the unit to be destroyed. f.e. "radars", "ships", "tanks", "centers". --- @param table{string,...} DestroyGroupNames Table of string containing the group names of which the radars are be destroyed. --- @param string DestroyUnitTypes Table of string containing the type names of the units to achieve mission success. --- @return DESTROYUNITTYPESTASK -function DESTROYUNITTYPESTASK:New( DestroyGroupType, DestroyUnitType, DestroyGroupNames, DestroyUnitTypes ) - local self = BASE:Inherit( self, DESTROYBASETASK:New( DestroyGroupType, DestroyUnitType, DestroyGroupNames ) ) - self:F( { DestroyGroupType, DestroyUnitType, DestroyGroupNames, DestroyUnitTypes } ) - - if type(DestroyUnitTypes) == 'table' then - self.DestroyUnitTypes = DestroyUnitTypes - else - self.DestroyUnitTypes = { DestroyUnitTypes } - end - - self.Name = 'Destroy Unit Types' - self.GoalVerb = "Destroy " .. DestroyGroupType - - _EVENTDISPATCHER:OnDead( self.EventDead , self ) - - return self -end - ---- Report Goal Progress. --- @param Group DestroyGroup Group structure describing the group to be evaluated. --- @param Unit DestroyUnit Unit structure describing the Unit to be evaluated. -function DESTROYUNITTYPESTASK:ReportGoalProgress( DestroyGroup, DestroyUnit ) - self:F( { DestroyGroup, DestroyUnit } ) - - local DestroyCount = 0 - for UnitTypeID, UnitType in pairs( self.DestroyUnitTypes ) do - if DestroyUnit and DestroyUnit:getTypeName() == UnitType then - if DestroyUnit and DestroyUnit:getLife() <= 1.0 then - DestroyCount = DestroyCount + 1 - end - end - end - return DestroyCount -end ---- A PICKUPTASK orchestrates the loading of CARGO at a specific landing zone. --- @module PICKUPTASK --- @parent TASK - ---- The PICKUPTASK class --- @type -PICKUPTASK = { - ClassName = "PICKUPTASK", - TEXT = { "Pick-Up", "picked-up", "loaded" }, - GoalVerb = "Pick-Up" -} - ---- Creates a new PICKUPTASK. --- @param table{string,...}|string LandingZones Table of Zone names where Cargo is to be loaded. --- @param CARGO_TYPE CargoType Type of the Cargo. The type must be of the following Enumeration:.. --- @param number OnBoardSide Reflects from which side the cargo Group will be on-boarded on the Carrier. -function PICKUPTASK:New( CargoType, OnBoardSide ) - local self = BASE:Inherit( self, TASK:New() ) - self:F() - - -- self holds the inherited instance of the PICKUPTASK Class to the BASE class. - - local Valid = true - - if Valid then - self.Name = 'Pickup Cargo' - self.TaskBriefing = "Task: Fly to the indicated landing zones and pickup " .. CargoType .. ". Your co-pilot will provide you with the directions (required flight angle in degrees) and the distance (in km) to the pickup zone." - self.CargoType = CargoType - self.GoalVerb = CargoType .. " " .. self.GoalVerb - self.OnBoardSide = OnBoardSide - self.IsLandingRequired = true -- required to decide whether the client needs to land or not - self.IsSlingLoad = false -- Indicates whether the cargo is a sling load cargo - self.Stages = { STAGE_CARGO_INIT:New(), STAGE_CARGO_LOAD:New(), STAGEBRIEF:New(), STAGESTART:New(), STAGEROUTE:New(), STAGELANDING:New(), STAGELANDED:New(), STAGELOAD:New(), STAGEDONE:New() } - self.SetStage( self, 1 ) - end - - return self -end - -function PICKUPTASK:FromZone( LandingZone ) - self:F() - - self.LandingZones.LandingZoneNames[LandingZone.CargoZoneName] = LandingZone.CargoZoneName - self.LandingZones.LandingZones[LandingZone.CargoZoneName] = LandingZone - - return self -end - -function PICKUPTASK:InitCargo( InitCargos ) - self:F( { InitCargos } ) - - if type( InitCargos ) == "table" then - self.Cargos.InitCargos = InitCargos - else - self.Cargos.InitCargos = { InitCargos } - end - - return self -end - -function PICKUPTASK:LoadCargo( LoadCargos ) - self:F( { LoadCargos } ) - - if type( LoadCargos ) == "table" then - self.Cargos.LoadCargos = LoadCargos - else - self.Cargos.LoadCargos = { LoadCargos } - end - - return self -end - -function PICKUPTASK:AddCargoMenus( Client, Cargos, TransportRadius ) - self:F() - - for CargoID, Cargo in pairs( Cargos ) do - - self:T( { Cargo.ClassName, Cargo.CargoName, Cargo.CargoType, Cargo:IsStatusNone(), Cargo:IsStatusLoaded(), Cargo:IsStatusLoading(), Cargo:IsStatusUnLoaded() } ) - - -- If the Cargo has no status, allow the menu option. - if Cargo:IsStatusNone() or ( Cargo:IsStatusLoading() and Client == Cargo:IsLoadingToClient() ) then - - local MenuAdd = false - if Cargo:IsNear( Client, self.CurrentCargoZone ) then - MenuAdd = true - end - - if MenuAdd then - if Client._Menus[Cargo.CargoType] == nil then - Client._Menus[Cargo.CargoType] = {} - end - - if not Client._Menus[Cargo.CargoType].PickupMenu then - Client._Menus[Cargo.CargoType].PickupMenu = missionCommands.addSubMenuForGroup( - Client:GetClientGroupID(), - self.TEXT[1] .. " " .. Cargo.CargoType, - nil - ) - self:T( 'Added PickupMenu: ' .. self.TEXT[1] .. " " .. Cargo.CargoType ) - end - - if Client._Menus[Cargo.CargoType].PickupSubMenus == nil then - Client._Menus[Cargo.CargoType].PickupSubMenus = {} - end - - Client._Menus[Cargo.CargoType].PickupSubMenus[ #Client._Menus[Cargo.CargoType].PickupSubMenus + 1 ] = missionCommands.addCommandForGroup( - Client:GetClientGroupID(), - Cargo.CargoName .. " ( " .. Cargo.CargoWeight .. "kg )", - Client._Menus[Cargo.CargoType].PickupMenu, - self.MenuAction, - { ReferenceTask = self, CargoTask = Cargo } - ) - self:T( 'Added PickupSubMenu' .. Cargo.CargoType .. ":" .. Cargo.CargoName .. " ( " .. Cargo.CargoWeight .. "kg )" ) - end - end - end - -end - -function PICKUPTASK:RemoveCargoMenus( Client ) - self:F() - - for MenuID, MenuData in pairs( Client._Menus ) do - for SubMenuID, SubMenuData in pairs( MenuData.PickupSubMenus ) do - missionCommands.removeItemForGroup( Client:GetClientGroupID(), SubMenuData ) - self:T( "Removed PickupSubMenu " ) - SubMenuData = nil - end - if MenuData.PickupMenu then - missionCommands.removeItemForGroup( Client:GetClientGroupID(), MenuData.PickupMenu ) - self:T( "Removed PickupMenu " ) - MenuData.PickupMenu = nil - end - end - - for CargoID, Cargo in pairs( CARGOS ) do - self:T( { Cargo.ClassName, Cargo.CargoName, Cargo.CargoType, Cargo:IsStatusNone(), Cargo:IsStatusLoaded(), Cargo:IsStatusLoading(), Cargo:IsStatusUnLoaded() } ) - if Cargo:IsStatusLoading() and Client == Cargo:IsLoadingToClient() then - Cargo:StatusNone() - end - end - -end - - - -function PICKUPTASK:HasFailed( ClientDead ) - self:F() - - local TaskHasFailed = self.TaskFailed - return TaskHasFailed -end - ---- A DEPLOYTASK orchestrates the deployment of CARGO within a specific landing zone. --- @module DEPLOYTASK - - - ---- A DeployTask --- @type DEPLOYTASK -DEPLOYTASK = { - ClassName = "DEPLOYTASK", - TEXT = { "Deploy", "deployed", "unloaded" }, - GoalVerb = "Deployment" -} - - ---- Creates a new DEPLOYTASK object, which models the sequence of STAGEs to unload a cargo. --- @function [parent=#DEPLOYTASK] New --- @param #string CargoType Type of the Cargo. --- @return #DEPLOYTASK The created DeployTask -function DEPLOYTASK:New( CargoType ) - local self = BASE:Inherit( self, TASK:New() ) - self:F() - - local Valid = true - - if Valid then - self.Name = 'Deploy Cargo' - self.TaskBriefing = "Fly to one of the indicated landing zones and deploy " .. CargoType .. ". Your co-pilot will provide you with the directions (required flight angle in degrees) and the distance (in km) to the deployment zone." - self.CargoType = CargoType - self.GoalVerb = CargoType .. " " .. self.GoalVerb - self.Stages = { STAGE_CARGO_INIT:New(), STAGE_CARGO_LOAD:New(), STAGEBRIEF:New(), STAGESTART:New(), STAGEROUTE:New(), STAGELANDING:New(), STAGELANDED:New(), STAGEUNLOAD:New(), STAGEDONE:New() } - self.SetStage( self, 1 ) - end - - return self -end - -function DEPLOYTASK:ToZone( LandingZone ) - self:F() - - self.LandingZones.LandingZoneNames[LandingZone.CargoZoneName] = LandingZone.CargoZoneName - self.LandingZones.LandingZones[LandingZone.CargoZoneName] = LandingZone - - return self -end - - -function DEPLOYTASK:InitCargo( InitCargos ) - self:F( { InitCargos } ) - - if type( InitCargos ) == "table" then - self.Cargos.InitCargos = InitCargos - else - self.Cargos.InitCargos = { InitCargos } - end - - return self -end - - -function DEPLOYTASK:LoadCargo( LoadCargos ) - self:F( { LoadCargos } ) - - if type( LoadCargos ) == "table" then - self.Cargos.LoadCargos = LoadCargos - else - self.Cargos.LoadCargos = { LoadCargos } - end - - return self -end - - ---- When the cargo is unloaded, it will move to the target zone name. --- @param string TargetZoneName Name of the Zone to where the Cargo should move after unloading. -function DEPLOYTASK:SetCargoTargetZoneName( TargetZoneName ) - self:F() - - local Valid = true - - Valid = routines.ValidateString( TargetZoneName, "TargetZoneName", Valid ) - - if Valid then - self.TargetZoneName = TargetZoneName - end - - return Valid - -end - -function DEPLOYTASK:AddCargoMenus( Client, Cargos, TransportRadius ) - self:F() - - local ClientGroupID = Client:GetClientGroupID() - - self:T( ClientGroupID ) - - for CargoID, Cargo in pairs( Cargos ) do - - self:T( { Cargo.ClassName, Cargo.CargoName, Cargo.CargoType, Cargo.CargoWeight } ) - - if Cargo:IsStatusLoaded() and Client == Cargo:IsLoadedInClient() then - - if Client._Menus[Cargo.CargoType] == nil then - Client._Menus[Cargo.CargoType] = {} - end - - if not Client._Menus[Cargo.CargoType].DeployMenu then - Client._Menus[Cargo.CargoType].DeployMenu = missionCommands.addSubMenuForGroup( - ClientGroupID, - self.TEXT[1] .. " " .. Cargo.CargoType, - nil - ) - self:T( 'Added DeployMenu ' .. self.TEXT[1] ) - end - - if Client._Menus[Cargo.CargoType].DeploySubMenus == nil then - Client._Menus[Cargo.CargoType].DeploySubMenus = {} - end - - if Client._Menus[Cargo.CargoType].DeployMenu == nil then - self:T( 'deploymenu is nil' ) - end - - Client._Menus[Cargo.CargoType].DeploySubMenus[ #Client._Menus[Cargo.CargoType].DeploySubMenus + 1 ] = missionCommands.addCommandForGroup( - ClientGroupID, - Cargo.CargoName .. " ( " .. Cargo.CargoWeight .. "kg )", - Client._Menus[Cargo.CargoType].DeployMenu, - self.MenuAction, - { ReferenceTask = self, CargoTask = Cargo } - ) - self:T( 'Added DeploySubMenu ' .. Cargo.CargoType .. ":" .. Cargo.CargoName .. " ( " .. Cargo.CargoWeight .. "kg )" ) - end - end - -end - -function DEPLOYTASK:RemoveCargoMenus( Client ) - self:F() - - local ClientGroupID = Client:GetClientGroupID() - self:T( ClientGroupID ) - - for MenuID, MenuData in pairs( Client._Menus ) do - if MenuData.DeploySubMenus ~= nil then - for SubMenuID, SubMenuData in pairs( MenuData.DeploySubMenus ) do - missionCommands.removeItemForGroup( ClientGroupID, SubMenuData ) - self:T( "Removed DeploySubMenu " ) - SubMenuData = nil - end - end - if MenuData.DeployMenu then - missionCommands.removeItemForGroup( ClientGroupID, MenuData.DeployMenu ) - self:T( "Removed DeployMenu " ) - MenuData.DeployMenu = nil - end - end - -end ---- A NOTASK is a dummy activity... But it will show a Mission Briefing... --- @module NOTASK - ---- The NOTASK class --- @type -NOTASK = { - ClassName = "NOTASK", -} - ---- Creates a new NOTASK. -function NOTASK:New() - local self = BASE:Inherit( self, TASK:New() ) - self:F() - - local Valid = true - - if Valid then - self.Name = 'Nothing' - self.TaskBriefing = "Task: Execute your mission." - self.Stages = { STAGEBRIEF:New(), STAGESTART:New(), STAGEDONE:New() } - self.SetStage( self, 1 ) - end - - return self -end ---- A ROUTETASK orchestrates the travel to a specific zone defined within the ME. --- @module ROUTETASK - ---- The ROUTETASK class --- @type -ROUTETASK = { - ClassName = "ROUTETASK", - GoalVerb = "Route", -} - ---- Creates a new ROUTETASK. --- @param table{sring,...}|string LandingZones Table of Zone Names where the target is located. --- @param string TaskBriefing (optional) Defines a text describing the briefing of the task. --- @return ROUTETASK -function ROUTETASK:New( LandingZones, TaskBriefing ) - local self = BASE:Inherit( self, TASK:New() ) - self:F( { LandingZones, TaskBriefing } ) - - local Valid = true - - Valid = routines.ValidateZone( LandingZones, "LandingZones", Valid ) - - if Valid then - self.Name = 'Route To Zone' - if TaskBriefing then - self.TaskBriefing = TaskBriefing .. " Your co-pilot will provide you with the directions (required flight angle in degrees) and the distance (in km) to the target objective." - else - self.TaskBriefing = "Task: Fly to specified zone(s). Your co-pilot will provide you with the directions (required flight angle in degrees) and the distance (in km) to the target objective." - end - if type( LandingZones ) == "table" then - self.LandingZones = LandingZones - else - self.LandingZones = { LandingZones } - end - self.Stages = { STAGEBRIEF:New(), STAGESTART:New(), STAGEROUTE:New(), STAGEARRIVE:New(), STAGEDONE:New() } - self.SetStage( self, 1 ) - end - - return self -end - ---- A MISSION is the main owner of a Mission orchestration within MOOSE . The Mission framework orchestrates @{CLIENT}s, @{TASK}s, @{STAGE}s etc. --- A @{CLIENT} needs to be registered within the @{MISSION} through the function @{AddClient}. A @{TASK} needs to be registered within the @{MISSION} through the function @{AddTask}. --- @module Mission - ---- The MISSION class --- @type MISSION --- @extends Base#BASE --- @field #MISSION.Clients _Clients --- @field Menu#MENU_COALITION MissionMenu --- @field #string MissionBriefing -MISSION = { - ClassName = "MISSION", - Name = "", - MissionStatus = "PENDING", - _Clients = {}, - Tasks = {}, - TaskMenus = {}, - TaskCategoryMenus = {}, - TaskTypeMenus = {}, - _ActiveTasks = {}, - GoalFunction = nil, - MissionReportTrigger = 0, - MissionProgressTrigger = 0, - MissionReportShow = false, - MissionReportFlash = false, - MissionTimeInterval = 0, - MissionCoalition = "", - SUCCESS = 1, - FAILED = 2, - REPEAT = 3, - _GoalTasks = {} -} - ---- @type MISSION.Clients --- @list - -function MISSION:Meta() - - local self = BASE:Inherit( self, BASE:New() ) - - return self -end - ---- This is the main MISSION declaration method. Each Mission is like the master or a Mission orchestration between, Clients, Tasks, Stages etc. --- @param #MISSION self --- @param #string MissionName is the name of the mission. This name will be used to reference the status of each mission by the players. --- @param #string MissionPriority is a string indicating the "priority" of the Mission. f.e. "Primary", "Secondary" or "First", "Second". It is free format and up to the Mission designer to choose. There are no rules behind this field. --- @param #string MissionBriefing is a string indicating the mission briefing to be shown when a player joins a @{CLIENT}. --- @param DCSCoalitionObject#coalition MissionCoalition is a string indicating the coalition or party to which this mission belongs to. It is free format and can be chosen freely by the mission designer. Note that this field is not to be confused with the coalition concept of the ME. Examples of a Mission Coalition could be "NATO", "CCCP", "Intruders", "Terrorists"... --- @return #MISSION self -function MISSION:New( MissionName, MissionPriority, MissionBriefing, MissionCoalition ) - - self = MISSION:Meta() - self:T( { MissionName, MissionPriority, MissionBriefing, MissionCoalition } ) - - self.Name = MissionName - self.MissionPriority = MissionPriority - self.MissionBriefing = MissionBriefing - self.MissionCoalition = MissionCoalition - - return self -end - ---- Gets the mission name. --- @param #MISSION self --- @return #MISSION self -function MISSION:GetName() - return self.Name -end - ---- Add a scoring to the mission. --- @param #MISSION self --- @return #MISSION self -function MISSION:AddScoring( Scoring ) - self.Scoring = Scoring - return self -end - ---- Get the scoring object of a mission. --- @param #MISSION self --- @return #SCORING Scoring -function MISSION:GetScoring() - return self.Scoring -end - - ---- Sets the Planned Task menu. --- @param #MISSION self -function MISSION:SetPlannedMenu() - - for _, Task in pairs( self.Tasks ) do - local Task = Task -- Task#TASK_BASE - Task:RemoveMenu() - Task:SetPlannedMenu() - end - -end - ---- Sets the Assigned Task menu. --- @param #MISSION self --- @param Task#TASK_BASE Task --- @param #string MenuText The menu text. --- @return #MISSION self -function MISSION:SetAssignedMenu( Task ) - - for _, Task in pairs( self.Tasks ) do - local Task = Task -- Task#TASK_BASE - Task:RemoveMenu() - Task:SetAssignedMenu() - end - -end - ---- Removes a Task menu. --- @param #MISSION self --- @param Task#TASK_BASE Task --- @return #MISSION self -function MISSION:RemoveTaskMenu( Task ) - - Task:RemoveMenu() -end - - ---- Gets the mission menu for the coalition. --- @param #MISSION self --- @param Group#GROUP TaskGroup --- @return Menu#MENU_COALITION self -function MISSION:GetMissionMenu( TaskGroup ) - local TaskGroupName = TaskGroup:GetName() - return self.MenuMission[TaskGroupName] -end - - ---- Clears the mission menu for the coalition. --- @param #MISSION self --- @return #MISSION self -function MISSION:ClearMissionMenu() - self.MissionMenu:Remove() - self.MissionMenu = nil -end - ---- Get the TASK identified by the TaskNumber from the Mission. This function is useful in GoalFunctions. --- @param #string TaskIndex is the Index of the @{Task} within the @{Mission}. --- @param #number TaskID is the ID of the @{Task} within the @{Mission}. --- @return Task#TASK_BASE The Task --- @return #nil Returns nil if no task was found. -function MISSION:GetTask( TaskName ) - self:F( { TaskName } ) - - return self.Tasks[TaskName] -end - - ---- Register a @{Task} to be completed within the @{Mission}. --- Note that there can be multiple @{Task}s registered to be completed. --- Each Task can be set a certain Goals. The Mission will not be completed until all Goals are reached. --- @param #MISSION self --- @param Task#TASK_BASE Task is the @{Task} object. --- @return Task#TASK_BASE The task added. -function MISSION:AddTask( Task ) - - local TaskName = Task:GetTaskName() - self:F( TaskName ) - self.Tasks[TaskName] = self.Tasks[TaskName] or { n = 0 } - - self.Tasks[TaskName] = Task - - return Task -end - ---- Removes a @{Task} to be completed within the @{Mission}. --- Note that there can be multiple @{Task}s registered to be completed. --- Each Task can be set a certain Goals. The Mission will not be completed until all Goals are reached. --- @param #MISSION self --- @param Task#TASK_BASE Task is the @{Task} object. --- @return #nil The cleaned Task reference. -function MISSION:RemoveTask( Task ) - - local TaskName = Task:GetTaskName() - self:F( TaskName ) - self.Tasks[TaskName] = self.Tasks[TaskName] or { n = 0 } - - Task:CleanUp() -- Cleans all events and sets task to nil to get Garbage Collected - - -- Ensure everything gets garbarge collected. - self.Tasks[TaskName] = nil - Task = nil - - return nil -end - ---- Return the next @{Task} ID to be completed within the @{Mission}. --- @param #MISSION self --- @param Task#TASK_BASE Task is the @{Task} object. --- @return Task#TASK_BASE The task added. -function MISSION:GetNextTaskID( Task ) - - local TaskName = Task:GetTaskName() - self:F( TaskName ) - self.Tasks[TaskName] = self.Tasks[TaskName] or { n = 0 } - - self.Tasks[TaskName].n = self.Tasks[TaskName].n + 1 - - return self.Tasks[TaskName].n -end - - - ---- old stuff - ---- Returns if a Mission has completed. --- @return bool -function MISSION:IsCompleted() - self:F() - return self.MissionStatus == "ACCOMPLISHED" -end - ---- Set a Mission to completed. -function MISSION:Completed() - self:F() - self.MissionStatus = "ACCOMPLISHED" - self:StatusToClients() -end - ---- Returns if a Mission is ongoing. --- treturn bool -function MISSION:IsOngoing() - self:F() - return self.MissionStatus == "ONGOING" -end - ---- Set a Mission to ongoing. -function MISSION:Ongoing() - self:F() - self.MissionStatus = "ONGOING" - --self:StatusToClients() -end - ---- Returns if a Mission is pending. --- treturn bool -function MISSION:IsPending() - self:F() - return self.MissionStatus == "PENDING" -end - ---- Set a Mission to pending. -function MISSION:Pending() - self:F() - self.MissionStatus = "PENDING" - self:StatusToClients() -end - ---- Returns if a Mission has failed. --- treturn bool -function MISSION:IsFailed() - self:F() - return self.MissionStatus == "FAILED" -end - ---- Set a Mission to failed. -function MISSION:Failed() - self:F() - self.MissionStatus = "FAILED" - self:StatusToClients() -end - ---- Send the status of the MISSION to all Clients. -function MISSION:StatusToClients() - self:F() - if self.MissionReportFlash then - for ClientID, Client in pairs( self._Clients ) do - Client:Message( self.MissionCoalition .. ' "' .. self.Name .. '": ' .. self.MissionStatus .. '! ( ' .. self.MissionPriority .. ' mission ) ', 10, "Mission Command: Mission Status") - end - end -end - ---- Handles the reporting. After certain time intervals, a MISSION report MESSAGE will be shown to All Players. -function MISSION:ReportTrigger() - self:F() - - if self.MissionReportShow == true then - self.MissionReportShow = false - return true - else - if self.MissionReportFlash == true then - if timer.getTime() >= self.MissionReportTrigger then - self.MissionReportTrigger = timer.getTime() + self.MissionTimeInterval - return true - else - return false - end - else - return false - end - end -end - ---- Report the status of all MISSIONs to all active Clients. -function MISSION:ReportToAll() - self:F() - - local AlivePlayers = '' - for ClientID, Client in pairs( self._Clients ) do - if Client:GetDCSGroup() then - if Client:GetClientGroupDCSUnit() then - if Client:GetClientGroupDCSUnit():getLife() > 0.0 then - if AlivePlayers == '' then - AlivePlayers = ' Players: ' .. Client:GetClientGroupDCSUnit():getPlayerName() - else - AlivePlayers = AlivePlayers .. ' / ' .. Client:GetClientGroupDCSUnit():getPlayerName() - end - end - end - end - end - local Tasks = self:GetTasks() - local TaskText = "" - for TaskID, TaskData in pairs( Tasks ) do - TaskText = TaskText .. " - Task " .. TaskID .. ": " .. TaskData.Name .. ": " .. TaskData:GetGoalProgress() .. "\n" - end - MESSAGE:New( self.MissionCoalition .. ' "' .. self.Name .. '": ' .. self.MissionStatus .. ' ( ' .. self.MissionPriority .. ' mission )' .. AlivePlayers .. "\n" .. TaskText:gsub("\n$",""), 10, "Mission Command: Mission Report" ):ToAll() -end - - ---- Add a goal function to a MISSION. Goal functions are called when a @{TASK} within a mission has been completed. --- @param function GoalFunction is the function defined by the mission designer to evaluate whether a certain goal has been reached after a @{TASK} finishes within the @{MISSION}. A GoalFunction must accept 2 parameters: Mission, Client, which contains the current MISSION object and the current CLIENT object respectively. --- @usage --- PatriotActivation = { --- { "US SAM Patriot Zerti", false }, --- { "US SAM Patriot Zegduleti", false }, --- { "US SAM Patriot Gvleti", false } --- } --- --- function DeployPatriotTroopsGoal( Mission, Client ) --- --- --- -- Check if the cargo is all deployed for mission success. --- for CargoID, CargoData in pairs( Mission._Cargos ) do --- if Group.getByName( CargoData.CargoGroupName ) then --- CargoGroup = Group.getByName( CargoData.CargoGroupName ) --- if CargoGroup then --- -- Check if the cargo is ready to activate --- CurrentLandingZoneID = routines.IsUnitInZones( CargoGroup:getUnits()[1], Mission:GetTask( 2 ).LandingZones ) -- The second task is the Deploytask to measure mission success upon --- if CurrentLandingZoneID then --- if PatriotActivation[CurrentLandingZoneID][2] == false then --- -- Now check if this is a new Mission Task to be completed... --- trigger.action.setGroupAIOn( Group.getByName( PatriotActivation[CurrentLandingZoneID][1] ) ) --- PatriotActivation[CurrentLandingZoneID][2] = true --- MessageToBlue( "Mission Command: Message to all airborne units! The " .. PatriotActivation[CurrentLandingZoneID][1] .. " is armed. Our air defenses are now stronger.", 60, "BLUE/PatriotDefense" ) --- MessageToRed( "Mission Command: Our satellite systems are detecting additional NATO air defenses. To all airborne units: Take care!!!", 60, "RED/PatriotDefense" ) --- Mission:GetTask( 2 ):AddGoalCompletion( "Patriots activated", PatriotActivation[CurrentLandingZoneID][1], 1 ) -- Register Patriot activation as part of mission goal. --- end --- end --- end --- end --- end --- end --- --- local Mission = MISSIONSCHEDULER.AddMission( 'NATO Transport Troops', 'Operational', 'Transport 3 groups of air defense engineers from our barracks "Gold" and "Titan" to each patriot battery control center to activate our air defenses.', 'NATO' ) --- Mission:AddGoalFunction( DeployPatriotTroopsGoal ) -function MISSION:AddGoalFunction( GoalFunction ) - self:F() - self.GoalFunction = GoalFunction -end - ---- Register a new @{CLIENT} to participate within the mission. --- @param CLIENT Client is the @{CLIENT} object. The object must have been instantiated with @{CLIENT:New}. --- @return CLIENT --- @usage --- Add a number of Client objects to the Mission. --- Mission:AddClient( CLIENT:FindByName( 'US UH-1H*HOT-Deploy Troops 1', 'Transport 3 groups of air defense engineers from our barracks "Gold" and "Titan" to each patriot battery control center to activate our air defenses.' ):Transport() ) --- Mission:AddClient( CLIENT:FindByName( 'US UH-1H*RAMP-Deploy Troops 3', 'Transport 3 groups of air defense engineers from our barracks "Gold" and "Titan" to each patriot battery control center to activate our air defenses.' ):Transport() ) --- Mission:AddClient( CLIENT:FindByName( 'US UH-1H*HOT-Deploy Troops 2', 'Transport 3 groups of air defense engineers from our barracks "Gold" and "Titan" to each patriot battery control center to activate our air defenses.' ):Transport() ) --- Mission:AddClient( CLIENT:FindByName( 'US UH-1H*RAMP-Deploy Troops 4', 'Transport 3 groups of air defense engineers from our barracks "Gold" and "Titan" to each patriot battery control center to activate our air defenses.' ):Transport() ) -function MISSION:AddClient( Client ) - self:F( { Client } ) - - local Valid = true - - if Valid then - self._Clients[Client.ClientName] = Client - end - - return Client -end - ---- Find a @{CLIENT} object within the @{MISSION} by its ClientName. --- @param CLIENT ClientName is a string defining the Client Group as defined within the ME. --- @return CLIENT --- @usage --- -- Seach for Client "Bomber" within the Mission. --- local BomberClient = Mission:FindClient( "Bomber" ) -function MISSION:FindClient( ClientName ) - self:F( { self._Clients[ClientName] } ) - return self._Clients[ClientName] -end - - ---- Get all the TASKs from the Mission. This function is useful in GoalFunctions. --- @return {TASK,...} Structure of TASKS with the @{TASK} number as the key. --- @usage --- -- Get Tasks from the Mission. --- Tasks = Mission:GetTasks() --- env.info( "Task 2 Completion = " .. Tasks[2]:GetGoalPercentage() .. "%" ) -function MISSION:GetTasks() - self:F() - - return self._Tasks -end - - ---[[ - _TransportExecuteStage: Defines the different stages of Transport unload/load execution. This table is internal and is used to control the validity of Transport load/unload timing. - - - _TransportExecuteStage.EXECUTING - - _TransportExecuteStage.SUCCESS - - _TransportExecuteStage.FAILED - ---]] -_TransportExecuteStage = { - NONE = 0, - EXECUTING = 1, - SUCCESS = 2, - FAILED = 3 -} - - ---- The MISSIONSCHEDULER is an OBJECT and is the main scheduler of ALL active MISSIONs registered within this scheduler. It's workings are considered internal and is automatically created when the Mission.lua file is included. --- @type MISSIONSCHEDULER --- @field #MISSIONSCHEDULER.MISSIONS Missions -MISSIONSCHEDULER = { - Missions = {}, - MissionCount = 0, - TimeIntervalCount = 0, - TimeIntervalShow = 150, - TimeSeconds = 14400, - TimeShow = 5 -} - ---- @type MISSIONSCHEDULER.MISSIONS --- @list <#MISSION> Mission - ---- This is the main MISSIONSCHEDULER Scheduler function. It is considered internal and is automatically created when the Mission.lua file is included. -function MISSIONSCHEDULER.Scheduler() - - - -- loop through the missions in the TransportTasks - for MissionName, MissionData in pairs( MISSIONSCHEDULER.Missions ) do - - local Mission = MissionData -- #MISSION - - if not Mission:IsCompleted() then - - -- This flag will monitor if for this mission, there are clients alive. If this flag is still false at the end of the loop, the mission status will be set to Pending (if not Failed or Completed). - local ClientsAlive = false - - for ClientID, ClientData in pairs( Mission._Clients ) do - - local Client = ClientData -- Client#CLIENT - - if Client:IsAlive() then - - -- There is at least one Client that is alive... So the Mission status is set to Ongoing. - ClientsAlive = true - - -- If this Client was not registered as Alive before: - -- 1. We register the Client as Alive. - -- 2. We initialize the Client Tasks and make a link to the original Mission Task. - -- 3. We initialize the Cargos. - -- 4. We flag the Mission as Ongoing. - if not Client.ClientAlive then - Client.ClientAlive = true - Client.ClientBriefingShown = false - for TaskNumber, Task in pairs( Mission._Tasks ) do - -- Note that this a deepCopy. Each client must have their own Tasks with own Stages!!! - Client._Tasks[TaskNumber] = routines.utils.deepCopy( Mission._Tasks[TaskNumber] ) - -- Each MissionTask must point to the original Mission. - Client._Tasks[TaskNumber].MissionTask = Mission._Tasks[TaskNumber] - Client._Tasks[TaskNumber].Cargos = Mission._Tasks[TaskNumber].Cargos - Client._Tasks[TaskNumber].LandingZones = Mission._Tasks[TaskNumber].LandingZones - end - - Mission:Ongoing() - end - - - -- For each Client, check for each Task the state and evolve the mission. - -- This flag will indicate if the Task of the Client is Complete. - local TaskComplete = false - - for TaskNumber, Task in pairs( Client._Tasks ) do - - if not Task.Stage then - Task:SetStage( 1 ) - end - - - local TransportTime = timer.getTime() - - if not Task:IsDone() then - - if Task:Goal() then - Task:ShowGoalProgress( Mission, Client ) - end - - --env.info( 'Scheduler: Mission = ' .. Mission.Name .. ' / Client = ' .. Client.ClientName .. ' / Task = ' .. Task.Name .. ' / Stage = ' .. Task.ActiveStage .. ' - ' .. Task.Stage.Name .. ' - ' .. Task.Stage.StageType ) - - -- Action - if Task:StageExecute() then - Task.Stage:Execute( Mission, Client, Task ) - end - - -- Wait until execution is finished - if Task.ExecuteStage == _TransportExecuteStage.EXECUTING then - Task.Stage:Executing( Mission, Client, Task ) - end - - -- Validate completion or reverse to earlier stage - if Task.Time + Task.Stage.WaitTime <= TransportTime then - Task:SetStage( Task.Stage:Validate( Mission, Client, Task ) ) - end - - if Task:IsDone() then - --env.info( 'Scheduler: Mission '.. Mission.Name .. ' Task ' .. Task.Name .. ' Stage ' .. Task.Stage.Name .. ' done. TaskComplete = ' .. string.format ( "%s", TaskComplete and "true" or "false" ) ) - TaskComplete = true -- when a task is not yet completed, a mission cannot be completed - - else - -- break only if this task is not yet done, so that future task are not yet activated. - TaskComplete = false -- when a task is not yet completed, a mission cannot be completed - --env.info( 'Scheduler: Mission "'.. Mission.Name .. '" Task "' .. Task.Name .. '" Stage "' .. Task.Stage.Name .. '" break. TaskComplete = ' .. string.format ( "%s", TaskComplete and "true" or "false" ) ) - break - end - - if TaskComplete then - - if Mission.GoalFunction ~= nil then - Mission.GoalFunction( Mission, Client ) - end - if MISSIONSCHEDULER.Scoring then - MISSIONSCHEDULER.Scoring:_AddMissionTaskScore( Client:GetClientGroupDCSUnit(), Mission.Name, 25 ) - end - --- if not Mission:IsCompleted() then --- end - end - end - end - - local MissionComplete = true - for TaskNumber, Task in pairs( Mission._Tasks ) do - if Task:Goal() then --- Task:ShowGoalProgress( Mission, Client ) - if Task:IsGoalReached() then - else - MissionComplete = false - end - else - MissionComplete = false -- If there is no goal, the mission should never be ended. The goal status will be set somewhere else. - end - end - - if MissionComplete then - Mission:Completed() - if MISSIONSCHEDULER.Scoring then - MISSIONSCHEDULER.Scoring:_AddMissionScore( Mission.Name, 100 ) - end - else - if TaskComplete then - -- Reset for new tasking of active client - Client.ClientAlive = false -- Reset the client tasks. - end - end - - - else - if Client.ClientAlive then - env.info( 'Scheduler: Client "' .. Client.ClientName .. '" is inactive.' ) - Client.ClientAlive = false - - -- This is tricky. If we sanitize Client._Tasks before sanitizing Client._Tasks[TaskNumber].MissionTask, then the original MissionTask will be sanitized, and will be lost within the garbage collector. - -- So first sanitize Client._Tasks[TaskNumber].MissionTask, after that, sanitize only the whole _Tasks structure... - --Client._Tasks[TaskNumber].MissionTask = nil - --Client._Tasks = nil - end - end - end - - -- If all Clients of this Mission are not activated, then the Mission status needs to be put back into Pending status. - -- But only if the Mission was Ongoing. In case the Mission is Completed or Failed, the Mission status may not be changed. In these cases, this will be the last run of this Mission in the Scheduler. - if ClientsAlive == false then - if Mission:IsOngoing() then - -- Mission status back to pending... - Mission:Pending() - end - end - end - - Mission:StatusToClients() - - if Mission:ReportTrigger() then - Mission:ReportToAll() - end - end - - return true -end - ---- Start the MISSIONSCHEDULER. -function MISSIONSCHEDULER.Start() - if MISSIONSCHEDULER ~= nil then - --MISSIONSCHEDULER.SchedulerId = routines.scheduleFunction( MISSIONSCHEDULER.Scheduler, { }, 0, 2 ) - MISSIONSCHEDULER.SchedulerId = SCHEDULER:New( nil, MISSIONSCHEDULER.Scheduler, { }, 0, 2 ) - end -end - ---- Stop the MISSIONSCHEDULER. -function MISSIONSCHEDULER.Stop() - if MISSIONSCHEDULER.SchedulerId then - routines.removeFunction(MISSIONSCHEDULER.SchedulerId) - MISSIONSCHEDULER.SchedulerId = nil - end -end - ---- This is the main MISSION declaration method. Each Mission is like the master or a Mission orchestration between, Clients, Tasks, Stages etc. --- @param Mission is the MISSION object instantiated by @{MISSION:New}. --- @return MISSION --- @usage --- -- Declare a mission. --- Mission = MISSION:New( 'Russia Transport Troops SA-6', --- 'Operational', --- 'Transport troops from the control center to one of the SA-6 SAM sites to activate their operation.', --- 'Russia' ) --- MISSIONSCHEDULER:AddMission( Mission ) -function MISSIONSCHEDULER.AddMission( Mission ) - MISSIONSCHEDULER.Missions[Mission.Name] = Mission - MISSIONSCHEDULER.MissionCount = MISSIONSCHEDULER.MissionCount + 1 - -- Add an overall AI Client for the AI tasks... This AI Client will facilitate the Events in the background for each Task. - --MissionAdd:AddClient( CLIENT:Register( 'AI' ) ) - - return Mission -end - ---- Remove a MISSION from the MISSIONSCHEDULER. --- @param MissionName is the name of the MISSION given at declaration using @{AddMission}. --- @usage --- -- Declare a mission. --- Mission = MISSION:New( 'Russia Transport Troops SA-6', --- 'Operational', --- 'Transport troops from the control center to one of the SA-6 SAM sites to activate their operation.', --- 'Russia' ) --- MISSIONSCHEDULER:AddMission( Mission ) --- --- -- Now remove the Mission. --- MISSIONSCHEDULER:RemoveMission( 'Russia Transport Troops SA-6' ) -function MISSIONSCHEDULER.RemoveMission( MissionName ) - MISSIONSCHEDULER.Missions[MissionName] = nil - MISSIONSCHEDULER.MissionCount = MISSIONSCHEDULER.MissionCount - 1 -end - ---- Find a MISSION within the MISSIONSCHEDULER. --- @param MissionName is the name of the MISSION given at declaration using @{AddMission}. --- @return MISSION --- @usage --- -- Declare a mission. --- Mission = MISSION:New( 'Russia Transport Troops SA-6', --- 'Operational', --- 'Transport troops from the control center to one of the SA-6 SAM sites to activate their operation.', --- 'Russia' ) --- MISSIONSCHEDULER:AddMission( Mission ) --- --- -- Now find the Mission. --- MissionFind = MISSIONSCHEDULER:FindMission( 'Russia Transport Troops SA-6' ) -function MISSIONSCHEDULER.FindMission( MissionName ) - return MISSIONSCHEDULER.Missions[MissionName] -end - --- Internal function used by the MISSIONSCHEDULER menu. -function MISSIONSCHEDULER.ReportMissionsShow( ) - for MissionName, Mission in pairs( MISSIONSCHEDULER.Missions ) do - Mission.MissionReportShow = true - Mission.MissionReportFlash = false - end -end - --- Internal function used by the MISSIONSCHEDULER menu. -function MISSIONSCHEDULER.ReportMissionsFlash( TimeInterval ) - local Count = 0 - for MissionName, Mission in pairs( MISSIONSCHEDULER.Missions ) do - Mission.MissionReportShow = false - Mission.MissionReportFlash = true - Mission.MissionReportTrigger = timer.getTime() + Count * TimeInterval - Mission.MissionTimeInterval = MISSIONSCHEDULER.MissionCount * TimeInterval - env.info( "TimeInterval = " .. Mission.MissionTimeInterval ) - Count = Count + 1 - end -end - --- Internal function used by the MISSIONSCHEDULER menu. -function MISSIONSCHEDULER.ReportMissionsHide( Prm ) - for MissionName, Mission in pairs( MISSIONSCHEDULER.Missions ) do - Mission.MissionReportShow = false - Mission.MissionReportFlash = false - end -end - ---- Enables a MENU option in the communications menu under F10 to control the status of the active missions. --- This function should be called only once when starting the MISSIONSCHEDULER. -function MISSIONSCHEDULER.ReportMenu() - local ReportMenu = SUBMENU:New( 'Status' ) - local ReportMenuShow = COMMANDMENU:New( 'Show Report Missions', ReportMenu, MISSIONSCHEDULER.ReportMissionsShow, 0 ) - local ReportMenuFlash = COMMANDMENU:New('Flash Report Missions', ReportMenu, MISSIONSCHEDULER.ReportMissionsFlash, 120 ) - local ReportMenuHide = COMMANDMENU:New( 'Hide Report Missions', ReportMenu, MISSIONSCHEDULER.ReportMissionsHide, 0 ) -end - ---- Show the remaining mission time. -function MISSIONSCHEDULER:TimeShow() - self.TimeIntervalCount = self.TimeIntervalCount + 1 - if self.TimeIntervalCount >= self.TimeTriggerShow then - local TimeMsg = string.format("%00d", ( self.TimeSeconds / 60 ) - ( timer.getTime() / 60 )) .. ' minutes left until mission reload.' - MESSAGE:New( TimeMsg, self.TimeShow, "Mission time" ):ToAll() - self.TimeIntervalCount = 0 - end -end - -function MISSIONSCHEDULER:Time( TimeSeconds, TimeIntervalShow, TimeShow ) - - self.TimeIntervalCount = 0 - self.TimeSeconds = TimeSeconds - self.TimeIntervalShow = TimeIntervalShow - self.TimeShow = TimeShow -end - ---- Adds a mission scoring to the game. -function MISSIONSCHEDULER:Scoring( Scoring ) - - self.Scoring = Scoring -end - ---- The CLEANUP class keeps an area clean of crashing or colliding airplanes. It also prevents airplanes from firing within this area. --- @module CleanUp --- @author Flightcontrol - - - - - - - ---- The CLEANUP class. --- @type CLEANUP --- @extends Base#BASE -CLEANUP = { - ClassName = "CLEANUP", - ZoneNames = {}, - TimeInterval = 300, - CleanUpList = {}, -} - ---- Creates the main object which is handling the cleaning of the debris within the given Zone Names. --- @param #CLEANUP self --- @param #table ZoneNames Is a table of zone names where the debris should be cleaned. Also a single string can be passed with one zone name. --- @param #number TimeInterval The interval in seconds when the clean activity takes place. The default is 300 seconds, thus every 5 minutes. --- @return #CLEANUP --- @usage --- -- Clean these Zones. --- CleanUpAirports = CLEANUP:New( { 'CLEAN Tbilisi', 'CLEAN Kutaisi' }, 150 ) --- or --- CleanUpTbilisi = CLEANUP:New( 'CLEAN Tbilisi', 150 ) --- CleanUpKutaisi = CLEANUP:New( 'CLEAN Kutaisi', 600 ) -function CLEANUP:New( ZoneNames, TimeInterval ) local self = BASE:Inherit( self, BASE:New() ) - self:F( { ZoneNames, TimeInterval } ) - - if type( ZoneNames ) == 'table' then - self.ZoneNames = ZoneNames - else - self.ZoneNames = { ZoneNames } - end - if TimeInterval then - self.TimeInterval = TimeInterval - end - - _EVENTDISPATCHER:OnBirth( self._OnEventBirth, self ) - - self.CleanUpScheduler = SCHEDULER:New( self, self._CleanUpScheduler, {}, 1, TimeInterval ) - - return self -end - - ---- Destroys a group from the simulator, but checks first if it is still existing! --- @param #CLEANUP self --- @param DCSGroup#Group GroupObject The object to be destroyed. --- @param #string CleanUpGroupName The groupname... -function CLEANUP:_DestroyGroup( GroupObject, CleanUpGroupName ) - self:F( { GroupObject, CleanUpGroupName } ) - - if GroupObject then -- and GroupObject:isExist() then - trigger.action.deactivateGroup(GroupObject) - self:T( { "GroupObject Destroyed", GroupObject } ) - end -end - ---- Destroys a @{DCSUnit#Unit} from the simulator, but checks first if it is still existing! --- @param #CLEANUP self --- @param DCSUnit#Unit CleanUpUnit The object to be destroyed. --- @param #string CleanUpUnitName The Unit name ... -function CLEANUP:_DestroyUnit( CleanUpUnit, CleanUpUnitName ) - self:F( { CleanUpUnit, CleanUpUnitName } ) - - if CleanUpUnit then - local CleanUpGroup = Unit.getGroup(CleanUpUnit) - -- TODO Client bug in 1.5.3 - if CleanUpGroup and CleanUpGroup:isExist() then - local CleanUpGroupUnits = CleanUpGroup:getUnits() - if #CleanUpGroupUnits == 1 then - local CleanUpGroupName = CleanUpGroup:getName() - --self:CreateEventCrash( timer.getTime(), CleanUpUnit ) - CleanUpGroup:destroy() - self:T( { "Destroyed Group:", CleanUpGroupName } ) - else - CleanUpUnit:destroy() - self:T( { "Destroyed Unit:", CleanUpUnitName } ) - end - self.CleanUpList[CleanUpUnitName] = nil -- Cleaning from the list - CleanUpUnit = nil - end - end -end - --- TODO check DCSTypes#Weapon ---- Destroys a missile from the simulator, but checks first if it is still existing! --- @param #CLEANUP self --- @param DCSTypes#Weapon MissileObject -function CLEANUP:_DestroyMissile( MissileObject ) - self:F( { MissileObject } ) - - if MissileObject and MissileObject:isExist() then - MissileObject:destroy() - self:T( "MissileObject Destroyed") - end -end - -function CLEANUP:_OnEventBirth( Event ) - self:F( { Event } ) - - self.CleanUpList[Event.IniDCSUnitName] = {} - self.CleanUpList[Event.IniDCSUnitName].CleanUpUnit = Event.IniDCSUnit - self.CleanUpList[Event.IniDCSUnitName].CleanUpGroup = Event.IniDCSGroup - self.CleanUpList[Event.IniDCSUnitName].CleanUpGroupName = Event.IniDCSGroupName - self.CleanUpList[Event.IniDCSUnitName].CleanUpUnitName = Event.IniDCSUnitName - - _EVENTDISPATCHER:OnEngineShutDownForUnit( Event.IniDCSUnitName, self._EventAddForCleanUp, self ) - _EVENTDISPATCHER:OnEngineStartUpForUnit( Event.IniDCSUnitName, self._EventAddForCleanUp, self ) - _EVENTDISPATCHER:OnHitForUnit( Event.IniDCSUnitName, self._EventAddForCleanUp, self ) - _EVENTDISPATCHER:OnPilotDeadForUnit( Event.IniDCSUnitName, self._EventCrash, self ) - _EVENTDISPATCHER:OnDeadForUnit( Event.IniDCSUnitName, self._EventCrash, self ) - _EVENTDISPATCHER:OnCrashForUnit( Event.IniDCSUnitName, self._EventCrash, self ) - _EVENTDISPATCHER:OnShotForUnit( Event.IniDCSUnitName, self._EventShot, self ) - - --self:AddEvent( world.event.S_EVENT_ENGINE_SHUTDOWN, self._EventAddForCleanUp ) - --self:AddEvent( world.event.S_EVENT_ENGINE_STARTUP, self._EventAddForCleanUp ) --- self:AddEvent( world.event.S_EVENT_HIT, self._EventAddForCleanUp ) -- , self._EventHitCleanUp ) --- self:AddEvent( world.event.S_EVENT_CRASH, self._EventCrash ) -- , self._EventHitCleanUp ) --- --self:AddEvent( world.event.S_EVENT_DEAD, self._EventCrash ) --- self:AddEvent( world.event.S_EVENT_SHOT, self._EventShot ) --- --- self:EnableEvents() - - -end - ---- Detects if a crash event occurs. --- Crashed units go into a CleanUpList for removal. --- @param #CLEANUP self --- @param DCSTypes#Event event -function CLEANUP:_EventCrash( Event ) - self:F( { Event } ) - - --TODO: This stuff is not working due to a DCS bug. Burning units cannot be destroyed. - -- self:T("before getGroup") - -- local _grp = Unit.getGroup(event.initiator)-- Identify the group that fired - -- self:T("after getGroup") - -- _grp:destroy() - -- self:T("after deactivateGroup") - -- event.initiator:destroy() - - self.CleanUpList[Event.IniDCSUnitName] = {} - self.CleanUpList[Event.IniDCSUnitName].CleanUpUnit = Event.IniDCSUnit - self.CleanUpList[Event.IniDCSUnitName].CleanUpGroup = Event.IniDCSGroup - self.CleanUpList[Event.IniDCSUnitName].CleanUpGroupName = Event.IniDCSGroupName - self.CleanUpList[Event.IniDCSUnitName].CleanUpUnitName = Event.IniDCSUnitName - -end - ---- Detects if a unit shoots a missile. --- If this occurs within one of the zones, then the weapon used must be destroyed. --- @param #CLEANUP self --- @param DCSTypes#Event event -function CLEANUP:_EventShot( Event ) - self:F( { Event } ) - - -- Test if the missile was fired within one of the CLEANUP.ZoneNames. - local CurrentLandingZoneID = 0 - CurrentLandingZoneID = routines.IsUnitInZones( Event.IniDCSUnit, self.ZoneNames ) - if ( CurrentLandingZoneID ) then - -- Okay, the missile was fired within the CLEANUP.ZoneNames, destroy the fired weapon. - --_SEADmissile:destroy() - SCHEDULER:New( self, CLEANUP._DestroyMissile, { Event.Weapon }, 0.1 ) - end -end - - ---- Detects if the Unit has an S_EVENT_HIT within the given ZoneNames. If this is the case, destroy the unit. --- @param #CLEANUP self --- @param DCSTypes#Event event -function CLEANUP:_EventHitCleanUp( Event ) - self:F( { Event } ) - - if Event.IniDCSUnit then - if routines.IsUnitInZones( Event.IniDCSUnit, self.ZoneNames ) ~= nil then - self:T( { "Life: ", Event.IniDCSUnitName, ' = ', Event.IniDCSUnit:getLife(), "/", Event.IniDCSUnit:getLife0() } ) - if Event.IniDCSUnit:getLife() < Event.IniDCSUnit:getLife0() then - self:T( "CleanUp: Destroy: " .. Event.IniDCSUnitName ) - SCHEDULER:New( self, CLEANUP._DestroyUnit, { Event.IniDCSUnit }, 0.1 ) - end - end - end - - if Event.TgtDCSUnit then - if routines.IsUnitInZones( Event.TgtDCSUnit, self.ZoneNames ) ~= nil then - self:T( { "Life: ", Event.TgtDCSUnitName, ' = ', Event.TgtDCSUnit:getLife(), "/", Event.TgtDCSUnit:getLife0() } ) - if Event.TgtDCSUnit:getLife() < Event.TgtDCSUnit:getLife0() then - self:T( "CleanUp: Destroy: " .. Event.TgtDCSUnitName ) - SCHEDULER:New( self, CLEANUP._DestroyUnit, { Event.TgtDCSUnit }, 0.1 ) - end - end - end -end - ---- Add the @{DCSUnit#Unit} to the CleanUpList for CleanUp. -function CLEANUP:_AddForCleanUp( CleanUpUnit, CleanUpUnitName ) - self:F( { CleanUpUnit, CleanUpUnitName } ) - - self.CleanUpList[CleanUpUnitName] = {} - self.CleanUpList[CleanUpUnitName].CleanUpUnit = CleanUpUnit - self.CleanUpList[CleanUpUnitName].CleanUpUnitName = CleanUpUnitName - self.CleanUpList[CleanUpUnitName].CleanUpGroup = Unit.getGroup(CleanUpUnit) - self.CleanUpList[CleanUpUnitName].CleanUpGroupName = Unit.getGroup(CleanUpUnit):getName() - self.CleanUpList[CleanUpUnitName].CleanUpTime = timer.getTime() - self.CleanUpList[CleanUpUnitName].CleanUpMoved = false - - self:T( { "CleanUp: Add to CleanUpList: ", Unit.getGroup(CleanUpUnit):getName(), CleanUpUnitName } ) - -end - ---- Detects if the Unit has an S_EVENT_ENGINE_SHUTDOWN or an S_EVENT_HIT within the given ZoneNames. If this is the case, add the Group to the CLEANUP List. --- @param #CLEANUP self --- @param DCSTypes#Event event -function CLEANUP:_EventAddForCleanUp( Event ) - - if Event.IniDCSUnit then - if self.CleanUpList[Event.IniDCSUnitName] == nil then - if routines.IsUnitInZones( Event.IniDCSUnit, self.ZoneNames ) ~= nil then - self:_AddForCleanUp( Event.IniDCSUnit, Event.IniDCSUnitName ) - end - end - end - - if Event.TgtDCSUnit then - if self.CleanUpList[Event.TgtDCSUnitName] == nil then - if routines.IsUnitInZones( Event.TgtDCSUnit, self.ZoneNames ) ~= nil then - self:_AddForCleanUp( Event.TgtDCSUnit, Event.TgtDCSUnitName ) - end - end - end - -end - -local CleanUpSurfaceTypeText = { - "LAND", - "SHALLOW_WATER", - "WATER", - "ROAD", - "RUNWAY" - } - ---- At the defined time interval, CleanUp the Groups within the CleanUpList. --- @param #CLEANUP self -function CLEANUP:_CleanUpScheduler() - self:F( { "CleanUp Scheduler" } ) - - local CleanUpCount = 0 - for CleanUpUnitName, UnitData in pairs( self.CleanUpList ) do - CleanUpCount = CleanUpCount + 1 - - self:T( { CleanUpUnitName, UnitData } ) - local CleanUpUnit = Unit.getByName(UnitData.CleanUpUnitName) - local CleanUpGroupName = UnitData.CleanUpGroupName - local CleanUpUnitName = UnitData.CleanUpUnitName - if CleanUpUnit then - self:T( { "CleanUp Scheduler", "Checking:", CleanUpUnitName } ) - if _DATABASE:GetStatusGroup( CleanUpGroupName ) ~= "ReSpawn" then - local CleanUpUnitVec3 = CleanUpUnit:getPoint() - --self:T( CleanUpUnitVec3 ) - local CleanUpUnitVec2 = {} - CleanUpUnitVec2.x = CleanUpUnitVec3.x - CleanUpUnitVec2.y = CleanUpUnitVec3.z - --self:T( CleanUpUnitVec2 ) - local CleanUpSurfaceType = land.getSurfaceType(CleanUpUnitVec2) - --self:T( CleanUpSurfaceType ) - - if CleanUpUnit and CleanUpUnit:getLife() <= CleanUpUnit:getLife0() * 0.95 then - if CleanUpSurfaceType == land.SurfaceType.RUNWAY then - if CleanUpUnit:inAir() then - local CleanUpLandHeight = land.getHeight(CleanUpUnitVec2) - local CleanUpUnitHeight = CleanUpUnitVec3.y - CleanUpLandHeight - self:T( { "CleanUp Scheduler", "Height = " .. CleanUpUnitHeight } ) - if CleanUpUnitHeight < 30 then - self:T( { "CleanUp Scheduler", "Destroy " .. CleanUpUnitName .. " because below safe height and damaged." } ) - self:_DestroyUnit(CleanUpUnit, CleanUpUnitName) - end - else - self:T( { "CleanUp Scheduler", "Destroy " .. CleanUpUnitName .. " because on runway and damaged." } ) - self:_DestroyUnit(CleanUpUnit, CleanUpUnitName) - end - end - end - -- Clean Units which are waiting for a very long time in the CleanUpZone. - if CleanUpUnit then - local CleanUpUnitVelocity = CleanUpUnit:getVelocity() - local CleanUpUnitVelocityTotal = math.abs(CleanUpUnitVelocity.x) + math.abs(CleanUpUnitVelocity.y) + math.abs(CleanUpUnitVelocity.z) - if CleanUpUnitVelocityTotal < 1 then - if UnitData.CleanUpMoved then - if UnitData.CleanUpTime + 180 <= timer.getTime() then - self:T( { "CleanUp Scheduler", "Destroy due to not moving anymore " .. CleanUpUnitName } ) - self:_DestroyUnit(CleanUpUnit, CleanUpUnitName) - end - end - else - UnitData.CleanUpTime = timer.getTime() - UnitData.CleanUpMoved = true - end - end - - else - -- Do nothing ... - self.CleanUpList[CleanUpUnitName] = nil -- Not anymore in the DCSRTE - end - else - self:T( "CleanUp: Group " .. CleanUpUnitName .. " cannot be found in DCS RTE, removing ..." ) - self.CleanUpList[CleanUpUnitName] = nil -- Not anymore in the DCSRTE - end - end - self:T(CleanUpCount) - - return true -end - ---- This module contains the SPAWN class. --- --- 1) @{Spawn#SPAWN} class, extends @{Base#BASE} --- ============================================= --- The @{#SPAWN} class allows to spawn dynamically new groups, based on pre-defined initialization settings, modifying the behaviour when groups are spawned. --- For each group to be spawned, within the mission editor, a group has to be created with the "late activation flag" set. We call this group the *"Spawn Template"* of the SPAWN object. --- A reference to this Spawn Template needs to be provided when constructing the SPAWN object, by indicating the name of the group within the mission editor in the constructor methods. --- --- Within the SPAWN object, there is an internal index that keeps track of which group from the internal group list was spawned. --- When new groups get spawned by using the SPAWN functions (see below), it will be validated whether the Limits (@{#SPAWN.Limit}) of the SPAWN object are not reached. --- When all is valid, a new group will be created by the spawning methods, and the internal index will be increased with 1. --- --- Regarding the name of new spawned groups, a _SpawnPrefix_ will be assigned for each new group created. --- If you want to have the Spawn Template name to be used as the _SpawnPrefix_ name, use the @{#SPAWN.New} constructor. --- However, when the @{#SPAWN.NewWithAlias} constructor was used, the Alias name will define the _SpawnPrefix_ name. --- Groups will follow the following naming structure when spawned at run-time: --- --- 1. Spawned groups will have the name _SpawnPrefix_#ggg, where ggg is a counter from 0 to 999. --- 2. Spawned units will have the name _SpawnPrefix_#ggg-uu, where uu is a counter from 0 to 99 for each new spawned unit belonging to the group. --- --- Some additional notes that need to be remembered: --- --- * Templates are actually groups defined within the mission editor, with the flag "Late Activation" set. As such, these groups are never used within the mission, but are used by the @{#SPAWN} module. --- * It is important to defined BEFORE you spawn new groups, a proper initialization of the SPAWN instance is done with the options you want to use. --- * When designing a mission, NEVER name groups using a "#" within the name of the group Spawn Template(s), or the SPAWN module logic won't work anymore. --- --- 1.1) SPAWN construction methods --- ------------------------------- --- Create a new SPAWN object with the @{#SPAWN.New} or the @{#SPAWN.NewWithAlias} methods: --- --- * @{#SPAWN.New}: Creates a new SPAWN object taking the name of the group that functions as the Template. --- --- It is important to understand how the SPAWN class works internally. The SPAWN object created will contain internally a list of groups that will be spawned and that are already spawned. --- The initialization functions will modify this list of groups so that when a group gets spawned, ALL information is already prepared when spawning. This is done for performance reasons. --- So in principle, the group list will contain all parameters and configurations after initialization, and when groups get actually spawned, this spawning can be done quickly and efficient. --- --- 1.2) SPAWN initialization methods --- --------------------------------- --- A spawn object will behave differently based on the usage of initialization methods: --- --- * @{#SPAWN.Limit}: Limits the amount of groups that can be alive at the same time and that can be dynamically spawned. --- * @{#SPAWN.RandomizeRoute}: Randomize the routes of spawned groups. --- * @{#SPAWN.RandomizeTemplate}: Randomize the group templates so that when a new group is spawned, a random group template is selected from one of the templates defined. --- * @{#SPAWN.Uncontrolled}: Spawn plane groups uncontrolled. --- * @{#SPAWN.Array}: Make groups visible before they are actually activated, and order these groups like a batallion in an array. --- * @{#SPAWN.InitRepeat}: Re-spawn groups when they land at the home base. Similar functions are @{#SPAWN.InitRepeatOnLanding} and @{#SPAWN.InitRepeatOnEngineShutDown}. --- --- 1.3) SPAWN spawning methods --- --------------------------- --- Groups can be spawned at different times and methods: --- --- * @{#SPAWN.Spawn}: Spawn one new group based on the last spawned index. --- * @{#SPAWN.ReSpawn}: Re-spawn a group based on a given index. --- * @{#SPAWN.SpawnScheduled}: Spawn groups at scheduled but randomized intervals. You can use @{#SPAWN.SpawnScheduleStart} and @{#SPAWN.SpawnScheduleStop} to start and stop the schedule respectively. --- * @{#SPAWN.SpawnFromUnit}: Spawn a new group taking the position of a @{UNIT}. --- * @{#SPAWN.SpawnInZone}: Spawn a new group in a @{ZONE}. --- --- Note that @{#SPAWN.Spawn} and @{#SPAWN.ReSpawn} return a @{GROUP#GROUP.New} object, that contains a reference to the DCSGroup object. --- You can use the @{GROUP} object to do further actions with the DCSGroup. --- --- 1.4) SPAWN object cleaning --- -------------------------- --- Sometimes, it will occur during a mission run-time, that ground or especially air objects get damaged, and will while being damged stop their activities, while remaining alive. --- In such cases, the SPAWN object will just sit there and wait until that group gets destroyed, but most of the time it won't, --- and it may occur that no new groups are or can be spawned as limits are reached. --- To prevent this, a @{#SPAWN.CleanUp} initialization method has been defined that will silently monitor the status of each spawned group. --- Once a group has a velocity = 0, and has been waiting for a defined interval, that group will be cleaned or removed from run-time. --- There is a catch however :-) If a damaged group has returned to an airbase within the coalition, that group will not be considered as "lost"... --- In such a case, when the inactive group is cleaned, a new group will Re-spawned automatically. --- This models AI that has succesfully returned to their airbase, to restart their combat activities. --- Check the @{#SPAWN.CleanUp} for further info. --- --- --- @module Spawn --- @author FlightControl - ---- SPAWN Class --- @type SPAWN --- @extends Base#BASE --- @field ClassName --- @field #string SpawnTemplatePrefix --- @field #string SpawnAliasPrefix --- @field #number AliveUnits --- @field #number MaxAliveUnits --- @field #number SpawnIndex --- @field #number MaxAliveGroups -SPAWN = { - ClassName = "SPAWN", - SpawnTemplatePrefix = nil, - SpawnAliasPrefix = nil, -} - - - ---- Creates the main object to spawn a GROUP defined in the DCS ME. --- @param #SPAWN self --- @param #string SpawnTemplatePrefix is the name of the Group in the ME that defines the Template. Each new group will have the name starting with SpawnTemplatePrefix. --- @return #SPAWN --- @usage --- -- NATO helicopters engaging in the battle field. --- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ) --- @usage local Plane = SPAWN:New( "Plane" ) -- Creates a new local variable that can initiate new planes with the name "Plane#ddd" using the template "Plane" as defined within the ME. -function SPAWN:New( SpawnTemplatePrefix ) - local self = BASE:Inherit( self, BASE:New() ) - self:F( { SpawnTemplatePrefix } ) - - local TemplateGroup = Group.getByName( SpawnTemplatePrefix ) - if TemplateGroup then - self.SpawnTemplatePrefix = SpawnTemplatePrefix - self.SpawnIndex = 0 - self.SpawnCount = 0 -- The internal counter of the amount of spawning the has happened since SpawnStart. - self.AliveUnits = 0 -- Contains the counter how many units are currently alive - self.SpawnIsScheduled = false -- Reflects if the spawning for this SpawnTemplatePrefix is going to be scheduled or not. - self.SpawnTemplate = self._GetTemplate( self, SpawnTemplatePrefix ) -- Contains the template structure for a Group Spawn from the Mission Editor. Note that this group must have lateActivation always on!!! - self.Repeat = false -- Don't repeat the group from Take-Off till Landing and back Take-Off by ReSpawning. - self.UnControlled = false -- When working in UnControlled mode, all planes are Spawned in UnControlled mode before the scheduler starts. - self.SpawnMaxUnitsAlive = 0 -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time. - self.SpawnMaxGroups = 0 -- The maximum amount of groups that can be spawned. - self.SpawnRandomize = false -- Sets the randomization flag of new Spawned units to false. - self.SpawnVisible = false -- Flag that indicates if all the Groups of the SpawnGroup need to be visible when Spawned. - - self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned. - else - error( "SPAWN:New: There is no group declared in the mission editor with SpawnTemplatePrefix = '" .. SpawnTemplatePrefix .. "'" ) - end - - return self -end - ---- Creates a new SPAWN instance to create new groups based on the defined template and using a new alias for each new group. --- @param #SPAWN self --- @param #string SpawnTemplatePrefix is the name of the Group in the ME that defines the Template. --- @param #string SpawnAliasPrefix is the name that will be given to the Group at runtime. --- @return #SPAWN --- @usage --- -- NATO helicopters engaging in the battle field. --- Spawn_BE_KA50 = SPAWN:NewWithAlias( 'BE KA-50@RAMP-Ground Defense', 'Helicopter Attacking a City' ) --- @usage local PlaneWithAlias = SPAWN:NewWithAlias( "Plane", "Bomber" ) -- Creates a new local variable that can instantiate new planes with the name "Bomber#ddd" using the template "Plane" as defined within the ME. -function SPAWN:NewWithAlias( SpawnTemplatePrefix, SpawnAliasPrefix ) - local self = BASE:Inherit( self, BASE:New() ) - self:F( { SpawnTemplatePrefix, SpawnAliasPrefix } ) - - local TemplateGroup = Group.getByName( SpawnTemplatePrefix ) - if TemplateGroup then - self.SpawnTemplatePrefix = SpawnTemplatePrefix - self.SpawnAliasPrefix = SpawnAliasPrefix - self.SpawnIndex = 0 - self.SpawnCount = 0 -- The internal counter of the amount of spawning the has happened since SpawnStart. - self.AliveUnits = 0 -- Contains the counter how many units are currently alive - self.SpawnIsScheduled = false -- Reflects if the spawning for this SpawnTemplatePrefix is going to be scheduled or not. - self.SpawnTemplate = self._GetTemplate( self, SpawnTemplatePrefix ) -- Contains the template structure for a Group Spawn from the Mission Editor. Note that this group must have lateActivation always on!!! - self.Repeat = false -- Don't repeat the group from Take-Off till Landing and back Take-Off by ReSpawning. - self.UnControlled = false -- When working in UnControlled mode, all planes are Spawned in UnControlled mode before the scheduler starts. - self.SpawnMaxUnitsAlive = 0 -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time. - self.SpawnMaxGroups = 0 -- The maximum amount of groups that can be spawned. - self.SpawnRandomize = false -- Sets the randomization flag of new Spawned units to false. - self.SpawnVisible = false -- Flag that indicates if all the Groups of the SpawnGroup need to be visible when Spawned. - - self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned. - else - error( "SPAWN:New: There is no group declared in the mission editor with SpawnTemplatePrefix = '" .. SpawnTemplatePrefix .. "'" ) - end - - return self -end - - ---- Limits the Maximum amount of Units that can be alive at the same time, and the maximum amount of groups that can be spawned. --- Note that this method is exceptionally important to balance the performance of the mission. Depending on the machine etc, a mission can only process a maximum amount of units. --- If the time interval must be short, but there should not be more Units or Groups alive than a maximum amount of units, then this function should be used... --- When a @{#SPAWN.New} is executed and the limit of the amount of units alive is reached, then no new spawn will happen of the group, until some of these units of the spawn object will be destroyed. --- @param #SPAWN self --- @param #number SpawnMaxUnitsAlive The maximum amount of units that can be alive at runtime. --- @param #number SpawnMaxGroups The maximum amount of groups that can be spawned. When the limit is reached, then no more actual spawns will happen of the group. --- This parameter is useful to define a maximum amount of airplanes, ground troops, helicopters, ships etc within a supply area. --- This parameter accepts the value 0, which defines that there are no maximum group limits, but there are limits on the maximum of units that can be alive at the same time. --- @return #SPAWN self --- @usage --- -- NATO helicopters engaging in the battle field. --- -- This helicopter group consists of one Unit. So, this group will SPAWN maximum 2 groups simultaneously within the DCSRTE. --- -- There will be maximum 24 groups spawned during the whole mission lifetime. --- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):Limit( 2, 24 ) -function SPAWN:Limit( SpawnMaxUnitsAlive, SpawnMaxGroups ) - self:F( { self.SpawnTemplatePrefix, SpawnMaxUnitsAlive, SpawnMaxGroups } ) - - self.SpawnMaxUnitsAlive = SpawnMaxUnitsAlive -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time. - self.SpawnMaxGroups = SpawnMaxGroups -- The maximum amount of groups that can be spawned. - - for SpawnGroupID = 1, self.SpawnMaxGroups do - self:_InitializeSpawnGroups( SpawnGroupID ) - end - - return self -end - - ---- Randomizes the defined route of the SpawnTemplatePrefix group in the ME. This is very useful to define extra variation of the behaviour of groups. --- @param #SPAWN self --- @param #number SpawnStartPoint is the waypoint where the randomization begins. --- Note that the StartPoint = 0 equaling the point where the group is spawned. --- @param #number SpawnEndPoint is the waypoint where the randomization ends counting backwards. --- This parameter is useful to avoid randomization to end at a waypoint earlier than the last waypoint on the route. --- @param #number SpawnRadius is the radius in meters in which the randomization of the new waypoints, with the original waypoint of the original template located in the middle ... --- @return #SPAWN --- @usage --- -- NATO helicopters engaging in the battle field. --- -- The KA-50 has waypoints Start point ( =0 or SP ), 1, 2, 3, 4, End point (= 5 or DP). --- -- Waypoints 2 and 3 will only be randomized. The others will remain on their original position with each new spawn of the helicopter. --- -- The randomization of waypoint 2 and 3 will take place within a radius of 2000 meters. --- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):RandomizeRoute( 2, 2, 2000 ) -function SPAWN:RandomizeRoute( SpawnStartPoint, SpawnEndPoint, SpawnRadius ) - self:F( { self.SpawnTemplatePrefix, SpawnStartPoint, SpawnEndPoint, SpawnRadius } ) - - self.SpawnRandomizeRoute = true - self.SpawnRandomizeRouteStartPoint = SpawnStartPoint - self.SpawnRandomizeRouteEndPoint = SpawnEndPoint - self.SpawnRandomizeRouteRadius = SpawnRadius - - for GroupID = 1, self.SpawnMaxGroups do - self:_RandomizeRoute( GroupID ) - end - - return self -end - - ---- This function is rather complicated to understand. But I'll try to explain. --- This function becomes useful when you need to spawn groups with random templates of groups defined within the mission editor, --- but they will all follow the same Template route and have the same prefix name. --- In other words, this method randomizes between a defined set of groups the template to be used for each new spawn of a group. --- @param #SPAWN self --- @param #string SpawnTemplatePrefixTable A table with the names of the groups defined within the mission editor, from which one will be choosen when a new group will be spawned. --- @return #SPAWN --- @usage --- -- NATO Tank Platoons invading Gori. --- -- Choose between 13 different 'US Tank Platoon' configurations for each new SPAWN the Group to be spawned for the --- -- 'US Tank Platoon Left', 'US Tank Platoon Middle' and 'US Tank Platoon Right' SpawnTemplatePrefixes. --- -- Each new SPAWN will randomize the route, with a defined time interval of 200 seconds with 40% time variation (randomization) and --- -- with a limit set of maximum 12 Units alive simulteneously and 150 Groups to be spawned during the whole mission. --- Spawn_US_Platoon = { 'US Tank Platoon 1', 'US Tank Platoon 2', 'US Tank Platoon 3', 'US Tank Platoon 4', 'US Tank Platoon 5', --- 'US Tank Platoon 6', 'US Tank Platoon 7', 'US Tank Platoon 8', 'US Tank Platoon 9', 'US Tank Platoon 10', --- 'US Tank Platoon 11', 'US Tank Platoon 12', 'US Tank Platoon 13' } --- Spawn_US_Platoon_Left = SPAWN:New( 'US Tank Platoon Left' ):Limit( 12, 150 ):Schedule( 200, 0.4 ):RandomizeTemplate( Spawn_US_Platoon ):RandomizeRoute( 3, 3, 2000 ) --- Spawn_US_Platoon_Middle = SPAWN:New( 'US Tank Platoon Middle' ):Limit( 12, 150 ):Schedule( 200, 0.4 ):RandomizeTemplate( Spawn_US_Platoon ):RandomizeRoute( 3, 3, 2000 ) --- Spawn_US_Platoon_Right = SPAWN:New( 'US Tank Platoon Right' ):Limit( 12, 150 ):Schedule( 200, 0.4 ):RandomizeTemplate( Spawn_US_Platoon ):RandomizeRoute( 3, 3, 2000 ) -function SPAWN:RandomizeTemplate( SpawnTemplatePrefixTable ) - self:F( { self.SpawnTemplatePrefix, SpawnTemplatePrefixTable } ) - - self.SpawnTemplatePrefixTable = SpawnTemplatePrefixTable - self.SpawnRandomizeTemplate = true - - for SpawnGroupID = 1, self.SpawnMaxGroups do - self:_RandomizeTemplate( SpawnGroupID ) - end - - return self -end - - - - - ---- For planes and helicopters, when these groups go home and land on their home airbases and farps, they normally would taxi to the parking spot, shut-down their engines and wait forever until the Group is removed by the runtime environment. --- This function is used to re-spawn automatically (so no extra call is needed anymore) the same group after it has landed. --- This will enable a spawned group to be re-spawned after it lands, until it is destroyed... --- Note: When the group is respawned, it will re-spawn from the original airbase where it took off. --- So ensure that the routes for groups that respawn, always return to the original airbase, or players may get confused ... --- @param #SPAWN self --- @return #SPAWN self --- @usage --- -- RU Su-34 - AI Ship Attack --- -- Re-SPAWN the Group(s) after each landing and Engine Shut-Down automatically. --- SpawnRU_SU34 = SPAWN:New( 'TF1 RU Su-34 Krymsk@AI - Attack Ships' ):Schedule( 2, 3, 1800, 0.4 ):SpawnUncontrolled():RandomizeRoute( 1, 1, 3000 ):RepeatOnEngineShutDown() -function SPAWN:InitRepeat() - self:F( { self.SpawnTemplatePrefix, self.SpawnIndex } ) - - self.Repeat = true - self.RepeatOnEngineShutDown = false - self.RepeatOnLanding = true - - return self -end - ---- Respawn group after landing. --- @param #SPAWN self --- @return #SPAWN self -function SPAWN:InitRepeatOnLanding() - self:F( { self.SpawnTemplatePrefix } ) - - self:InitRepeat() - self.RepeatOnEngineShutDown = false - self.RepeatOnLanding = true - - return self -end - - ---- Respawn after landing when its engines have shut down. --- @param #SPAWN self --- @return #SPAWN self -function SPAWN:InitRepeatOnEngineShutDown() - self:F( { self.SpawnTemplatePrefix } ) - - self:InitRepeat() - self.RepeatOnEngineShutDown = true - self.RepeatOnLanding = false - - return self -end - - ---- CleanUp groups when they are still alive, but inactive. --- When groups are still alive and have become inactive due to damage and are unable to contribute anything, then this group will be removed at defined intervals in seconds. --- @param #SPAWN self --- @param #string SpawnCleanUpInterval The interval to check for inactive groups within seconds. --- @return #SPAWN self --- @usage Spawn_Helicopter:CleanUp( 20 ) -- CleanUp the spawning of the helicopters every 20 seconds when they become inactive. -function SPAWN:CleanUp( SpawnCleanUpInterval ) - self:F( { self.SpawnTemplatePrefix, SpawnCleanUpInterval } ) - - self.SpawnCleanUpInterval = SpawnCleanUpInterval - self.SpawnCleanUpTimeStamps = {} - --self.CleanUpFunction = routines.scheduleFunction( self._SpawnCleanUpScheduler, { self }, timer.getTime() + 1, SpawnCleanUpInterval ) - self.CleanUpScheduler = SCHEDULER:New( self, self._SpawnCleanUpScheduler, {}, 1, SpawnCleanUpInterval, 0.2 ) - return self -end - - - ---- Makes the groups visible before start (like a batallion). --- The method will take the position of the group as the first position in the array. --- @param #SPAWN self --- @param #number SpawnAngle The angle in degrees how the groups and each unit of the group will be positioned. --- @param #number SpawnWidth The amount of Groups that will be positioned on the X axis. --- @param #number SpawnDeltaX The space between each Group on the X-axis. --- @param #number SpawnDeltaY The space between each Group on the Y-axis. --- @return #SPAWN self --- @usage --- -- Define an array of Groups. --- Spawn_BE_Ground = SPAWN:New( 'BE Ground' ):Limit( 2, 24 ):Visible( 90, "Diamond", 10, 100, 50 ) -function SPAWN:Array( SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY ) - self:F( { self.SpawnTemplatePrefix, SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY } ) - - self.SpawnVisible = true -- When the first Spawn executes, all the Groups need to be made visible before start. - - local SpawnX = 0 - local SpawnY = 0 - local SpawnXIndex = 0 - local SpawnYIndex = 0 - - for SpawnGroupID = 1, self.SpawnMaxGroups do - self:T( { SpawnX, SpawnY, SpawnXIndex, SpawnYIndex } ) - - self.SpawnGroups[SpawnGroupID].Visible = true - self.SpawnGroups[SpawnGroupID].Spawned = false - - SpawnXIndex = SpawnXIndex + 1 - if SpawnWidth and SpawnWidth ~= 0 then - if SpawnXIndex >= SpawnWidth then - SpawnXIndex = 0 - SpawnYIndex = SpawnYIndex + 1 - end - end - - local SpawnRootX = self.SpawnGroups[SpawnGroupID].SpawnTemplate.x - local SpawnRootY = self.SpawnGroups[SpawnGroupID].SpawnTemplate.y - - self:_TranslateRotate( SpawnGroupID, SpawnRootX, SpawnRootY, SpawnX, SpawnY, SpawnAngle ) - - self.SpawnGroups[SpawnGroupID].SpawnTemplate.lateActivation = true - self.SpawnGroups[SpawnGroupID].SpawnTemplate.visible = true - - self.SpawnGroups[SpawnGroupID].Visible = true - - _EVENTDISPATCHER:OnBirthForTemplate( self.SpawnGroups[SpawnGroupID].SpawnTemplate, self._OnBirth, self ) - _EVENTDISPATCHER:OnCrashForTemplate( self.SpawnGroups[SpawnGroupID].SpawnTemplate, self._OnDeadOrCrash, self ) - _EVENTDISPATCHER:OnDeadForTemplate( self.SpawnGroups[SpawnGroupID].SpawnTemplate, self._OnDeadOrCrash, self ) - - if self.Repeat then - _EVENTDISPATCHER:OnTakeOffForTemplate( self.SpawnGroups[SpawnGroupID].SpawnTemplate, self._OnTakeOff, self ) - _EVENTDISPATCHER:OnLandForTemplate( self.SpawnGroups[SpawnGroupID].SpawnTemplate, self._OnLand, self ) - end - if self.RepeatOnEngineShutDown then - _EVENTDISPATCHER:OnEngineShutDownForTemplate( self.SpawnGroups[SpawnGroupID].SpawnTemplate, self._OnEngineShutDown, self ) - end - - self.SpawnGroups[SpawnGroupID].Group = _DATABASE:Spawn( self.SpawnGroups[SpawnGroupID].SpawnTemplate ) - - SpawnX = SpawnXIndex * SpawnDeltaX - SpawnY = SpawnYIndex * SpawnDeltaY - end - - return self -end - - - ---- Will spawn a group based on the internal index. --- Note: Uses @{DATABASE} module defined in MOOSE. --- @param #SPAWN self --- @return Group#GROUP The group that was spawned. You can use this group for further actions. -function SPAWN:Spawn() - self:F( { self.SpawnTemplatePrefix, self.SpawnIndex, self.AliveUnits } ) - - return self:SpawnWithIndex( self.SpawnIndex + 1 ) -end - ---- Will re-spawn a group based on a given index. --- Note: Uses @{DATABASE} module defined in MOOSE. --- @param #SPAWN self --- @param #string SpawnIndex The index of the group to be spawned. --- @return Group#GROUP The group that was spawned. You can use this group for further actions. -function SPAWN:ReSpawn( SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, SpawnIndex } ) - - if not SpawnIndex then - SpawnIndex = 1 - end - --- TODO: This logic makes DCS crash and i don't know why (yet). - local SpawnGroup = self:GetGroupFromIndex( SpawnIndex ) - if SpawnGroup then - local SpawnDCSGroup = SpawnGroup:GetDCSObject() - if SpawnDCSGroup then - SpawnGroup:Destroy() - end - end - - return self:SpawnWithIndex( SpawnIndex ) -end - ---- Will spawn a group with a specified index number. --- Uses @{DATABASE} global object defined in MOOSE. --- @param #SPAWN self --- @return Group#GROUP The group that was spawned. You can use this group for further actions. -function SPAWN:SpawnWithIndex( SpawnIndex ) - self:F2( { SpawnTemplatePrefix = self.SpawnTemplatePrefix, SpawnIndex = SpawnIndex, AliveUnits = self.AliveUnits, SpawnMaxGroups = self.SpawnMaxGroups } ) - - if self:_GetSpawnIndex( SpawnIndex ) then - - if self.SpawnGroups[self.SpawnIndex].Visible then - self.SpawnGroups[self.SpawnIndex].Group:Activate() - else - _EVENTDISPATCHER:OnBirthForTemplate( self.SpawnGroups[self.SpawnIndex].SpawnTemplate, self._OnBirth, self ) - _EVENTDISPATCHER:OnCrashForTemplate( self.SpawnGroups[self.SpawnIndex].SpawnTemplate, self._OnDeadOrCrash, self ) - _EVENTDISPATCHER:OnDeadForTemplate( self.SpawnGroups[self.SpawnIndex].SpawnTemplate, self._OnDeadOrCrash, self ) - - if self.Repeat then - _EVENTDISPATCHER:OnTakeOffForTemplate( self.SpawnGroups[self.SpawnIndex].SpawnTemplate, self._OnTakeOff, self ) - _EVENTDISPATCHER:OnLandForTemplate( self.SpawnGroups[self.SpawnIndex].SpawnTemplate, self._OnLand, self ) - end - if self.RepeatOnEngineShutDown then - _EVENTDISPATCHER:OnEngineShutDownForTemplate( self.SpawnGroups[self.SpawnIndex].SpawnTemplate, self._OnEngineShutDown, self ) - end - self:T3( self.SpawnGroups[self.SpawnIndex].SpawnTemplate ) - - self.SpawnGroups[self.SpawnIndex].Group = _DATABASE:Spawn( self.SpawnGroups[self.SpawnIndex].SpawnTemplate ) - - -- If there is a SpawnFunction hook defined, call it. - if self.SpawnFunctionHook then - self.SpawnFunctionHook( self.SpawnGroups[self.SpawnIndex].Group, unpack( self.SpawnFunctionArguments ) ) - end - -- TODO: Need to fix this by putting an "R" in the name of the group when the group repeats. - --if self.Repeat then - -- _DATABASE:SetStatusGroup( SpawnTemplate.name, "ReSpawn" ) - --end - end - - self.SpawnGroups[self.SpawnIndex].Spawned = true - return self.SpawnGroups[self.SpawnIndex].Group - else - --self:E( { self.SpawnTemplatePrefix, "No more Groups to Spawn:", SpawnIndex, self.SpawnMaxGroups } ) - end - - return nil -end - ---- Spawns new groups at varying time intervals. --- This is useful if you want to have continuity within your missions of certain (AI) groups to be present (alive) within your missions. --- @param #SPAWN self --- @param #number SpawnTime The time interval defined in seconds between each new spawn of new groups. --- @param #number SpawnTimeVariation The variation to be applied on the defined time interval between each new spawn. --- The variation is a number between 0 and 1, representing the %-tage of variation to be applied on the time interval. --- @return #SPAWN self --- @usage --- -- NATO helicopters engaging in the battle field. --- -- The time interval is set to SPAWN new helicopters between each 600 seconds, with a time variation of 50%. --- -- The time variation in this case will be between 450 seconds and 750 seconds. --- -- This is calculated as follows: --- -- Low limit: 600 * ( 1 - 0.5 / 2 ) = 450 --- -- High limit: 600 * ( 1 + 0.5 / 2 ) = 750 --- -- Between these two values, a random amount of seconds will be choosen for each new spawn of the helicopters. --- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):Schedule( 600, 0.5 ) -function SPAWN:SpawnScheduled( SpawnTime, SpawnTimeVariation ) - self:F( { SpawnTime, SpawnTimeVariation } ) - - if SpawnTime ~= nil and SpawnTimeVariation ~= nil then - self.SpawnScheduler = SCHEDULER:New( self, self._Scheduler, {}, 1, SpawnTime, SpawnTimeVariation ) - end - - return self -end - ---- Will re-start the spawning scheduler. --- Note: This function is only required to be called when the schedule was stopped. -function SPAWN:SpawnScheduleStart() - self:F( { self.SpawnTemplatePrefix } ) - - self.SpawnScheduler:Start() -end - ---- Will stop the scheduled spawning scheduler. -function SPAWN:SpawnScheduleStop() - self:F( { self.SpawnTemplatePrefix } ) - - self.SpawnScheduler:Stop() -end - - ---- Allows to place a CallFunction hook when a new group spawns. --- The provided function will be called when a new group is spawned, including its given parameters. --- The first parameter of the SpawnFunction is the @{Group#GROUP} that was spawned. --- @param #SPAWN self --- @param #function SpawnFunctionHook The function to be called when a group spawns. --- @param SpawnFunctionArguments A random amount of arguments to be provided to the function when the group spawns. --- @return #SPAWN -function SPAWN:SpawnFunction( SpawnFunctionHook, ... ) - self:F( SpawnFunction ) - - self.SpawnFunctionHook = SpawnFunctionHook - self.SpawnFunctionArguments = {} - if arg then - self.SpawnFunctionArguments = arg - end - - return self -end - - - - ---- Will spawn a group from a hosting unit. This function is mostly advisable to be used if you want to simulate spawning from air units, like helicopters, which are dropping infantry into a defined Landing Zone. --- Note that each point in the route assigned to the spawning group is reset to the point of the spawn. --- You can use the returned group to further define the route to be followed. --- @param #SPAWN self --- @param Unit#UNIT HostUnit The air or ground unit dropping or unloading the group. --- @param #number OuterRadius The outer radius in meters where the new group will be spawned. --- @param #number InnerRadius The inner radius in meters where the new group will NOT be spawned. --- @param #number SpawnIndex (Optional) The index which group to spawn within the given zone. --- @return Group#GROUP that was spawned. --- @return #nil Nothing was spawned. -function SPAWN:SpawnFromUnit( HostUnit, OuterRadius, InnerRadius, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, HostUnit, OuterRadius, InnerRadius, SpawnIndex } ) - - if HostUnit and HostUnit:IsAlive() then -- and HostUnit:getUnit(1):inAir() == false then - - if SpawnIndex then - else - SpawnIndex = self.SpawnIndex + 1 - end - - if self:_GetSpawnIndex( SpawnIndex ) then - - local SpawnTemplate = self.SpawnGroups[self.SpawnIndex].SpawnTemplate - - if SpawnTemplate then - - local UnitPoint = HostUnit:GetVec2() - - self:T( { "Current point of ", self.SpawnTemplatePrefix, UnitPoint } ) - - --for PointID, Point in pairs( SpawnTemplate.route.points ) do - --Point.x = UnitPoint.x - --Point.y = UnitPoint.y - --Point.alt = nil - --Point.alt_type = nil - --end - - SpawnTemplate.route.points[1].x = UnitPoint.x - SpawnTemplate.route.points[1].y = UnitPoint.y - - if not InnerRadius then - InnerRadius = 10 - end - - if not OuterRadius then - OuterRadius = 50 - end - - -- Apply SpawnFormation - for UnitID = 1, #SpawnTemplate.units do - if InnerRadius == 0 then - SpawnTemplate.units[UnitID].x = UnitPoint.x - SpawnTemplate.units[UnitID].y = UnitPoint.y - else - local CirclePos = routines.getRandPointInCircle( UnitPoint, OuterRadius, InnerRadius ) - SpawnTemplate.units[UnitID].x = CirclePos.x - SpawnTemplate.units[UnitID].y = CirclePos.y - end - self:T( 'SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y ) - end - - local SpawnPos = routines.getRandPointInCircle( UnitPoint, OuterRadius, InnerRadius ) - local Point = {} - Point.type = "Turning Point" - Point.x = SpawnPos.x - Point.y = SpawnPos.y - Point.action = "Cone" - Point.speed = 5 - - table.insert( SpawnTemplate.route.points, 2, Point ) - - return self:SpawnWithIndex( self.SpawnIndex ) - end - end - end - - return nil -end - ---- Will spawn a Group within a given @{Zone#ZONE}. --- Once the group is spawned within the zone, it will continue on its route. --- The first waypoint (where the group is spawned) is replaced with the zone coordinates. --- @param #SPAWN self --- @param Zone#ZONE Zone The zone where the group is to be spawned. --- @param #number ZoneRandomize (Optional) Set to true if you want to randomize the starting point in the zone. --- @param #number SpawnIndex (Optional) The index which group to spawn within the given zone. --- @return Group#GROUP that was spawned. --- @return #nil when nothing was spawned. -function SPAWN:SpawnInZone( Zone, ZoneRandomize, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, Zone, ZoneRandomize, SpawnIndex } ) - - if Zone then - - if SpawnIndex then - else - SpawnIndex = self.SpawnIndex + 1 - end - - if self:_GetSpawnIndex( SpawnIndex ) then - - local SpawnTemplate = self.SpawnGroups[self.SpawnIndex].SpawnTemplate - - if SpawnTemplate then - - local ZonePoint - - if ZoneRandomize == true then - ZonePoint = Zone:GetRandomVec2() - else - ZonePoint = Zone:GetVec2() - end - - SpawnTemplate.route.points[1].x = ZonePoint.x - SpawnTemplate.route.points[1].y = ZonePoint.y - - -- Apply SpawnFormation - for UnitID = 1, #SpawnTemplate.units do - local ZonePointUnit = Zone:GetRandomVec2() - SpawnTemplate.units[UnitID].x = ZonePointUnit.x - SpawnTemplate.units[UnitID].y = ZonePointUnit.y - self:T( 'SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y ) - end - - return self:SpawnWithIndex( self.SpawnIndex ) - end - end - end - - return nil -end - - - - ---- Will spawn a plane group in uncontrolled mode... --- This will be similar to the uncontrolled flag setting in the ME. --- @return #SPAWN self -function SPAWN:UnControlled() - self:F( { self.SpawnTemplatePrefix } ) - - self.SpawnUnControlled = true - - for SpawnGroupID = 1, self.SpawnMaxGroups do - self.SpawnGroups[SpawnGroupID].UnControlled = true - end - - return self -end - - - ---- Will return the SpawnGroupName either with with a specific count number or without any count. --- @param #SPAWN self --- @param #number SpawnIndex Is the number of the Group that is to be spawned. --- @return #string SpawnGroupName -function SPAWN:SpawnGroupName( SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, SpawnIndex } ) - - local SpawnPrefix = self.SpawnTemplatePrefix - if self.SpawnAliasPrefix then - SpawnPrefix = self.SpawnAliasPrefix - end - - if SpawnIndex then - local SpawnName = string.format( '%s#%03d', SpawnPrefix, SpawnIndex ) - self:T( SpawnName ) - return SpawnName - else - self:T( SpawnPrefix ) - return SpawnPrefix - end - -end - ---- Find the first alive group. --- @param #SPAWN self --- @param #number SpawnCursor A number holding the index from where to find the first group from. --- @return Group#GROUP, #number The group found, the new index where the group was found. --- @return #nil, #nil When no group is found, #nil is returned. -function SPAWN:GetFirstAliveGroup( SpawnCursor ) - self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnCursor } ) - - for SpawnIndex = 1, self.SpawnCount do - local SpawnGroup = self:GetGroupFromIndex( SpawnIndex ) - if SpawnGroup and SpawnGroup:IsAlive() then - SpawnCursor = SpawnIndex - return SpawnGroup, SpawnCursor - end - end - - return nil, nil -end - - ---- Find the next alive group. --- @param #SPAWN self --- @param #number SpawnCursor A number holding the last found previous index. --- @return Group#GROUP, #number The group found, the new index where the group was found. --- @return #nil, #nil When no group is found, #nil is returned. -function SPAWN:GetNextAliveGroup( SpawnCursor ) - self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnCursor } ) - - SpawnCursor = SpawnCursor + 1 - for SpawnIndex = SpawnCursor, self.SpawnCount do - local SpawnGroup = self:GetGroupFromIndex( SpawnIndex ) - if SpawnGroup and SpawnGroup:IsAlive() then - SpawnCursor = SpawnIndex - return SpawnGroup, SpawnCursor - end - end - - return nil, nil -end - ---- Find the last alive group during runtime. -function SPAWN:GetLastAliveGroup() - self:F( { self.SpawnTemplatePrefixself.SpawnAliasPrefix } ) - - self.SpawnIndex = self:_GetLastIndex() - for SpawnIndex = self.SpawnIndex, 1, -1 do - local SpawnGroup = self:GetGroupFromIndex( SpawnIndex ) - if SpawnGroup and SpawnGroup:IsAlive() then - self.SpawnIndex = SpawnIndex - return SpawnGroup - end - end - - self.SpawnIndex = nil - return nil -end - - - ---- Get the group from an index. --- Returns the group from the SpawnGroups list. --- If no index is given, it will return the first group in the list. --- @param #SPAWN self --- @param #number SpawnIndex The index of the group to return. --- @return Group#GROUP self -function SPAWN:GetGroupFromIndex( SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnIndex } ) - - if not SpawnIndex then - SpawnIndex = 1 - end - - if self.SpawnGroups and self.SpawnGroups[SpawnIndex] then - local SpawnGroup = self.SpawnGroups[SpawnIndex].Group - return SpawnGroup - else - return nil - end -end - ---- Get the group index from a DCSUnit. --- The method will search for a #-mark, and will return the index behind the #-mark of the DCSUnit. --- It will return nil of no prefix was found. --- @param #SPAWN self --- @param DCSUnit#Unit DCSUnit The @{DCSUnit} to be searched. --- @return #string The prefix --- @return #nil Nothing found -function SPAWN:_GetGroupIndexFromDCSUnit( DCSUnit ) - self:F3( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, DCSUnit } ) - - local SpawnUnitName = ( DCSUnit and DCSUnit:getName() ) or nil - if SpawnUnitName then - local IndexString = string.match( SpawnUnitName, "#.*-" ):sub( 2, -2 ) - if IndexString then - local Index = tonumber( IndexString ) - return Index - end - end - - return nil -end - ---- Return the prefix of a SpawnUnit. --- The method will search for a #-mark, and will return the text before the #-mark. --- It will return nil of no prefix was found. --- @param #SPAWN self --- @param DCSUnit#UNIT DCSUnit The @{DCSUnit} to be searched. --- @return #string The prefix --- @return #nil Nothing found -function SPAWN:_GetPrefixFromDCSUnit( DCSUnit ) - self:F3( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, DCSUnit } ) - - local DCSUnitName = ( DCSUnit and DCSUnit:getName() ) or nil - if DCSUnitName then - local SpawnPrefix = string.match( DCSUnitName, ".*#" ) - if SpawnPrefix then - SpawnPrefix = SpawnPrefix:sub( 1, -2 ) - end - return SpawnPrefix - end - - return nil -end - ---- Return the group within the SpawnGroups collection with input a DCSUnit. --- @param #SPAWN self --- @param DCSUnit#Unit DCSUnit The @{DCSUnit} to be searched. --- @return Group#GROUP The Group --- @return #nil Nothing found -function SPAWN:_GetGroupFromDCSUnit( DCSUnit ) - self:F3( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, DCSUnit } ) - - local SpawnPrefix = self:_GetPrefixFromDCSUnit( DCSUnit ) - - if self.SpawnTemplatePrefix == SpawnPrefix or ( self.SpawnAliasPrefix and self.SpawnAliasPrefix == SpawnPrefix ) then - local SpawnGroupIndex = self:_GetGroupIndexFromDCSUnit( DCSUnit ) - local SpawnGroup = self.SpawnGroups[SpawnGroupIndex].Group - self:T( SpawnGroup ) - return SpawnGroup - end - - return nil -end - - ---- Get the index from a given group. --- The function will search the name of the group for a #, and will return the number behind the #-mark. -function SPAWN:GetSpawnIndexFromGroup( SpawnGroup ) - self:F3( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnGroup } ) - - local IndexString = string.match( SpawnGroup:GetName(), "#.*$" ):sub( 2 ) - local Index = tonumber( IndexString ) - - self:T3( IndexString, Index ) - return Index - -end - ---- Return the last maximum index that can be used. -function SPAWN:_GetLastIndex() - self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) - - return self.SpawnMaxGroups -end - ---- Initalize the SpawnGroups collection. -function SPAWN:_InitializeSpawnGroups( SpawnIndex ) - self:F3( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnIndex } ) - - if not self.SpawnGroups[SpawnIndex] then - self.SpawnGroups[SpawnIndex] = {} - self.SpawnGroups[SpawnIndex].Visible = false - self.SpawnGroups[SpawnIndex].Spawned = false - self.SpawnGroups[SpawnIndex].UnControlled = false - self.SpawnGroups[SpawnIndex].SpawnTime = 0 - - self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix = self.SpawnTemplatePrefix - self.SpawnGroups[SpawnIndex].SpawnTemplate = self:_Prepare( self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix, SpawnIndex ) - end - - self:_RandomizeTemplate( SpawnIndex ) - self:_RandomizeRoute( SpawnIndex ) - --self:_TranslateRotate( SpawnIndex ) - - return self.SpawnGroups[SpawnIndex] -end - - - ---- Gets the CategoryID of the Group with the given SpawnPrefix -function SPAWN:_GetGroupCategoryID( SpawnPrefix ) - local TemplateGroup = Group.getByName( SpawnPrefix ) - - if TemplateGroup then - return TemplateGroup:getCategory() - else - return nil - end -end - ---- Gets the CoalitionID of the Group with the given SpawnPrefix -function SPAWN:_GetGroupCoalitionID( SpawnPrefix ) - local TemplateGroup = Group.getByName( SpawnPrefix ) - - if TemplateGroup then - return TemplateGroup:getCoalition() - else - return nil - end -end - ---- Gets the CountryID of the Group with the given SpawnPrefix -function SPAWN:_GetGroupCountryID( SpawnPrefix ) - self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnPrefix } ) - - local TemplateGroup = Group.getByName( SpawnPrefix ) - - if TemplateGroup then - local TemplateUnits = TemplateGroup:getUnits() - return TemplateUnits[1]:getCountry() - else - return nil - end -end - ---- Gets the Group Template from the ME environment definition. --- This method used the @{DATABASE} object, which contains ALL initial and new spawned object in MOOSE. --- @param #SPAWN self --- @param #string SpawnTemplatePrefix --- @return @SPAWN self -function SPAWN:_GetTemplate( SpawnTemplatePrefix ) - self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnTemplatePrefix } ) - - local SpawnTemplate = nil - - SpawnTemplate = routines.utils.deepCopy( _DATABASE.Templates.Groups[SpawnTemplatePrefix].Template ) - - if SpawnTemplate == nil then - error( 'No Template returned for SpawnTemplatePrefix = ' .. SpawnTemplatePrefix ) - end - - SpawnTemplate.SpawnCoalitionID = self:_GetGroupCoalitionID( SpawnTemplatePrefix ) - SpawnTemplate.SpawnCategoryID = self:_GetGroupCategoryID( SpawnTemplatePrefix ) - SpawnTemplate.SpawnCountryID = self:_GetGroupCountryID( SpawnTemplatePrefix ) - - self:T3( { SpawnTemplate } ) - return SpawnTemplate -end - ---- Prepares the new Group Template. --- @param #SPAWN self --- @param #string SpawnTemplatePrefix --- @param #number SpawnIndex --- @return #SPAWN self -function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) - - local SpawnTemplate = self:_GetTemplate( SpawnTemplatePrefix ) - SpawnTemplate.name = self:SpawnGroupName( SpawnIndex ) - - SpawnTemplate.groupId = nil - --SpawnTemplate.lateActivation = false - SpawnTemplate.lateActivation = false - - if SpawnTemplate.SpawnCategoryID == Group.Category.GROUND then - self:T3( "For ground units, visible needs to be false..." ) - SpawnTemplate.visible = false - end - - if SpawnTemplate.SpawnCategoryID == Group.Category.HELICOPTER or SpawnTemplate.SpawnCategoryID == Group.Category.AIRPLANE then - SpawnTemplate.uncontrolled = false - end - - for UnitID = 1, #SpawnTemplate.units do - SpawnTemplate.units[UnitID].name = string.format( SpawnTemplate.name .. '-%02d', UnitID ) - SpawnTemplate.units[UnitID].unitId = nil - SpawnTemplate.units[UnitID].x = SpawnTemplate.route.points[1].x - SpawnTemplate.units[UnitID].y = SpawnTemplate.route.points[1].y - end - - self:T3( { "Template:", SpawnTemplate } ) - return SpawnTemplate - -end - ---- Private method randomizing the routes. --- @param #SPAWN self --- @param #number SpawnIndex The index of the group to be spawned. --- @return #SPAWN -function SPAWN:_RandomizeRoute( SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeRoute, self.SpawnRandomizeRouteStartPoint, self.SpawnRandomizeRouteEndPoint, self.SpawnRandomizeRouteRadius } ) - - if self.SpawnRandomizeRoute then - local SpawnTemplate = self.SpawnGroups[SpawnIndex].SpawnTemplate - local RouteCount = #SpawnTemplate.route.points - - for t = self.SpawnRandomizeRouteStartPoint + 1, ( RouteCount - self.SpawnRandomizeRouteEndPoint ) do - SpawnTemplate.route.points[t].x = SpawnTemplate.route.points[t].x + math.random( self.SpawnRandomizeRouteRadius * -1, self.SpawnRandomizeRouteRadius ) - SpawnTemplate.route.points[t].y = SpawnTemplate.route.points[t].y + math.random( self.SpawnRandomizeRouteRadius * -1, self.SpawnRandomizeRouteRadius ) - -- TODO: manage altitude for airborne units ... - SpawnTemplate.route.points[t].alt = nil - --SpawnGroup.route.points[t].alt_type = nil - self:T( 'SpawnTemplate.route.points[' .. t .. '].x = ' .. SpawnTemplate.route.points[t].x .. ', SpawnTemplate.route.points[' .. t .. '].y = ' .. SpawnTemplate.route.points[t].y ) - end - end - - return self -end - ---- Private method that randomizes the template of the group. --- @param #SPAWN self --- @param #number SpawnIndex --- @return #SPAWN self -function SPAWN:_RandomizeTemplate( SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeTemplate } ) - - if self.SpawnRandomizeTemplate then - self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix = self.SpawnTemplatePrefixTable[ math.random( 1, #self.SpawnTemplatePrefixTable ) ] - self.SpawnGroups[SpawnIndex].SpawnTemplate = self:_Prepare( self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix, SpawnIndex ) - self.SpawnGroups[SpawnIndex].SpawnTemplate.route = routines.utils.deepCopy( self.SpawnTemplate.route ) - self.SpawnGroups[SpawnIndex].SpawnTemplate.x = self.SpawnTemplate.x - self.SpawnGroups[SpawnIndex].SpawnTemplate.y = self.SpawnTemplate.y - self.SpawnGroups[SpawnIndex].SpawnTemplate.start_time = self.SpawnTemplate.start_time - for UnitID = 1, #self.SpawnGroups[SpawnIndex].SpawnTemplate.units do - self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].heading = self.SpawnTemplate.units[1].heading - end - end - - self:_RandomizeRoute( SpawnIndex ) - - return self -end - -function SPAWN:_TranslateRotate( SpawnIndex, SpawnRootX, SpawnRootY, SpawnX, SpawnY, SpawnAngle ) - self:F( { self.SpawnTemplatePrefix, SpawnIndex, SpawnRootX, SpawnRootY, SpawnX, SpawnY, SpawnAngle } ) - - -- Translate - local TranslatedX = SpawnX - local TranslatedY = SpawnY - - -- Rotate - -- From Wikipedia: https://en.wikipedia.org/wiki/Rotation_matrix#Common_rotations - -- x' = x \cos \theta - y \sin \theta\ - -- y' = x \sin \theta + y \cos \theta\ - local RotatedX = - TranslatedX * math.cos( math.rad( SpawnAngle ) ) - + TranslatedY * math.sin( math.rad( SpawnAngle ) ) - local RotatedY = TranslatedX * math.sin( math.rad( SpawnAngle ) ) - + TranslatedY * math.cos( math.rad( SpawnAngle ) ) - - -- Assign - self.SpawnGroups[SpawnIndex].SpawnTemplate.x = SpawnRootX - RotatedX - self.SpawnGroups[SpawnIndex].SpawnTemplate.y = SpawnRootY + RotatedY - - - local SpawnUnitCount = table.getn( self.SpawnGroups[SpawnIndex].SpawnTemplate.units ) - for u = 1, SpawnUnitCount do - - -- Translate - local TranslatedX = SpawnX - local TranslatedY = SpawnY - 10 * ( u - 1 ) - - -- Rotate - local RotatedX = - TranslatedX * math.cos( math.rad( SpawnAngle ) ) - + TranslatedY * math.sin( math.rad( SpawnAngle ) ) - local RotatedY = TranslatedX * math.sin( math.rad( SpawnAngle ) ) - + TranslatedY * math.cos( math.rad( SpawnAngle ) ) - - -- Assign - self.SpawnGroups[SpawnIndex].SpawnTemplate.units[u].x = SpawnRootX - RotatedX - self.SpawnGroups[SpawnIndex].SpawnTemplate.units[u].y = SpawnRootY + RotatedY - self.SpawnGroups[SpawnIndex].SpawnTemplate.units[u].heading = self.SpawnGroups[SpawnIndex].SpawnTemplate.units[u].heading + math.rad( SpawnAngle ) - end - - return self -end - ---- Get the next index of the groups to be spawned. This function is complicated, as it is used at several spaces. -function SPAWN:_GetSpawnIndex( SpawnIndex ) - self:F2( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnMaxGroups, self.SpawnMaxUnitsAlive, self.AliveUnits, #self.SpawnTemplate.units } ) - - if ( self.SpawnMaxGroups == 0 ) or ( SpawnIndex <= self.SpawnMaxGroups ) then - if ( self.SpawnMaxUnitsAlive == 0 ) or ( self.AliveUnits + #self.SpawnTemplate.units <= self.SpawnMaxUnitsAlive ) or self.UnControlled == true then - if SpawnIndex and SpawnIndex >= self.SpawnCount + 1 then - self.SpawnCount = self.SpawnCount + 1 - SpawnIndex = self.SpawnCount - end - self.SpawnIndex = SpawnIndex - if not self.SpawnGroups[self.SpawnIndex] then - self:_InitializeSpawnGroups( self.SpawnIndex ) - end - else - return nil - end - else - return nil - end - - return self.SpawnIndex -end - - --- TODO Need to delete this... _DATABASE does this now ... - ---- @param #SPAWN self --- @param Event#EVENTDATA Event -function SPAWN:_OnBirth( Event ) - - if timer.getTime0() < timer.getAbsTime() then - if Event.IniDCSUnit then - local EventPrefix = self:_GetPrefixFromDCSUnit( Event.IniDCSUnit ) - self:T( { "Birth Event:", EventPrefix, self.SpawnTemplatePrefix } ) - if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) then - self.AliveUnits = self.AliveUnits + 1 - self:T( "Alive Units: " .. self.AliveUnits ) - end - end - end - -end - ---- Obscolete --- @todo Need to delete this... _DATABASE does this now ... - ---- @param #SPAWN self --- @param Event#EVENTDATA Event -function SPAWN:_OnDeadOrCrash( Event ) - self:F( self.SpawnTemplatePrefix, Event ) - - if Event.IniDCSUnit then - local EventPrefix = self:_GetPrefixFromDCSUnit( Event.IniDCSUnit ) - self:T( { "Dead event: " .. EventPrefix, self.SpawnTemplatePrefix } ) - if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) then - self.AliveUnits = self.AliveUnits - 1 - self:T( "Alive Units: " .. self.AliveUnits ) - end - end -end - ---- Will detect AIR Units taking off... When the event takes place, the spawned Group is registered as airborne... --- This is needed to ensure that Re-SPAWNing only is done for landed AIR Groups. --- @todo Need to test for AIR Groups only... -function SPAWN:_OnTakeOff( event ) - self:F( self.SpawnTemplatePrefix, event ) - - if event.initiator and event.initiator:getName() then - local SpawnGroup = self:_GetGroupFromDCSUnit( event.initiator ) - if SpawnGroup then - self:T( { "TakeOff event: " .. event.initiator:getName(), event } ) - self:T( "self.Landed = false" ) - self.Landed = false - end - end -end - ---- Will detect AIR Units landing... When the event takes place, the spawned Group is registered as landed. --- This is needed to ensure that Re-SPAWNing is only done for landed AIR Groups. --- @todo Need to test for AIR Groups only... -function SPAWN:_OnLand( event ) - self:F( self.SpawnTemplatePrefix, event ) - - local SpawnUnit = event.initiator - if SpawnUnit and SpawnUnit:isExist() and Object.getCategory(SpawnUnit) == Object.Category.UNIT then - local SpawnGroup = self:_GetGroupFromDCSUnit( SpawnUnit ) - if SpawnGroup then - self:T( { "Landed event:" .. SpawnUnit:getName(), event } ) - self.Landed = true - self:T( "self.Landed = true" ) - if self.Landed and self.RepeatOnLanding then - local SpawnGroupIndex = self:GetSpawnIndexFromGroup( SpawnGroup ) - self:T( { "Landed:", "ReSpawn:", SpawnGroup:GetName(), SpawnGroupIndex } ) - self:ReSpawn( SpawnGroupIndex ) - end - end - end -end - ---- Will detect AIR Units shutting down their engines ... --- When the event takes place, and the method @{RepeatOnEngineShutDown} was called, the spawned Group will Re-SPAWN. --- But only when the Unit was registered to have landed. --- @param #SPAWN self --- @see _OnTakeOff --- @see _OnLand --- @todo Need to test for AIR Groups only... -function SPAWN:_OnEngineShutDown( event ) - self:F( self.SpawnTemplatePrefix, event ) - - local SpawnUnit = event.initiator - if SpawnUnit and SpawnUnit:isExist() and Object.getCategory(SpawnUnit) == Object.Category.UNIT then - local SpawnGroup = self:_GetGroupFromDCSUnit( SpawnUnit ) - if SpawnGroup then - self:T( { "EngineShutDown event: " .. SpawnUnit:getName(), event } ) - if self.Landed and self.RepeatOnEngineShutDown then - local SpawnGroupIndex = self:GetSpawnIndexFromGroup( SpawnGroup ) - self:T( { "EngineShutDown: ", "ReSpawn:", SpawnGroup:GetName(), SpawnGroupIndex } ) - self:ReSpawn( SpawnGroupIndex ) - end - end - end -end - ---- This function is called automatically by the Spawning scheduler. --- It is the internal worker method SPAWNing new Groups on the defined time intervals. -function SPAWN:_Scheduler() - self:F2( { "_Scheduler", self.SpawnTemplatePrefix, self.SpawnAliasPrefix, self.SpawnIndex, self.SpawnMaxGroups, self.SpawnMaxUnitsAlive } ) - - -- Validate if there are still groups left in the batch... - self:Spawn() - - return true -end - -function SPAWN:_SpawnCleanUpScheduler() - self:F( { "CleanUp Scheduler:", self.SpawnTemplatePrefix } ) - - local SpawnCursor - local SpawnGroup, SpawnCursor = self:GetFirstAliveGroup( SpawnCursor ) - - self:T( { "CleanUp Scheduler:", SpawnGroup } ) - - while SpawnGroup do - - if SpawnGroup:AllOnGround() and SpawnGroup:GetMaxVelocity() < 1 then - if not self.SpawnCleanUpTimeStamps[SpawnGroup:GetName()] then - self.SpawnCleanUpTimeStamps[SpawnGroup:GetName()] = timer.getTime() - else - if self.SpawnCleanUpTimeStamps[SpawnGroup:GetName()] + self.SpawnCleanUpInterval < timer.getTime() then - self:T( { "CleanUp Scheduler:", "Cleaning:", SpawnGroup } ) - SpawnGroup:Destroy() - end - end - else - self.SpawnCleanUpTimeStamps[SpawnGroup:GetName()] = nil - end - - SpawnGroup, SpawnCursor = self:GetNextAliveGroup( SpawnCursor ) - - self:T( { "CleanUp Scheduler:", SpawnGroup } ) - - end - - return true -- Repeat - -end ---- Limit the simultaneous movement of Groups within a running Mission. --- This module is defined to improve the performance in missions, and to bring additional realism for GROUND vehicles. --- Performance: If in a DCSRTE there are a lot of moving GROUND units, then in a multi player mission, this WILL create lag if --- the main DCS execution core of your CPU is fully utilized. So, this class will limit the amount of simultaneous moving GROUND units --- on defined intervals (currently every minute). --- @module MOVEMENT - ---- the MOVEMENT class --- @type -MOVEMENT = { - ClassName = "MOVEMENT", -} - ---- Creates the main object which is handling the GROUND forces movement. --- @param table{string,...}|string MovePrefixes is a table of the Prefixes (names) of the GROUND Groups that need to be controlled by the MOVEMENT Object. --- @param number MoveMaximum is a number that defines the maximum amount of GROUND Units to be moving during one minute. --- @return MOVEMENT --- @usage --- -- Limit the amount of simultaneous moving units on the ground to prevent lag. --- Movement_US_Platoons = MOVEMENT:New( { 'US Tank Platoon Left', 'US Tank Platoon Middle', 'US Tank Platoon Right', 'US CH-47D Troops' }, 15 ) - -function MOVEMENT:New( MovePrefixes, MoveMaximum ) - local self = BASE:Inherit( self, BASE:New() ) - self:F( { MovePrefixes, MoveMaximum } ) - - if type( MovePrefixes ) == 'table' then - self.MovePrefixes = MovePrefixes - else - self.MovePrefixes = { MovePrefixes } - end - self.MoveCount = 0 -- The internal counter of the amount of Moveing the has happened since MoveStart. - self.MoveMaximum = MoveMaximum -- Contains the Maximum amount of units that are allowed to move... - self.AliveUnits = 0 -- Contains the counter how many units are currently alive - self.MoveUnits = {} -- Reflects if the Moving for this MovePrefixes is going to be scheduled or not. - - _EVENTDISPATCHER:OnBirth( self.OnBirth, self ) - --- self:AddEvent( world.event.S_EVENT_BIRTH, self.OnBirth ) --- --- self:EnableEvents() - - self:ScheduleStart() - - return self -end - ---- Call this function to start the MOVEMENT scheduling. -function MOVEMENT:ScheduleStart() - self:F() - --self.MoveFunction = routines.scheduleFunction( self._Scheduler, { self }, timer.getTime() + 1, 120 ) - self.MoveFunction = SCHEDULER:New( self, self._Scheduler, {}, 1, 120 ) -end - ---- Call this function to stop the MOVEMENT scheduling. --- @todo need to implement it ... Forgot. -function MOVEMENT:ScheduleStop() - self:F() - -end - ---- Captures the birth events when new Units were spawned. --- @todo This method should become obsolete. The new @{DATABASE} class will handle the collection administration. -function MOVEMENT:OnBirth( Event ) - self:F( { Event } ) - - if timer.getTime0() < timer.getAbsTime() then -- dont need to add units spawned in at the start of the mission if mist is loaded in init line - if Event.IniDCSUnit then - self:T( "Birth object : " .. Event.IniDCSUnitName ) - if Event.IniDCSGroup and Event.IniDCSGroup:isExist() then - for MovePrefixID, MovePrefix in pairs( self.MovePrefixes ) do - if string.find( Event.IniDCSUnitName, MovePrefix, 1, true ) then - self.AliveUnits = self.AliveUnits + 1 - self.MoveUnits[Event.IniDCSUnitName] = Event.IniDCSGroupName - self:T( self.AliveUnits ) - end - end - end - end - _EVENTDISPATCHER:OnCrashForUnit( Event.IniDCSUnitName, self.OnDeadOrCrash, self ) - _EVENTDISPATCHER:OnDeadForUnit( Event.IniDCSUnitName, self.OnDeadOrCrash, self ) - end - -end - ---- Captures the Dead or Crash events when Units crash or are destroyed. --- @todo This method should become obsolete. The new @{DATABASE} class will handle the collection administration. -function MOVEMENT:OnDeadOrCrash( Event ) - self:F( { Event } ) - - if Event.IniDCSUnit then - self:T( "Dead object : " .. Event.IniDCSUnitName ) - for MovePrefixID, MovePrefix in pairs( self.MovePrefixes ) do - if string.find( Event.IniDCSUnitName, MovePrefix, 1, true ) then - self.AliveUnits = self.AliveUnits - 1 - self.MoveUnits[Event.IniDCSUnitName] = nil - self:T( self.AliveUnits ) - end - end - end -end - ---- This function is called automatically by the MOVEMENT scheduler. A new function is scheduled when MoveScheduled is true. -function MOVEMENT:_Scheduler() - self:F( { self.MovePrefixes, self.MoveMaximum, self.AliveUnits, self.MovementGroups } ) - - if self.AliveUnits > 0 then - local MoveProbability = ( self.MoveMaximum * 100 ) / self.AliveUnits - self:T( 'Move Probability = ' .. MoveProbability ) - - for MovementUnitName, MovementGroupName in pairs( self.MoveUnits ) do - local MovementGroup = Group.getByName( MovementGroupName ) - if MovementGroup and MovementGroup:isExist() then - local MoveOrStop = math.random( 1, 100 ) - self:T( 'MoveOrStop = ' .. MoveOrStop ) - if MoveOrStop <= MoveProbability then - self:T( 'Group continues moving = ' .. MovementGroupName ) - trigger.action.groupContinueMoving( MovementGroup ) - else - self:T( 'Group stops moving = ' .. MovementGroupName ) - trigger.action.groupStopMoving( MovementGroup ) - end - else - self.MoveUnits[MovementUnitName] = nil - end - end - end - return true -end ---- Provides defensive behaviour to a set of SAM sites within a running Mission. --- @module Sead --- @author to be searched on the forum --- @author (co) Flightcontrol (Modified and enriched with functionality) - ---- The SEAD class --- @type SEAD --- @extends Base#BASE -SEAD = { - ClassName = "SEAD", - TargetSkill = { - Average = { Evade = 50, DelayOff = { 10, 25 }, DelayOn = { 10, 30 } } , - Good = { Evade = 30, DelayOff = { 8, 20 }, DelayOn = { 20, 40 } } , - High = { Evade = 15, DelayOff = { 5, 17 }, DelayOn = { 30, 50 } } , - Excellent = { Evade = 10, DelayOff = { 3, 10 }, DelayOn = { 30, 60 } } - }, - SEADGroupPrefixes = {} -} - ---- Creates the main object which is handling defensive actions for SA sites or moving SA vehicles. --- When an anti radiation missile is fired (KH-58, KH-31P, KH-31A, KH-25MPU, HARM missiles), the SA will shut down their radars and will take evasive actions... --- Chances are big that the missile will miss. --- @param table{string,...}|string SEADGroupPrefixes which is a table of Prefixes of the SA Groups in the DCSRTE on which evasive actions need to be taken. --- @return SEAD --- @usage --- -- CCCP SEAD Defenses --- -- Defends the Russian SA installations from SEAD attacks. --- SEAD_RU_SAM_Defenses = SEAD:New( { 'RU SA-6 Kub', 'RU SA-6 Defenses', 'RU MI-26 Troops', 'RU Attack Gori' } ) -function SEAD:New( SEADGroupPrefixes ) - local self = BASE:Inherit( self, BASE:New() ) - self:F( SEADGroupPrefixes ) - if type( SEADGroupPrefixes ) == 'table' then - for SEADGroupPrefixID, SEADGroupPrefix in pairs( SEADGroupPrefixes ) do - self.SEADGroupPrefixes[SEADGroupPrefix] = SEADGroupPrefix - end - else - self.SEADGroupNames[SEADGroupPrefixes] = SEADGroupPrefixes - end - _EVENTDISPATCHER:OnShot( self.EventShot, self ) - - return self -end - ---- Detects if an SA site was shot with an anti radiation missile. In this case, take evasive actions based on the skill level set within the ME. --- @see SEAD -function SEAD:EventShot( Event ) - self:F( { Event } ) - - local SEADUnit = Event.IniDCSUnit - local SEADUnitName = Event.IniDCSUnitName - local SEADWeapon = Event.Weapon -- Identify the weapon fired - local SEADWeaponName = Event.WeaponName -- return weapon type - -- Start of the 2nd loop - self:T( "Missile Launched = " .. SEADWeaponName ) - if SEADWeaponName == "KH-58" or SEADWeaponName == "KH-25MPU" or SEADWeaponName == "AGM-88" or SEADWeaponName == "KH-31A" or SEADWeaponName == "KH-31P" then -- Check if the missile is a SEAD - local _evade = math.random (1,100) -- random number for chance of evading action - local _targetMim = Event.Weapon:getTarget() -- Identify target - local _targetMimname = Unit.getName(_targetMim) - local _targetMimgroup = Unit.getGroup(Weapon.getTarget(SEADWeapon)) - local _targetMimgroupName = _targetMimgroup:getName() - local _targetMimcont= _targetMimgroup:getController() - local _targetskill = _DATABASE.Templates.Units[_targetMimname].Template.skill - self:T( self.SEADGroupPrefixes ) - self:T( _targetMimgroupName ) - local SEADGroupFound = false - for SEADGroupPrefixID, SEADGroupPrefix in pairs( self.SEADGroupPrefixes ) do - if string.find( _targetMimgroupName, SEADGroupPrefix, 1, true ) then - SEADGroupFound = true - self:T( 'Group Found' ) - break - end - end - if SEADGroupFound == true then - if _targetskill == "Random" then -- when skill is random, choose a skill - local Skills = { "Average", "Good", "High", "Excellent" } - _targetskill = Skills[ math.random(1,4) ] - end - self:T( _targetskill ) - if self.TargetSkill[_targetskill] then - if (_evade > self.TargetSkill[_targetskill].Evade) then - self:T( string.format("Evading, target skill " ..string.format(_targetskill)) ) - local _targetMim = Weapon.getTarget(SEADWeapon) - local _targetMimname = Unit.getName(_targetMim) - local _targetMimgroup = Unit.getGroup(Weapon.getTarget(SEADWeapon)) - local _targetMimcont= _targetMimgroup:getController() - routines.groupRandomDistSelf(_targetMimgroup,300,'Diamond',250,20) -- move randomly - local SuppressedGroups1 = {} -- unit suppressed radar off for a random time - local function SuppressionEnd1(id) - id.ctrl:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN) - SuppressedGroups1[id.groupName] = nil - end - local id = { - groupName = _targetMimgroup, - ctrl = _targetMimcont - } - local delay1 = math.random(self.TargetSkill[_targetskill].DelayOff[1], self.TargetSkill[_targetskill].DelayOff[2]) - if SuppressedGroups1[id.groupName] == nil then - SuppressedGroups1[id.groupName] = { - SuppressionEndTime1 = timer.getTime() + delay1, - SuppressionEndN1 = SuppressionEndCounter1 --Store instance of SuppressionEnd() scheduled function - } - Controller.setOption(_targetMimcont, AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN) - timer.scheduleFunction(SuppressionEnd1, id, SuppressedGroups1[id.groupName].SuppressionEndTime1) --Schedule the SuppressionEnd() function - --trigger.action.outText( string.format("Radar Off " ..string.format(delay1)), 20) - end - - local SuppressedGroups = {} - local function SuppressionEnd(id) - id.ctrl:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.RED) - SuppressedGroups[id.groupName] = nil - end - local id = { - groupName = _targetMimgroup, - ctrl = _targetMimcont - } - local delay = math.random(self.TargetSkill[_targetskill].DelayOn[1], self.TargetSkill[_targetskill].DelayOn[2]) - if SuppressedGroups[id.groupName] == nil then - SuppressedGroups[id.groupName] = { - SuppressionEndTime = timer.getTime() + delay, - SuppressionEndN = SuppressionEndCounter --Store instance of SuppressionEnd() scheduled function - } - timer.scheduleFunction(SuppressionEnd, id, SuppressedGroups[id.groupName].SuppressionEndTime) --Schedule the SuppressionEnd() function - --trigger.action.outText( string.format("Radar On " ..string.format(delay)), 20) - end - end - end - end - end -end ---- Taking the lead of AI escorting your flight. --- --- @{#ESCORT} class --- ================ --- The @{#ESCORT} class allows you to interact with escorting AI on your flight and take the lead. --- Each escorting group can be commanded with a whole set of radio commands (radio menu in your flight, and then F10). --- --- The radio commands will vary according the category of the group. The richest set of commands are with Helicopters and AirPlanes. --- Ships and Ground troops will have a more limited set, but they can provide support through the bombing of targets designated by the other escorts. --- --- RADIO MENUs that can be created: --- ================================ --- Find a summary below of the current available commands: --- --- Navigation ...: --- --------------- --- Escort group navigation functions: --- --- * **"Join-Up and Follow at x meters":** The escort group fill follow you at about x meters, and they will follow you. --- * **"Flare":** Provides menu commands to let the escort group shoot a flare in the air in a color. --- * **"Smoke":** Provides menu commands to let the escort group smoke the air in a color. Note that smoking is only available for ground and naval troops. --- --- Hold position ...: --- ------------------ --- Escort group navigation functions: --- --- * **"At current location":** Stops the escort group and they will hover 30 meters above the ground at the position they stopped. --- * **"At client location":** Stops the escort group and they will hover 30 meters above the ground at the position they stopped. --- --- Report targets ...: --- ------------------- --- Report targets will make the escort group to report any target that it identifies within a 8km range. Any detected target can be attacked using the 4. Attack nearby targets function. (see below). --- --- * **"Report now":** Will report the current detected targets. --- * **"Report targets on":** Will make the escort group to report detected targets and will fill the "Attack nearby targets" menu list. --- * **"Report targets off":** Will stop detecting targets. --- --- Scan targets ...: --- ----------------- --- Menu items to pop-up the escort group for target scanning. After scanning, the escort group will resume with the mission or defined task. --- --- * **"Scan targets 30 seconds":** Scan 30 seconds for targets. --- * **"Scan targets 60 seconds":** Scan 60 seconds for targets. --- --- Attack targets ...: --- ------------------- --- This menu item will list all detected targets within a 15km range. Depending on the level of detection (known/unknown) and visuality, the targets type will also be listed. --- --- Request assistance from ...: --- ---------------------------- --- This menu item will list all detected targets within a 15km range, as with the menu item **Attack Targets**. --- This menu item allows to request attack support from other escorts supporting the current client group. --- eg. the function allows a player to request support from the Ship escort to attack a target identified by the Plane escort with its Tomahawk missiles. --- eg. the function allows a player to request support from other Planes escorting to bomb the unit with illumination missiles or bombs, so that the main plane escort can attack the area. --- --- ROE ...: --- -------- --- Sets the Rules of Engagement (ROE) of the escort group when in flight. --- --- * **"Hold Fire":** The escort group will hold fire. --- * **"Return Fire":** The escort group will return fire. --- * **"Open Fire":** The escort group will open fire on designated targets. --- * **"Weapon Free":** The escort group will engage with any target. --- --- Evasion ...: --- ------------ --- Will define the evasion techniques that the escort group will perform during flight or combat. --- --- * **"Fight until death":** The escort group will have no reaction to threats. --- * **"Use flares, chaff and jammers":** The escort group will use passive defense using flares and jammers. No evasive manoeuvres are executed. --- * **"Evade enemy fire":** The rescort group will evade enemy fire before firing. --- * **"Go below radar and evade fire":** The escort group will perform evasive vertical manoeuvres. --- --- Resume Mission ...: --- ------------------- --- Escort groups can have their own mission. This menu item will allow the escort group to resume their Mission from a given waypoint. --- Note that this is really fantastic, as you now have the dynamic of taking control of the escort groups, and allowing them to resume their path or mission. --- --- ESCORT construction methods. --- ============================ --- Create a new SPAWN object with the @{#ESCORT.New} method: --- --- * @{#ESCORT.New}: Creates a new ESCORT object from a @{Group#GROUP} for a @{Client#CLIENT}, with an optional briefing text. --- --- ESCORT initialization methods. --- ============================== --- The following menus are created within the RADIO MENU of an active unit hosted by a player: --- --- * @{#ESCORT.MenuFollowAt}: Creates a menu to make the escort follow the client. --- * @{#ESCORT.MenuHoldAtEscortPosition}: Creates a menu to hold the escort at its current position. --- * @{#ESCORT.MenuHoldAtLeaderPosition}: Creates a menu to hold the escort at the client position. --- * @{#ESCORT.MenuScanForTargets}: Creates a menu so that the escort scans targets. --- * @{#ESCORT.MenuFlare}: Creates a menu to disperse flares. --- * @{#ESCORT.MenuSmoke}: Creates a menu to disparse smoke. --- * @{#ESCORT.MenuReportTargets}: Creates a menu so that the escort reports targets. --- * @{#ESCORT.MenuReportPosition}: Creates a menu so that the escort reports its current position from bullseye. --- * @{#ESCORT.MenuAssistedAttack: Creates a menu so that the escort supportes assisted attack from other escorts with the client. --- * @{#ESCORT.MenuROE: Creates a menu structure to set the rules of engagement of the escort. --- * @{#ESCORT.MenuEvasion: Creates a menu structure to set the evasion techniques when the escort is under threat. --- * @{#ESCORT.MenuResumeMission}: Creates a menu structure so that the escort can resume from a waypoint. --- --- --- @usage --- -- Declare a new EscortPlanes object as follows: --- --- -- First find the GROUP object and the CLIENT object. --- local EscortClient = CLIENT:FindByName( "Unit Name" ) -- The Unit Name is the name of the unit flagged with the skill Client in the mission editor. --- local EscortGroup = GROUP:FindByName( "Group Name" ) -- The Group Name is the name of the group that will escort the Escort Client. --- --- -- Now use these 2 objects to construct the new EscortPlanes object. --- EscortPlanes = ESCORT:New( EscortClient, EscortGroup, "Desert", "Welcome to the mission. You are escorted by a plane with code name 'Desert', which can be instructed through the F10 radio menu." ) --- --- --- --- @module Escort --- @author FlightControl - ---- ESCORT class --- @type ESCORT --- @extends Base#BASE --- @field Client#CLIENT EscortClient --- @field Group#GROUP EscortGroup --- @field #string EscortName --- @field #ESCORT.MODE EscortMode The mode the escort is in. --- @field Scheduler#SCHEDULER FollowScheduler The instance of the SCHEDULER class. --- @field #number FollowDistance The current follow distance. --- @field #boolean ReportTargets If true, nearby targets are reported. --- @Field DCSTypes#AI.Option.Air.val.ROE OptionROE Which ROE is set to the EscortGroup. --- @field DCSTypes#AI.Option.Air.val.REACTION_ON_THREAT OptionReactionOnThreat Which REACTION_ON_THREAT is set to the EscortGroup. --- @field Menu#MENU_CLIENT EscortMenuResumeMission -ESCORT = { - ClassName = "ESCORT", - EscortName = nil, -- The Escort Name - EscortClient = nil, - EscortGroup = nil, - EscortMode = 1, - MODE = { - FOLLOW = 1, - MISSION = 2, - }, - Targets = {}, -- The identified targets - FollowScheduler = nil, - ReportTargets = true, - OptionROE = AI.Option.Air.val.ROE.OPEN_FIRE, - OptionReactionOnThreat = AI.Option.Air.val.REACTION_ON_THREAT.ALLOW_ABORT_MISSION, - SmokeDirectionVector = false, - TaskPoints = {} -} - ---- ESCORT.Mode class --- @type ESCORT.MODE --- @field #number FOLLOW --- @field #number MISSION - ---- MENUPARAM type --- @type MENUPARAM --- @field #ESCORT ParamSelf --- @field #Distance ParamDistance --- @field #function ParamFunction --- @field #string ParamMessage - ---- ESCORT class constructor for an AI group --- @param #ESCORT self --- @param Client#CLIENT EscortClient The client escorted by the EscortGroup. --- @param Group#GROUP EscortGroup The group AI escorting the EscortClient. --- @param #string EscortName Name of the escort. --- @return #ESCORT self --- @usage --- -- Declare a new EscortPlanes object as follows: --- --- -- First find the GROUP object and the CLIENT object. --- local EscortClient = CLIENT:FindByName( "Unit Name" ) -- The Unit Name is the name of the unit flagged with the skill Client in the mission editor. --- local EscortGroup = GROUP:FindByName( "Group Name" ) -- The Group Name is the name of the group that will escort the Escort Client. --- --- -- Now use these 2 objects to construct the new EscortPlanes object. --- EscortPlanes = ESCORT:New( EscortClient, EscortGroup, "Desert", "Welcome to the mission. You are escorted by a plane with code name 'Desert', which can be instructed through the F10 radio menu." ) -function ESCORT:New( EscortClient, EscortGroup, EscortName, EscortBriefing ) - local self = BASE:Inherit( self, BASE:New() ) - self:F( { EscortClient, EscortGroup, EscortName } ) - - self.EscortClient = EscortClient -- Client#CLIENT - self.EscortGroup = EscortGroup -- Group#GROUP - self.EscortName = EscortName - self.EscortBriefing = EscortBriefing - - -- Set EscortGroup known at EscortClient. - if not self.EscortClient._EscortGroups then - self.EscortClient._EscortGroups = {} - end - - if not self.EscortClient._EscortGroups[EscortGroup:GetName()] then - self.EscortClient._EscortGroups[EscortGroup:GetName()] = {} - self.EscortClient._EscortGroups[EscortGroup:GetName()].EscortGroup = self.EscortGroup - self.EscortClient._EscortGroups[EscortGroup:GetName()].EscortName = self.EscortName - self.EscortClient._EscortGroups[EscortGroup:GetName()].Targets = {} - end - - self.EscortMenu = MENU_CLIENT:New( self.EscortClient, self.EscortName ) - - self.EscortGroup:WayPointInitialize(1) - - self.EscortGroup:OptionROTVertical() - self.EscortGroup:OptionROEOpenFire() - - EscortGroup:MessageToClient( EscortGroup:GetCategoryName() .. " '" .. EscortName .. "' (" .. EscortGroup:GetCallsign() .. ") reporting! " .. - "We're escorting your flight. " .. - "Use the Radio Menu and F10 and use the options under + " .. EscortName .. "\n", - 60, EscortClient - ) - - self.FollowDistance = 100 - self.CT1 = 0 - self.GT1 = 0 - self.FollowScheduler = SCHEDULER:New( self, self._FollowScheduler, {}, 1, .5, .01 ) - self.EscortMode = ESCORT.MODE.MISSION - self.FollowScheduler:Stop() - - return self -end - ---- This function is for test, it will put on the frequency of the FollowScheduler a red smoke at the direction vector calculated for the escort to fly to. --- This allows to visualize where the escort is flying to. --- @param #ESCORT self --- @param #boolean SmokeDirection If true, then the direction vector will be smoked. -function ESCORT:TestSmokeDirectionVector( SmokeDirection ) - self.SmokeDirectionVector = ( SmokeDirection == true ) and true or false -end - - ---- Defines the default menus --- @param #ESCORT self --- @return #ESCORT -function ESCORT:Menus() - self:F() - - self:MenuFollowAt( 100 ) - self:MenuFollowAt( 200 ) - self:MenuFollowAt( 300 ) - self:MenuFollowAt( 400 ) - - self:MenuScanForTargets( 100, 60 ) - - self:MenuHoldAtEscortPosition( 30 ) - self:MenuHoldAtLeaderPosition( 30 ) - - self:MenuFlare() - self:MenuSmoke() - - self:MenuReportTargets( 60 ) - self:MenuAssistedAttack() - self:MenuROE() - self:MenuEvasion() - self:MenuResumeMission() - - - return self -end - - - ---- Defines a menu slot to let the escort Join and Follow you at a certain distance. --- This menu will appear under **Navigation**. --- @param #ESCORT self --- @param DCSTypes#Distance Distance The distance in meters that the escort needs to follow the client. --- @return #ESCORT -function ESCORT:MenuFollowAt( Distance ) - self:F(Distance) - - if self.EscortGroup:IsAir() then - if not self.EscortMenuReportNavigation then - self.EscortMenuReportNavigation = MENU_CLIENT:New( self.EscortClient, "Navigation", self.EscortMenu ) - end - - if not self.EscortMenuJoinUpAndFollow then - self.EscortMenuJoinUpAndFollow = {} - end - - self.EscortMenuJoinUpAndFollow[#self.EscortMenuJoinUpAndFollow+1] = MENU_CLIENT_COMMAND:New( self.EscortClient, "Join-Up and Follow at " .. Distance, self.EscortMenuReportNavigation, ESCORT._JoinUpAndFollow, { ParamSelf = self, ParamDistance = Distance } ) - - self.EscortMode = ESCORT.MODE.FOLLOW - end - - return self -end - ---- Defines a menu slot to let the escort hold at their current position and stay low with a specified height during a specified time in seconds. --- This menu will appear under **Hold position**. --- @param #ESCORT self --- @param DCSTypes#Distance Height Optional parameter that sets the height in meters to let the escort orbit at the current location. The default value is 30 meters. --- @param DCSTypes#Time Seconds Optional parameter that lets the escort orbit at the current position for a specified time. (not implemented yet). The default value is 0 seconds, meaning, that the escort will orbit forever until a sequent command is given. --- @param #string MenuTextFormat Optional parameter that shows the menu option text. The text string is formatted, and should contain two %d tokens in the string. The first for the Height, the second for the Time (if given). If no text is given, the default text will be displayed. --- @return #ESCORT --- TODO: Implement Seconds parameter. Challenge is to first develop the "continue from last activity" function. -function ESCORT:MenuHoldAtEscortPosition( Height, Seconds, MenuTextFormat ) - self:F( { Height, Seconds, MenuTextFormat } ) - - if self.EscortGroup:IsAir() then - - if not self.EscortMenuHold then - self.EscortMenuHold = MENU_CLIENT:New( self.EscortClient, "Hold position", self.EscortMenu ) - end - - if not Height then - Height = 30 - end - - if not Seconds then - Seconds = 0 - end - - local MenuText = "" - if not MenuTextFormat then - if Seconds == 0 then - MenuText = string.format( "Hold at %d meter", Height ) - else - MenuText = string.format( "Hold at %d meter for %d seconds", Height, Seconds ) - end - else - if Seconds == 0 then - MenuText = string.format( MenuTextFormat, Height ) - else - MenuText = string.format( MenuTextFormat, Height, Seconds ) - end - end - - if not self.EscortMenuHoldPosition then - self.EscortMenuHoldPosition = {} - end - - self.EscortMenuHoldPosition[#self.EscortMenuHoldPosition+1] = MENU_CLIENT_COMMAND - :New( - self.EscortClient, - MenuText, - self.EscortMenuHold, - ESCORT._HoldPosition, - { ParamSelf = self, - ParamOrbitGroup = self.EscortGroup, - ParamHeight = Height, - ParamSeconds = Seconds - } - ) - end - - return self -end - - ---- Defines a menu slot to let the escort hold at the client position and stay low with a specified height during a specified time in seconds. --- This menu will appear under **Navigation**. --- @param #ESCORT self --- @param DCSTypes#Distance Height Optional parameter that sets the height in meters to let the escort orbit at the current location. The default value is 30 meters. --- @param DCSTypes#Time Seconds Optional parameter that lets the escort orbit at the current position for a specified time. (not implemented yet). The default value is 0 seconds, meaning, that the escort will orbit forever until a sequent command is given. --- @param #string MenuTextFormat Optional parameter that shows the menu option text. The text string is formatted, and should contain one or two %d tokens in the string. The first for the Height, the second for the Time (if given). If no text is given, the default text will be displayed. --- @return #ESCORT --- TODO: Implement Seconds parameter. Challenge is to first develop the "continue from last activity" function. -function ESCORT:MenuHoldAtLeaderPosition( Height, Seconds, MenuTextFormat ) - self:F( { Height, Seconds, MenuTextFormat } ) - - if self.EscortGroup:IsAir() then - - if not self.EscortMenuHold then - self.EscortMenuHold = MENU_CLIENT:New( self.EscortClient, "Hold position", self.EscortMenu ) - end - - if not Height then - Height = 30 - end - - if not Seconds then - Seconds = 0 - end - - local MenuText = "" - if not MenuTextFormat then - if Seconds == 0 then - MenuText = string.format( "Rejoin and hold at %d meter", Height ) - else - MenuText = string.format( "Rejoin and hold at %d meter for %d seconds", Height, Seconds ) - end - else - if Seconds == 0 then - MenuText = string.format( MenuTextFormat, Height ) - else - MenuText = string.format( MenuTextFormat, Height, Seconds ) - end - end - - if not self.EscortMenuHoldAtLeaderPosition then - self.EscortMenuHoldAtLeaderPosition = {} - end - - self.EscortMenuHoldAtLeaderPosition[#self.EscortMenuHoldAtLeaderPosition+1] = MENU_CLIENT_COMMAND - :New( - self.EscortClient, - MenuText, - self.EscortMenuHold, - ESCORT._HoldPosition, - { ParamSelf = self, - ParamOrbitGroup = self.EscortClient, - ParamHeight = Height, - ParamSeconds = Seconds - } - ) - end - - return self -end - ---- Defines a menu slot to let the escort scan for targets at a certain height for a certain time in seconds. --- This menu will appear under **Scan targets**. --- @param #ESCORT self --- @param DCSTypes#Distance Height Optional parameter that sets the height in meters to let the escort orbit at the current location. The default value is 30 meters. --- @param DCSTypes#Time Seconds Optional parameter that lets the escort orbit at the current position for a specified time. (not implemented yet). The default value is 0 seconds, meaning, that the escort will orbit forever until a sequent command is given. --- @param #string MenuTextFormat Optional parameter that shows the menu option text. The text string is formatted, and should contain one or two %d tokens in the string. The first for the Height, the second for the Time (if given). If no text is given, the default text will be displayed. --- @return #ESCORT -function ESCORT:MenuScanForTargets( Height, Seconds, MenuTextFormat ) - self:F( { Height, Seconds, MenuTextFormat } ) - - if self.EscortGroup:IsAir() then - if not self.EscortMenuScan then - self.EscortMenuScan = MENU_CLIENT:New( self.EscortClient, "Scan for targets", self.EscortMenu ) - end - - if not Height then - Height = 100 - end - - if not Seconds then - Seconds = 30 - end - - local MenuText = "" - if not MenuTextFormat then - if Seconds == 0 then - MenuText = string.format( "At %d meter", Height ) - else - MenuText = string.format( "At %d meter for %d seconds", Height, Seconds ) - end - else - if Seconds == 0 then - MenuText = string.format( MenuTextFormat, Height ) - else - MenuText = string.format( MenuTextFormat, Height, Seconds ) - end - end - - if not self.EscortMenuScanForTargets then - self.EscortMenuScanForTargets = {} - end - - self.EscortMenuScanForTargets[#self.EscortMenuScanForTargets+1] = MENU_CLIENT_COMMAND - :New( - self.EscortClient, - MenuText, - self.EscortMenuScan, - ESCORT._ScanTargets, - { ParamSelf = self, - ParamScanDuration = 30 - } - ) - end - - return self -end - - - ---- Defines a menu slot to let the escort disperse a flare in a certain color. --- This menu will appear under **Navigation**. --- The flare will be fired from the first unit in the group. --- @param #ESCORT self --- @param #string MenuTextFormat Optional parameter that shows the menu option text. If no text is given, the default text will be displayed. --- @return #ESCORT -function ESCORT:MenuFlare( MenuTextFormat ) - self:F() - - if not self.EscortMenuReportNavigation then - self.EscortMenuReportNavigation = MENU_CLIENT:New( self.EscortClient, "Navigation", self.EscortMenu ) - end - - local MenuText = "" - if not MenuTextFormat then - MenuText = "Flare" - else - MenuText = MenuTextFormat - end - - if not self.EscortMenuFlare then - self.EscortMenuFlare = MENU_CLIENT:New( self.EscortClient, MenuText, self.EscortMenuReportNavigation, ESCORT._Flare, { ParamSelf = self } ) - self.EscortMenuFlareGreen = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release green flare", self.EscortMenuFlare, ESCORT._Flare, { ParamSelf = self, ParamColor = UNIT.FlareColor.Green, ParamMessage = "Released a green flare!" } ) - self.EscortMenuFlareRed = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release red flare", self.EscortMenuFlare, ESCORT._Flare, { ParamSelf = self, ParamColor = UNIT.FlareColor.Red, ParamMessage = "Released a red flare!" } ) - self.EscortMenuFlareWhite = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release white flare", self.EscortMenuFlare, ESCORT._Flare, { ParamSelf = self, ParamColor = UNIT.FlareColor.White, ParamMessage = "Released a white flare!" } ) - self.EscortMenuFlareYellow = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release yellow flare", self.EscortMenuFlare, ESCORT._Flare, { ParamSelf = self, ParamColor = UNIT.FlareColor.Yellow, ParamMessage = "Released a yellow flare!" } ) - end - - return self -end - ---- Defines a menu slot to let the escort disperse a smoke in a certain color. --- This menu will appear under **Navigation**. --- Note that smoke menu options will only be displayed for ships and ground units. Not for air units. --- The smoke will be fired from the first unit in the group. --- @param #ESCORT self --- @param #string MenuTextFormat Optional parameter that shows the menu option text. If no text is given, the default text will be displayed. --- @return #ESCORT -function ESCORT:MenuSmoke( MenuTextFormat ) - self:F() - - if not self.EscortGroup:IsAir() then - if not self.EscortMenuReportNavigation then - self.EscortMenuReportNavigation = MENU_CLIENT:New( self.EscortClient, "Navigation", self.EscortMenu ) - end - - local MenuText = "" - if not MenuTextFormat then - MenuText = "Smoke" - else - MenuText = MenuTextFormat - end - - if not self.EscortMenuSmoke then - self.EscortMenuSmoke = MENU_CLIENT:New( self.EscortClient, "Smoke", self.EscortMenuReportNavigation, ESCORT._Smoke, { ParamSelf = self } ) - self.EscortMenuSmokeGreen = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release green smoke", self.EscortMenuSmoke, ESCORT._Smoke, { ParamSelf = self, ParamColor = UNIT.SmokeColor.Green, ParamMessage = "Releasing green smoke!" } ) - self.EscortMenuSmokeRed = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release red smoke", self.EscortMenuSmoke, ESCORT._Smoke, { ParamSelf = self, ParamColor = UNIT.SmokeColor.Red, ParamMessage = "Releasing red smoke!" } ) - self.EscortMenuSmokeWhite = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release white smoke", self.EscortMenuSmoke, ESCORT._Smoke, { ParamSelf = self, ParamColor = UNIT.SmokeColor.White, ParamMessage = "Releasing white smoke!" } ) - self.EscortMenuSmokeOrange = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release orange smoke", self.EscortMenuSmoke, ESCORT._Smoke, { ParamSelf = self, ParamColor = UNIT.SmokeColor.Orange, ParamMessage = "Releasing orange smoke!" } ) - self.EscortMenuSmokeBlue = MENU_CLIENT_COMMAND:New( self.EscortClient, "Release blue smoke", self.EscortMenuSmoke, ESCORT._Smoke, { ParamSelf = self, ParamColor = UNIT.SmokeColor.Blue, ParamMessage = "Releasing blue smoke!" } ) - end - end - - return self -end - ---- Defines a menu slot to let the escort report their current detected targets with a specified time interval in seconds. --- This menu will appear under **Report targets**. --- Note that if a report targets menu is not specified, no targets will be detected by the escort, and the attack and assisted attack menus will not be displayed. --- @param #ESCORT self --- @param DCSTypes#Time Seconds Optional parameter that lets the escort report their current detected targets after specified time interval in seconds. The default time is 30 seconds. --- @return #ESCORT -function ESCORT:MenuReportTargets( Seconds ) - self:F( { Seconds } ) - - if not self.EscortMenuReportNearbyTargets then - self.EscortMenuReportNearbyTargets = MENU_CLIENT:New( self.EscortClient, "Report targets", self.EscortMenu ) - end - - if not Seconds then - Seconds = 30 - end - - -- Report Targets - self.EscortMenuReportNearbyTargetsNow = MENU_CLIENT_COMMAND:New( self.EscortClient, "Report targets now!", self.EscortMenuReportNearbyTargets, ESCORT._ReportNearbyTargetsNow, { ParamSelf = self } ) - self.EscortMenuReportNearbyTargetsOn = MENU_CLIENT_COMMAND:New( self.EscortClient, "Report targets on", self.EscortMenuReportNearbyTargets, ESCORT._SwitchReportNearbyTargets, { ParamSelf = self, ParamReportTargets = true } ) - self.EscortMenuReportNearbyTargetsOff = MENU_CLIENT_COMMAND:New( self.EscortClient, "Report targets off", self.EscortMenuReportNearbyTargets, ESCORT._SwitchReportNearbyTargets, { ParamSelf = self, ParamReportTargets = false, } ) - - -- Attack Targets - self.EscortMenuAttackNearbyTargets = MENU_CLIENT:New( self.EscortClient, "Attack targets", self.EscortMenu ) - - - self.ReportTargetsScheduler = SCHEDULER:New( self, self._ReportTargetsScheduler, {}, 1, Seconds ) - - return self -end - ---- Defines a menu slot to let the escort attack its detected targets using assisted attack from another escort joined also with the client. --- This menu will appear under **Request assistance from**. --- Note that this method needs to be preceded with the method MenuReportTargets. --- @param #ESCORT self --- @return #ESCORT -function ESCORT:MenuAssistedAttack() - self:F() - - -- Request assistance from other escorts. - -- This is very useful to let f.e. an escorting ship attack a target detected by an escorting plane... - self.EscortMenuTargetAssistance = MENU_CLIENT:New( self.EscortClient, "Request assistance from", self.EscortMenu ) - - return self -end - ---- Defines a menu to let the escort set its rules of engagement. --- All rules of engagement will appear under the menu **ROE**. --- @param #ESCORT self --- @return #ESCORT -function ESCORT:MenuROE( MenuTextFormat ) - self:F( MenuTextFormat ) - - if not self.EscortMenuROE then - -- Rules of Engagement - self.EscortMenuROE = MENU_CLIENT:New( self.EscortClient, "ROE", self.EscortMenu ) - if self.EscortGroup:OptionROEHoldFirePossible() then - self.EscortMenuROEHoldFire = MENU_CLIENT_COMMAND:New( self.EscortClient, "Hold Fire", self.EscortMenuROE, ESCORT._ROE, { ParamSelf = self, ParamFunction = self.EscortGroup:OptionROEHoldFire(), ParamMessage = "Holding weapons!" } ) - end - if self.EscortGroup:OptionROEReturnFirePossible() then - self.EscortMenuROEReturnFire = MENU_CLIENT_COMMAND:New( self.EscortClient, "Return Fire", self.EscortMenuROE, ESCORT._ROE, { ParamSelf = self, ParamFunction = self.EscortGroup:OptionROEReturnFire(), ParamMessage = "Returning fire!" } ) - end - if self.EscortGroup:OptionROEOpenFirePossible() then - self.EscortMenuROEOpenFire = MENU_CLIENT_COMMAND:New( self.EscortClient, "Open Fire", self.EscortMenuROE, ESCORT._ROE, { ParamSelf = self, ParamFunction = self.EscortGroup:OptionROEOpenFire(), ParamMessage = "Opening fire on designated targets!!" } ) - end - if self.EscortGroup:OptionROEWeaponFreePossible() then - self.EscortMenuROEWeaponFree = MENU_CLIENT_COMMAND:New( self.EscortClient, "Weapon Free", self.EscortMenuROE, ESCORT._ROE, { ParamSelf = self, ParamFunction = self.EscortGroup:OptionROEWeaponFree(), ParamMessage = "Opening fire on targets of opportunity!" } ) - end - end - - return self -end - - ---- Defines a menu to let the escort set its evasion when under threat. --- All rules of engagement will appear under the menu **Evasion**. --- @param #ESCORT self --- @return #ESCORT -function ESCORT:MenuEvasion( MenuTextFormat ) - self:F( MenuTextFormat ) - - if self.EscortGroup:IsAir() then - if not self.EscortMenuEvasion then - -- Reaction to Threats - self.EscortMenuEvasion = MENU_CLIENT:New( self.EscortClient, "Evasion", self.EscortMenu ) - if self.EscortGroup:OptionROTNoReactionPossible() then - self.EscortMenuEvasionNoReaction = MENU_CLIENT_COMMAND:New( self.EscortClient, "Fight until death", self.EscortMenuEvasion, ESCORT._ROT, { ParamSelf = self, ParamFunction = self.EscortGroup:OptionROTNoReaction(), ParamMessage = "Fighting until death!" } ) - end - if self.EscortGroup:OptionROTPassiveDefensePossible() then - self.EscortMenuEvasionPassiveDefense = MENU_CLIENT_COMMAND:New( self.EscortClient, "Use flares, chaff and jammers", self.EscortMenuEvasion, ESCORT._ROT, { ParamSelf = self, ParamFunction = self.EscortGroup:OptionROTPassiveDefense(), ParamMessage = "Defending using jammers, chaff and flares!" } ) - end - if self.EscortGroup:OptionROTEvadeFirePossible() then - self.EscortMenuEvasionEvadeFire = MENU_CLIENT_COMMAND:New( self.EscortClient, "Evade enemy fire", self.EscortMenuEvasion, ESCORT._ROT, { ParamSelf = self, ParamFunction = self.EscortGroup:OptionROTEvadeFire(), ParamMessage = "Evading on enemy fire!" } ) - end - if self.EscortGroup:OptionROTVerticalPossible() then - self.EscortMenuOptionEvasionVertical = MENU_CLIENT_COMMAND:New( self.EscortClient, "Go below radar and evade fire", self.EscortMenuEvasion, ESCORT._ROT, { ParamSelf = self, ParamFunction = self.EscortGroup:OptionROTVertical(), ParamMessage = "Evading on enemy fire with vertical manoeuvres!" } ) - end - end - end - - return self -end - ---- Defines a menu to let the escort resume its mission from a waypoint on its route. --- All rules of engagement will appear under the menu **Resume mission from**. --- @param #ESCORT self --- @return #ESCORT -function ESCORT:MenuResumeMission() - self:F() - - if not self.EscortMenuResumeMission then - -- Mission Resume Menu Root - self.EscortMenuResumeMission = MENU_CLIENT:New( self.EscortClient, "Resume mission from", self.EscortMenu ) - end - - return self -end - - ---- @param #MENUPARAM MenuParam -function ESCORT._HoldPosition( MenuParam ) - - local self = MenuParam.ParamSelf - local EscortGroup = self.EscortGroup - local EscortClient = self.EscortClient - - local OrbitGroup = MenuParam.ParamOrbitGroup -- Group#GROUP - local OrbitUnit = OrbitGroup:GetUnit(1) -- Unit#UNIT - local OrbitHeight = MenuParam.ParamHeight - local OrbitSeconds = MenuParam.ParamSeconds -- Not implemented yet - - self.FollowScheduler:Stop() - - local PointFrom = {} - local GroupPoint = EscortGroup:GetUnit(1):GetPointVec3() - PointFrom = {} - PointFrom.x = GroupPoint.x - PointFrom.y = GroupPoint.z - PointFrom.speed = 250 - PointFrom.type = AI.Task.WaypointType.TURNING_POINT - PointFrom.alt = GroupPoint.y - PointFrom.alt_type = AI.Task.AltitudeType.BARO - - local OrbitPoint = OrbitUnit:GetVec2() - local PointTo = {} - PointTo.x = OrbitPoint.x - PointTo.y = OrbitPoint.y - PointTo.speed = 250 - PointTo.type = AI.Task.WaypointType.TURNING_POINT - PointTo.alt = OrbitHeight - PointTo.alt_type = AI.Task.AltitudeType.BARO - PointTo.task = EscortGroup:TaskOrbitCircleAtVec2( OrbitPoint, OrbitHeight, 0 ) - - local Points = { PointFrom, PointTo } - - EscortGroup:OptionROEHoldFire() - EscortGroup:OptionROTPassiveDefense() - - EscortGroup:SetTask( EscortGroup:TaskRoute( Points ) ) - EscortGroup:MessageToClient( "Orbiting at location.", 10, EscortClient ) - -end - ---- @param #MENUPARAM MenuParam -function ESCORT._JoinUpAndFollow( MenuParam ) - - local self = MenuParam.ParamSelf - local EscortGroup = self.EscortGroup - local EscortClient = self.EscortClient - - self.Distance = MenuParam.ParamDistance - - self:JoinUpAndFollow( EscortGroup, EscortClient, self.Distance ) -end - ---- JoinsUp and Follows a CLIENT. --- @param Escort#ESCORT self --- @param Group#GROUP EscortGroup --- @param Client#CLIENT EscortClient --- @param DCSTypes#Distance Distance -function ESCORT:JoinUpAndFollow( EscortGroup, EscortClient, Distance ) - self:F( { EscortGroup, EscortClient, Distance } ) - - self.FollowScheduler:Stop() - - EscortGroup:OptionROEHoldFire() - EscortGroup:OptionROTPassiveDefense() - - self.EscortMode = ESCORT.MODE.FOLLOW - - self.CT1 = 0 - self.GT1 = 0 - self.FollowScheduler:Start() - - EscortGroup:MessageToClient( "Rejoining and Following at " .. Distance .. "!", 30, EscortClient ) -end - ---- @param #MENUPARAM MenuParam -function ESCORT._Flare( MenuParam ) - - local self = MenuParam.ParamSelf - local EscortGroup = self.EscortGroup - local EscortClient = self.EscortClient - - local Color = MenuParam.ParamColor - local Message = MenuParam.ParamMessage - - EscortGroup:GetUnit(1):Flare( Color ) - EscortGroup:MessageToClient( Message, 10, EscortClient ) -end - ---- @param #MENUPARAM MenuParam -function ESCORT._Smoke( MenuParam ) - - local self = MenuParam.ParamSelf - local EscortGroup = self.EscortGroup - local EscortClient = self.EscortClient - - local Color = MenuParam.ParamColor - local Message = MenuParam.ParamMessage - - EscortGroup:GetUnit(1):Smoke( Color ) - EscortGroup:MessageToClient( Message, 10, EscortClient ) -end - - ---- @param #MENUPARAM MenuParam -function ESCORT._ReportNearbyTargetsNow( MenuParam ) - - local self = MenuParam.ParamSelf - local EscortGroup = self.EscortGroup - local EscortClient = self.EscortClient - - self:_ReportTargetsScheduler() - -end - -function ESCORT._SwitchReportNearbyTargets( MenuParam ) - - local self = MenuParam.ParamSelf - local EscortGroup = self.EscortGroup - local EscortClient = self.EscortClient - - self.ReportTargets = MenuParam.ParamReportTargets - - if self.ReportTargets then - if not self.ReportTargetsScheduler then - self.ReportTargetsScheduler = SCHEDULER:New( self, self._ReportTargetsScheduler, {}, 1, 30 ) - end - else - routines.removeFunction( self.ReportTargetsScheduler ) - self.ReportTargetsScheduler = nil - end -end - ---- @param #MENUPARAM MenuParam -function ESCORT._ScanTargets( MenuParam ) - - local self = MenuParam.ParamSelf - local EscortGroup = self.EscortGroup - local EscortClient = self.EscortClient - - local ScanDuration = MenuParam.ParamScanDuration - - self.FollowScheduler:Stop() - - if EscortGroup:IsHelicopter() then - SCHEDULER:New( EscortGroup, EscortGroup.PushTask, - { EscortGroup:TaskControlled( - EscortGroup:TaskOrbitCircle( 200, 20 ), - EscortGroup:TaskCondition( nil, nil, nil, nil, ScanDuration, nil ) - ) - }, - 1 - ) - elseif EscortGroup:IsAirPlane() then - SCHEDULER:New( EscortGroup, EscortGroup.PushTask, - { EscortGroup:TaskControlled( - EscortGroup:TaskOrbitCircle( 1000, 500 ), - EscortGroup:TaskCondition( nil, nil, nil, nil, ScanDuration, nil ) - ) - }, - 1 - ) - end - - EscortGroup:MessageToClient( "Scanning targets for " .. ScanDuration .. " seconds.", ScanDuration, EscortClient ) - - if self.EscortMode == ESCORT.MODE.FOLLOW then - self.FollowScheduler:Start() - end - -end - ---- @param Group#GROUP EscortGroup -function _Resume( EscortGroup ) - env.info( '_Resume' ) - - local Escort = EscortGroup:GetState( EscortGroup, "Escort" ) - env.info( "EscortMode = " .. Escort.EscortMode ) - if Escort.EscortMode == ESCORT.MODE.FOLLOW then - Escort:JoinUpAndFollow( EscortGroup, Escort.EscortClient, Escort.Distance ) - end - -end - ---- @param #MENUPARAM MenuParam -function ESCORT._AttackTarget( MenuParam ) - - local self = MenuParam.ParamSelf - local EscortGroup = self.EscortGroup - - local EscortClient = self.EscortClient - local AttackUnit = MenuParam.ParamUnit -- Unit#UNIT - - self.FollowScheduler:Stop() - - self:T( AttackUnit ) - - if EscortGroup:IsAir() then - EscortGroup:OptionROEOpenFire() - EscortGroup:OptionROTPassiveDefense() - EscortGroup:SetState( EscortGroup, "Escort", self ) - SCHEDULER:New( EscortGroup, - EscortGroup.PushTask, - { EscortGroup:TaskCombo( - { EscortGroup:TaskAttackUnit( AttackUnit ), - EscortGroup:TaskFunction( 1, 2, "_Resume", { "''" } ) - } - ) - }, 10 - ) - else - SCHEDULER:New( EscortGroup, - EscortGroup.PushTask, - { EscortGroup:TaskCombo( - { EscortGroup:TaskFireAtPoint( AttackUnit:GetVec2(), 50 ) - } - ) - }, 10 - ) - end - - EscortGroup:MessageToClient( "Engaging Designated Unit!", 10, EscortClient ) - -end - ---- @param #MENUPARAM MenuParam -function ESCORT._AssistTarget( MenuParam ) - - local self = MenuParam.ParamSelf - local EscortGroup = self.EscortGroup - local EscortClient = self.EscortClient - local EscortGroupAttack = MenuParam.ParamEscortGroup - local AttackUnit = MenuParam.ParamUnit -- Unit#UNIT - - self.FollowScheduler:Stop() - - self:T( AttackUnit ) - - if EscortGroupAttack:IsAir() then - EscortGroupAttack:OptionROEOpenFire() - EscortGroupAttack:OptionROTVertical() - SCHDULER:New( EscortGroupAttack, - EscortGroupAttack.PushTask, - { EscortGroupAttack:TaskCombo( - { EscortGroupAttack:TaskAttackUnit( AttackUnit ), - EscortGroupAttack:TaskOrbitCircle( 500, 350 ) - } - ) - }, 10 - ) - else - SCHEDULER:New( EscortGroupAttack, - EscortGroupAttack.PushTask, - { EscortGroupAttack:TaskCombo( - { EscortGroupAttack:TaskFireAtPoint( AttackUnit:GetVec2(), 50 ) - } - ) - }, 10 - ) - end - EscortGroupAttack:MessageToClient( "Assisting with the destroying the enemy unit!", 10, EscortClient ) - -end - ---- @param #MENUPARAM MenuParam -function ESCORT._ROE( MenuParam ) - - local self = MenuParam.ParamSelf - local EscortGroup = self.EscortGroup - local EscortClient = self.EscortClient - - local EscortROEFunction = MenuParam.ParamFunction - local EscortROEMessage = MenuParam.ParamMessage - - pcall( function() EscortROEFunction() end ) - EscortGroup:MessageToClient( EscortROEMessage, 10, EscortClient ) -end - ---- @param #MENUPARAM MenuParam -function ESCORT._ROT( MenuParam ) - - local self = MenuParam.ParamSelf - local EscortGroup = self.EscortGroup - local EscortClient = self.EscortClient - - local EscortROTFunction = MenuParam.ParamFunction - local EscortROTMessage = MenuParam.ParamMessage - - pcall( function() EscortROTFunction() end ) - EscortGroup:MessageToClient( EscortROTMessage, 10, EscortClient ) -end - ---- @param #MENUPARAM MenuParam -function ESCORT._ResumeMission( MenuParam ) - - local self = MenuParam.ParamSelf - local EscortGroup = self.EscortGroup - local EscortClient = self.EscortClient - - local WayPoint = MenuParam.ParamWayPoint - - self.FollowScheduler:Stop() - - local WayPoints = EscortGroup:GetTaskRoute() - self:T( WayPoint, WayPoints ) - - for WayPointIgnore = 1, WayPoint do - table.remove( WayPoints, 1 ) - end - - SCHEDULER:New( EscortGroup, EscortGroup.SetTask, { EscortGroup:TaskRoute( WayPoints ) }, 1 ) - - EscortGroup:MessageToClient( "Resuming mission from waypoint " .. WayPoint .. ".", 10, EscortClient ) -end - ---- Registers the waypoints --- @param #ESCORT self --- @return #table -function ESCORT:RegisterRoute() - self:F() - - local EscortGroup = self.EscortGroup -- Group#GROUP - - local TaskPoints = EscortGroup:GetTaskRoute() - - self:T( TaskPoints ) - - return TaskPoints -end - ---- @param Escort#ESCORT self -function ESCORT:_FollowScheduler() - self:F( { self.FollowDistance } ) - - self:T( {self.EscortClient.UnitName, self.EscortGroup.GroupName } ) - if self.EscortGroup:IsAlive() and self.EscortClient:IsAlive() then - - local ClientUnit = self.EscortClient:GetClientGroupUnit() - local GroupUnit = self.EscortGroup:GetUnit( 1 ) - local FollowDistance = self.FollowDistance - - self:T( {ClientUnit.UnitName, GroupUnit.UnitName } ) - - if self.CT1 == 0 and self.GT1 == 0 then - self.CV1 = ClientUnit:GetPointVec3() - self:T( { "self.CV1", self.CV1 } ) - self.CT1 = timer.getTime() - self.GV1 = GroupUnit:GetPointVec3() - self.GT1 = timer.getTime() - else - local CT1 = self.CT1 - local CT2 = timer.getTime() - local CV1 = self.CV1 - local CV2 = ClientUnit:GetPointVec3() - self.CT1 = CT2 - self.CV1 = CV2 - - local CD = ( ( CV2.x - CV1.x )^2 + ( CV2.y - CV1.y )^2 + ( CV2.z - CV1.z )^2 ) ^ 0.5 - local CT = CT2 - CT1 - - local CS = ( 3600 / CT ) * ( CD / 1000 ) - - self:T2( { "Client:", CS, CD, CT, CV2, CV1, CT2, CT1 } ) - - local GT1 = self.GT1 - local GT2 = timer.getTime() - local GV1 = self.GV1 - local GV2 = GroupUnit:GetPointVec3() - self.GT1 = GT2 - self.GV1 = GV2 - - local GD = ( ( GV2.x - GV1.x )^2 + ( GV2.y - GV1.y )^2 + ( GV2.z - GV1.z )^2 ) ^ 0.5 - local GT = GT2 - GT1 - - local GS = ( 3600 / GT ) * ( GD / 1000 ) - - self:T2( { "Group:", GS, GD, GT, GV2, GV1, GT2, GT1 } ) - - -- Calculate the group direction vector - local GV = { x = GV2.x - CV2.x, y = GV2.y - CV2.y, z = GV2.z - CV2.z } - - -- Calculate GH2, GH2 with the same height as CV2. - local GH2 = { x = GV2.x, y = CV2.y, z = GV2.z } - - -- Calculate the angle of GV to the orthonormal plane - local alpha = math.atan2( GV.z, GV.x ) - - -- Now we calculate the intersecting vector between the circle around CV2 with radius FollowDistance and GH2. - -- From the GeoGebra model: CVI = (x(CV2) + FollowDistance cos(alpha), y(GH2) + FollowDistance sin(alpha), z(CV2)) - local CVI = { x = CV2.x + FollowDistance * math.cos(alpha), - y = GH2.y, - z = CV2.z + FollowDistance * math.sin(alpha), - } - - -- Calculate the direction vector DV of the escort group. We use CVI as the base and CV2 as the direction. - local DV = { x = CV2.x - CVI.x, y = CV2.y - CVI.y, z = CV2.z - CVI.z } - - -- We now calculate the unary direction vector DVu, so that we can multiply DVu with the speed, which is expressed in meters / s. - -- We need to calculate this vector to predict the point the escort group needs to fly to according its speed. - -- The distance of the destination point should be far enough not to have the aircraft starting to swipe left to right... - local DVu = { x = DV.x / FollowDistance, y = DV.y / FollowDistance, z = DV.z / FollowDistance } - - -- Now we can calculate the group destination vector GDV. - local GDV = { x = DVu.x * CS * 8 + CVI.x, y = CVI.y, z = DVu.z * CS * 8 + CVI.z } - - if self.SmokeDirectionVector == true then - trigger.action.smoke( GDV, trigger.smokeColor.Red ) - end - - self:T2( { "CV2:", CV2 } ) - self:T2( { "CVI:", CVI } ) - self:T2( { "GDV:", GDV } ) - - -- Measure distance between client and group - local CatchUpDistance = ( ( GDV.x - GV2.x )^2 + ( GDV.y - GV2.y )^2 + ( GDV.z - GV2.z )^2 ) ^ 0.5 - - -- The calculation of the Speed would simulate that the group would take 30 seconds to overcome - -- the requested Distance). - local Time = 10 - local CatchUpSpeed = ( CatchUpDistance - ( CS * 8.4 ) ) / Time - - local Speed = CS + CatchUpSpeed - if Speed < 0 then - Speed = 0 - end - - self:T( { "Client Speed, Escort Speed, Speed, FollowDistance, Time:", CS, GS, Speed, FollowDistance, Time } ) - - -- Now route the escort to the desired point with the desired speed. - self.EscortGroup:TaskRouteToVec3( GDV, Speed / 3.6 ) -- DCS models speed in Mps (Miles per second) - end - - return true - end - - return false -end - - ---- Report Targets Scheduler. --- @param #ESCORT self -function ESCORT:_ReportTargetsScheduler() - self:F( self.EscortGroup:GetName() ) - - if self.EscortGroup:IsAlive() and self.EscortClient:IsAlive() then - local EscortGroupName = self.EscortGroup:GetName() - local EscortTargets = self.EscortGroup:GetDetectedTargets() - - local ClientEscortTargets = self.EscortClient._EscortGroups[EscortGroupName].Targets - - local EscortTargetMessages = "" - for EscortTargetID, EscortTarget in pairs( EscortTargets ) do - local EscortObject = EscortTarget.object - self:T( EscortObject ) - if EscortObject and EscortObject:isExist() and EscortObject.id_ < 50000000 then - - local EscortTargetUnit = UNIT:Find( EscortObject ) - local EscortTargetUnitName = EscortTargetUnit:GetName() - - - - -- local EscortTargetIsDetected, - -- EscortTargetIsVisible, - -- EscortTargetLastTime, - -- EscortTargetKnowType, - -- EscortTargetKnowDistance, - -- EscortTargetLastPos, - -- EscortTargetLastVelocity - -- = self.EscortGroup:IsTargetDetected( EscortObject ) - -- - -- self:T( { EscortTargetIsDetected, - -- EscortTargetIsVisible, - -- EscortTargetLastTime, - -- EscortTargetKnowType, - -- EscortTargetKnowDistance, - -- EscortTargetLastPos, - -- EscortTargetLastVelocity } ) - - - local EscortTargetUnitPositionVec3 = EscortTargetUnit:GetPointVec3() - local EscortPositionVec3 = self.EscortGroup:GetPointVec3() - local Distance = ( ( EscortTargetUnitPositionVec3.x - EscortPositionVec3.x )^2 + - ( EscortTargetUnitPositionVec3.y - EscortPositionVec3.y )^2 + - ( EscortTargetUnitPositionVec3.z - EscortPositionVec3.z )^2 - ) ^ 0.5 / 1000 - - self:T( { self.EscortGroup:GetName(), EscortTargetUnit:GetName(), Distance, EscortTarget } ) - - if Distance <= 15 then - - if not ClientEscortTargets[EscortTargetUnitName] then - ClientEscortTargets[EscortTargetUnitName] = {} - end - ClientEscortTargets[EscortTargetUnitName].AttackUnit = EscortTargetUnit - ClientEscortTargets[EscortTargetUnitName].visible = EscortTarget.visible - ClientEscortTargets[EscortTargetUnitName].type = EscortTarget.type - ClientEscortTargets[EscortTargetUnitName].distance = EscortTarget.distance - else - if ClientEscortTargets[EscortTargetUnitName] then - ClientEscortTargets[EscortTargetUnitName] = nil - end - end - end - end - - self:T( { "Sorting Targets Table:", ClientEscortTargets } ) - table.sort( ClientEscortTargets, function( a, b ) return a.Distance < b.Distance end ) - self:T( { "Sorted Targets Table:", ClientEscortTargets } ) - - -- Remove the sub menus of the Attack menu of the Escort for the EscortGroup. - self.EscortMenuAttackNearbyTargets:RemoveSubMenus() - - if self.EscortMenuTargetAssistance then - self.EscortMenuTargetAssistance:RemoveSubMenus() - end - - --for MenuIndex = 1, #self.EscortMenuAttackTargets do - -- self:T( { "Remove Menu:", self.EscortMenuAttackTargets[MenuIndex] } ) - -- self.EscortMenuAttackTargets[MenuIndex] = self.EscortMenuAttackTargets[MenuIndex]:Remove() - --end - - - if ClientEscortTargets then - for ClientEscortTargetUnitName, ClientEscortTargetData in pairs( ClientEscortTargets ) do - - for ClientEscortGroupName, EscortGroupData in pairs( self.EscortClient._EscortGroups ) do - - if ClientEscortTargetData and ClientEscortTargetData.AttackUnit:IsAlive() then - - local EscortTargetMessage = "" - local EscortTargetCategoryName = ClientEscortTargetData.AttackUnit:GetCategoryName() - local EscortTargetCategoryType = ClientEscortTargetData.AttackUnit:GetTypeName() - if ClientEscortTargetData.type then - EscortTargetMessage = EscortTargetMessage .. EscortTargetCategoryName .. " (" .. EscortTargetCategoryType .. ") at " - else - EscortTargetMessage = EscortTargetMessage .. "Unknown target at " - end - - local EscortTargetUnitPositionVec3 = ClientEscortTargetData.AttackUnit:GetPointVec3() - local EscortPositionVec3 = self.EscortGroup:GetPointVec3() - local Distance = ( ( EscortTargetUnitPositionVec3.x - EscortPositionVec3.x )^2 + - ( EscortTargetUnitPositionVec3.y - EscortPositionVec3.y )^2 + - ( EscortTargetUnitPositionVec3.z - EscortPositionVec3.z )^2 - ) ^ 0.5 / 1000 - - self:T( { self.EscortGroup:GetName(), ClientEscortTargetData.AttackUnit:GetName(), Distance, ClientEscortTargetData.AttackUnit } ) - if ClientEscortTargetData.visible == false then - EscortTargetMessage = EscortTargetMessage .. string.format( "%.2f", Distance ) .. " estimated km" - else - EscortTargetMessage = EscortTargetMessage .. string.format( "%.2f", Distance ) .. " km" - end - - if ClientEscortTargetData.visible then - EscortTargetMessage = EscortTargetMessage .. ", visual" - end - - if ClientEscortGroupName == EscortGroupName then - - MENU_CLIENT_COMMAND:New( self.EscortClient, - EscortTargetMessage, - self.EscortMenuAttackNearbyTargets, - ESCORT._AttackTarget, - { ParamSelf = self, - ParamUnit = ClientEscortTargetData.AttackUnit - } - ) - EscortTargetMessages = EscortTargetMessages .. "\n - " .. EscortTargetMessage - else - if self.EscortMenuTargetAssistance then - local MenuTargetAssistance = MENU_CLIENT:New( self.EscortClient, EscortGroupData.EscortName, self.EscortMenuTargetAssistance ) - MENU_CLIENT_COMMAND:New( self.EscortClient, - EscortTargetMessage, - MenuTargetAssistance, - ESCORT._AssistTarget, - { ParamSelf = self, - ParamEscortGroup = EscortGroupData.EscortGroup, - ParamUnit = ClientEscortTargetData.AttackUnit - } - ) - end - end - else - ClientEscortTargetData = nil - end - end - end - - if EscortTargetMessages ~= "" and self.ReportTargets == true then - self.EscortGroup:MessageToClient( "Detected targets within 15 km range:" .. EscortTargetMessages:gsub("\n$",""), 20, self.EscortClient ) - else - self.EscortGroup:MessageToClient( "No targets detected!", 20, self.EscortClient ) - end - end - - if self.EscortMenuResumeMission then - self.EscortMenuResumeMission:RemoveSubMenus() - - -- if self.EscortMenuResumeWayPoints then - -- for MenuIndex = 1, #self.EscortMenuResumeWayPoints do - -- self:T( { "Remove Menu:", self.EscortMenuResumeWayPoints[MenuIndex] } ) - -- self.EscortMenuResumeWayPoints[MenuIndex] = self.EscortMenuResumeWayPoints[MenuIndex]:Remove() - -- end - -- end - - local TaskPoints = self:RegisterRoute() - for WayPointID, WayPoint in pairs( TaskPoints ) do - local EscortPositionVec3 = self.EscortGroup:GetPointVec3() - local Distance = ( ( WayPoint.x - EscortPositionVec3.x )^2 + - ( WayPoint.y - EscortPositionVec3.z )^2 - ) ^ 0.5 / 1000 - MENU_CLIENT_COMMAND:New( self.EscortClient, "Waypoint " .. WayPointID .. " at " .. string.format( "%.2f", Distance ).. "km", self.EscortMenuResumeMission, ESCORT._ResumeMission, { ParamSelf = self, ParamWayPoint = WayPointID } ) - end - end - - return true - end - - return false -end ---- This module contains the MISSILETRAINER class. --- --- === --- --- 1) @{MissileTrainer#MISSILETRAINER} class, extends @{Base#BASE} --- =============================================================== --- The @{#MISSILETRAINER} class uses the DCS world messaging system to be alerted of any missiles fired, and when a missile would hit your aircraft, --- the class will destroy the missile within a certain range, to avoid damage to your aircraft. --- It suports the following functionality: --- --- * Track the missiles fired at you and other players, providing bearing and range information of the missiles towards the airplanes. --- * Provide alerts of missile launches, including detailed information of the units launching, including bearing, range � --- * Provide alerts when a missile would have killed your aircraft. --- * Provide alerts when the missile self destructs. --- * Enable / Disable and Configure the Missile Trainer using the various menu options. --- --- When running a mission where MISSILETRAINER is used, the following radio menu structure ( 'Radio Menu' -> 'Other (F10)' -> 'MissileTrainer' ) options are available for the players: --- --- * **Messages**: Menu to configure all messages. --- * **Messages On**: Show all messages. --- * **Messages Off**: Disable all messages. --- * **Tracking**: Menu to configure missile tracking messages. --- * **To All**: Shows missile tracking messages to all players. --- * **To Target**: Shows missile tracking messages only to the player where the missile is targetted at. --- * **Tracking On**: Show missile tracking messages. --- * **Tracking Off**: Disable missile tracking messages. --- * **Frequency Increase**: Increases the missile tracking message frequency with one second. --- * **Frequency Decrease**: Decreases the missile tracking message frequency with one second. --- * **Alerts**: Menu to configure alert messages. --- * **To All**: Shows alert messages to all players. --- * **To Target**: Shows alert messages only to the player where the missile is (was) targetted at. --- * **Hits On**: Show missile hit alert messages. --- * **Hits Off**: Disable missile hit alert messages. --- * **Launches On**: Show missile launch messages. --- * **Launches Off**: Disable missile launch messages. --- * **Details**: Menu to configure message details. --- * **Range On**: Shows range information when a missile is fired to a target. --- * **Range Off**: Disable range information when a missile is fired to a target. --- * **Bearing On**: Shows bearing information when a missile is fired to a target. --- * **Bearing Off**: Disable bearing information when a missile is fired to a target. --- * **Distance**: Menu to configure the distance when a missile needs to be destroyed when near to a player, during tracking. This will improve/influence hit calculation accuracy, but has the risk of damaging the aircraft when the missile reaches the aircraft before the distance is measured. --- * **50 meter**: Destroys the missile when the distance to the aircraft is below or equal to 50 meter. --- * **100 meter**: Destroys the missile when the distance to the aircraft is below or equal to 100 meter. --- * **150 meter**: Destroys the missile when the distance to the aircraft is below or equal to 150 meter. --- * **200 meter**: Destroys the missile when the distance to the aircraft is below or equal to 200 meter. --- --- --- 1.1) MISSILETRAINER construction methods: --- ----------------------------------------- --- Create a new MISSILETRAINER object with the @{#MISSILETRAINER.New} method: --- --- * @{#MISSILETRAINER.New}: Creates a new MISSILETRAINER object taking the maximum distance to your aircraft to evaluate when a missile needs to be destroyed. --- --- MISSILETRAINER will collect each unit declared in the mission with a skill level "Client" and "Player", and will monitor the missiles shot at those. --- --- 1.2) MISSILETRAINER initialization methods: --- ------------------------------------------- --- A MISSILETRAINER object will behave differently based on the usage of initialization methods: --- --- * @{#MISSILETRAINER.InitMessagesOnOff}: Sets by default the display of any message to be ON or OFF. --- * @{#MISSILETRAINER.InitTrackingToAll}: Sets by default the missile tracking report for all players or only for those missiles targetted to you. --- * @{#MISSILETRAINER.InitTrackingOnOff}: Sets by default the display of missile tracking report to be ON or OFF. --- * @{#MISSILETRAINER.InitTrackingFrequency}: Increases, decreases the missile tracking message display frequency with the provided time interval in seconds. --- * @{#MISSILETRAINER.InitAlertsToAll}: Sets by default the display of alerts to be shown to all players or only to you. --- * @{#MISSILETRAINER.InitAlertsHitsOnOff}: Sets by default the display of hit alerts ON or OFF. --- * @{#MISSILETRAINER.InitAlertsLaunchesOnOff}: Sets by default the display of launch alerts ON or OFF. --- * @{#MISSILETRAINER.InitRangeOnOff}: Sets by default the display of range information of missiles ON of OFF. --- * @{#MISSILETRAINER.InitBearingOnOff}: Sets by default the display of bearing information of missiles ON of OFF. --- * @{#MISSILETRAINER.InitMenusOnOff}: Allows to configure the options through the radio menu. --- --- === --- --- CREDITS --- ======= --- **Stuka (Danny)** Who you can search on the Eagle Dynamics Forums. --- Working together with Danny has resulted in the MISSILETRAINER class. --- Danny has shared his ideas and together we made a design. --- Together with the **476 virtual team**, we tested the MISSILETRAINER class, and got much positive feedback! --- --- @module MissileTrainer --- @author FlightControl - - ---- The MISSILETRAINER class --- @type MISSILETRAINER --- @field Set#SET_CLIENT DBClients --- @extends Base#BASE -MISSILETRAINER = { - ClassName = "MISSILETRAINER", - TrackingMissiles = {}, -} - -function MISSILETRAINER._Alive( Client, self ) - - if self.Briefing then - Client:Message( self.Briefing, 15, "Trainer" ) - end - - if self.MenusOnOff == true then - Client:Message( "Use the 'Radio Menu' -> 'Other (F10)' -> 'Missile Trainer' menu options to change the Missile Trainer settings (for all players).", 15, "Trainer" ) - - Client.MainMenu = MENU_CLIENT:New( Client, "Missile Trainer", nil ) -- Menu#MENU_CLIENT - - Client.MenuMessages = MENU_CLIENT:New( Client, "Messages", Client.MainMenu ) - Client.MenuOn = MENU_CLIENT_COMMAND:New( Client, "Messages On", Client.MenuMessages, self._MenuMessages, { MenuSelf = self, MessagesOnOff = true } ) - Client.MenuOff = MENU_CLIENT_COMMAND:New( Client, "Messages Off", Client.MenuMessages, self._MenuMessages, { MenuSelf = self, MessagesOnOff = false } ) - - Client.MenuTracking = MENU_CLIENT:New( Client, "Tracking", Client.MainMenu ) - Client.MenuTrackingToAll = MENU_CLIENT_COMMAND:New( Client, "To All", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, TrackingToAll = true } ) - Client.MenuTrackingToTarget = MENU_CLIENT_COMMAND:New( Client, "To Target", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, TrackingToAll = false } ) - Client.MenuTrackOn = MENU_CLIENT_COMMAND:New( Client, "Tracking On", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, TrackingOnOff = true } ) - Client.MenuTrackOff = MENU_CLIENT_COMMAND:New( Client, "Tracking Off", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, TrackingOnOff = false } ) - Client.MenuTrackIncrease = MENU_CLIENT_COMMAND:New( Client, "Frequency Increase", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, TrackingFrequency = -1 } ) - Client.MenuTrackDecrease = MENU_CLIENT_COMMAND:New( Client, "Frequency Decrease", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, TrackingFrequency = 1 } ) - - Client.MenuAlerts = MENU_CLIENT:New( Client, "Alerts", Client.MainMenu ) - Client.MenuAlertsToAll = MENU_CLIENT_COMMAND:New( Client, "To All", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsToAll = true } ) - Client.MenuAlertsToTarget = MENU_CLIENT_COMMAND:New( Client, "To Target", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsToAll = false } ) - Client.MenuHitsOn = MENU_CLIENT_COMMAND:New( Client, "Hits On", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsHitsOnOff = true } ) - Client.MenuHitsOff = MENU_CLIENT_COMMAND:New( Client, "Hits Off", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsHitsOnOff = false } ) - Client.MenuLaunchesOn = MENU_CLIENT_COMMAND:New( Client, "Launches On", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsLaunchesOnOff = true } ) - Client.MenuLaunchesOff = MENU_CLIENT_COMMAND:New( Client, "Launches Off", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsLaunchesOnOff = false } ) - - Client.MenuDetails = MENU_CLIENT:New( Client, "Details", Client.MainMenu ) - Client.MenuDetailsDistanceOn = MENU_CLIENT_COMMAND:New( Client, "Range On", Client.MenuDetails, self._MenuMessages, { MenuSelf = self, DetailsRangeOnOff = true } ) - Client.MenuDetailsDistanceOff = MENU_CLIENT_COMMAND:New( Client, "Range Off", Client.MenuDetails, self._MenuMessages, { MenuSelf = self, DetailsRangeOnOff = false } ) - Client.MenuDetailsBearingOn = MENU_CLIENT_COMMAND:New( Client, "Bearing On", Client.MenuDetails, self._MenuMessages, { MenuSelf = self, DetailsBearingOnOff = true } ) - Client.MenuDetailsBearingOff = MENU_CLIENT_COMMAND:New( Client, "Bearing Off", Client.MenuDetails, self._MenuMessages, { MenuSelf = self, DetailsBearingOnOff = false } ) - - Client.MenuDistance = MENU_CLIENT:New( Client, "Set distance to plane", Client.MainMenu ) - Client.MenuDistance50 = MENU_CLIENT_COMMAND:New( Client, "50 meter", Client.MenuDistance, self._MenuMessages, { MenuSelf = self, Distance = 50 / 1000 } ) - Client.MenuDistance100 = MENU_CLIENT_COMMAND:New( Client, "100 meter", Client.MenuDistance, self._MenuMessages, { MenuSelf = self, Distance = 100 / 1000 } ) - Client.MenuDistance150 = MENU_CLIENT_COMMAND:New( Client, "150 meter", Client.MenuDistance, self._MenuMessages, { MenuSelf = self, Distance = 150 / 1000 } ) - Client.MenuDistance200 = MENU_CLIENT_COMMAND:New( Client, "200 meter", Client.MenuDistance, self._MenuMessages, { MenuSelf = self, Distance = 200 / 1000 } ) - else - if Client.MainMenu then - Client.MainMenu:Remove() - end - end - - local ClientID = Client:GetID() - self:T( ClientID ) - if not self.TrackingMissiles[ClientID] then - self.TrackingMissiles[ClientID] = {} - end - self.TrackingMissiles[ClientID].Client = Client - if not self.TrackingMissiles[ClientID].MissileData then - self.TrackingMissiles[ClientID].MissileData = {} - end -end - ---- Creates the main object which is handling missile tracking. --- When a missile is fired a SCHEDULER is set off that follows the missile. When near a certain a client player, the missile will be destroyed. --- @param #MISSILETRAINER self --- @param #number Distance The distance in meters when a tracked missile needs to be destroyed when close to a player. --- @param #string Briefing (Optional) Will show a text to the players when starting their mission. Can be used for briefing purposes. --- @return #MISSILETRAINER -function MISSILETRAINER:New( Distance, Briefing ) - local self = BASE:Inherit( self, BASE:New() ) - self:F( Distance ) - - if Briefing then - self.Briefing = Briefing - end - - self.Schedulers = {} - self.SchedulerID = 0 - - self.MessageInterval = 2 - self.MessageLastTime = timer.getTime() - - self.Distance = Distance / 1000 - - _EVENTDISPATCHER:OnShot( self._EventShot, self ) - - self.DBClients = SET_CLIENT:New():FilterStart() - - --- for ClientID, Client in pairs( self.DBClients.Database ) do --- self:E( "ForEach:" .. Client.UnitName ) --- Client:Alive( self._Alive, self ) --- end --- - self.DBClients:ForEachClient( - function( Client ) - self:E( "ForEach:" .. Client.UnitName ) - Client:Alive( self._Alive, self ) - end - ) - - - --- self.DB:ForEachClient( --- --- @param Client#CLIENT Client --- function( Client ) --- --- ... actions ... --- --- end --- ) - - self.MessagesOnOff = true - - self.TrackingToAll = false - self.TrackingOnOff = true - self.TrackingFrequency = 3 - - self.AlertsToAll = true - self.AlertsHitsOnOff = true - self.AlertsLaunchesOnOff = true - - self.DetailsRangeOnOff = true - self.DetailsBearingOnOff = true - - self.MenusOnOff = true - - self.TrackingMissiles = {} - - self.TrackingScheduler = SCHEDULER:New( self, self._TrackMissiles, {}, 0.5, 0.05, 0 ) - - return self -end - --- Initialization methods. - - - ---- Sets by default the display of any message to be ON or OFF. --- @param #MISSILETRAINER self --- @param #boolean MessagesOnOff true or false --- @return #MISSILETRAINER self -function MISSILETRAINER:InitMessagesOnOff( MessagesOnOff ) - self:F( MessagesOnOff ) - - self.MessagesOnOff = MessagesOnOff - if self.MessagesOnOff == true then - MESSAGE:New( "Messages ON", 15, "Menu" ):ToAll() - else - MESSAGE:New( "Messages OFF", 15, "Menu" ):ToAll() - end - - return self -end - ---- Sets by default the missile tracking report for all players or only for those missiles targetted to you. --- @param #MISSILETRAINER self --- @param #boolean TrackingToAll true or false --- @return #MISSILETRAINER self -function MISSILETRAINER:InitTrackingToAll( TrackingToAll ) - self:F( TrackingToAll ) - - self.TrackingToAll = TrackingToAll - if self.TrackingToAll == true then - MESSAGE:New( "Missile tracking to all players ON", 15, "Menu" ):ToAll() - else - MESSAGE:New( "Missile tracking to all players OFF", 15, "Menu" ):ToAll() - end - - return self -end - ---- Sets by default the display of missile tracking report to be ON or OFF. --- @param #MISSILETRAINER self --- @param #boolean TrackingOnOff true or false --- @return #MISSILETRAINER self -function MISSILETRAINER:InitTrackingOnOff( TrackingOnOff ) - self:F( TrackingOnOff ) - - self.TrackingOnOff = TrackingOnOff - if self.TrackingOnOff == true then - MESSAGE:New( "Missile tracking ON", 15, "Menu" ):ToAll() - else - MESSAGE:New( "Missile tracking OFF", 15, "Menu" ):ToAll() - end - - return self -end - ---- Increases, decreases the missile tracking message display frequency with the provided time interval in seconds. --- The default frequency is a 3 second interval, so the Tracking Frequency parameter specifies the increase or decrease from the default 3 seconds or the last frequency update. --- @param #MISSILETRAINER self --- @param #number TrackingFrequency Provide a negative or positive value in seconds to incraese or decrease the display frequency. --- @return #MISSILETRAINER self -function MISSILETRAINER:InitTrackingFrequency( TrackingFrequency ) - self:F( TrackingFrequency ) - - self.TrackingFrequency = self.TrackingFrequency + TrackingFrequency - if self.TrackingFrequency < 0.5 then - self.TrackingFrequency = 0.5 - end - if self.TrackingFrequency then - MESSAGE:New( "Missile tracking frequency is " .. self.TrackingFrequency .. " seconds.", 15, "Menu" ):ToAll() - end - - return self -end - ---- Sets by default the display of alerts to be shown to all players or only to you. --- @param #MISSILETRAINER self --- @param #boolean AlertsToAll true or false --- @return #MISSILETRAINER self -function MISSILETRAINER:InitAlertsToAll( AlertsToAll ) - self:F( AlertsToAll ) - - self.AlertsToAll = AlertsToAll - if self.AlertsToAll == true then - MESSAGE:New( "Alerts to all players ON", 15, "Menu" ):ToAll() - else - MESSAGE:New( "Alerts to all players OFF", 15, "Menu" ):ToAll() - end - - return self -end - ---- Sets by default the display of hit alerts ON or OFF. --- @param #MISSILETRAINER self --- @param #boolean AlertsHitsOnOff true or false --- @return #MISSILETRAINER self -function MISSILETRAINER:InitAlertsHitsOnOff( AlertsHitsOnOff ) - self:F( AlertsHitsOnOff ) - - self.AlertsHitsOnOff = AlertsHitsOnOff - if self.AlertsHitsOnOff == true then - MESSAGE:New( "Alerts Hits ON", 15, "Menu" ):ToAll() - else - MESSAGE:New( "Alerts Hits OFF", 15, "Menu" ):ToAll() - end - - return self -end - ---- Sets by default the display of launch alerts ON or OFF. --- @param #MISSILETRAINER self --- @param #boolean AlertsLaunchesOnOff true or false --- @return #MISSILETRAINER self -function MISSILETRAINER:InitAlertsLaunchesOnOff( AlertsLaunchesOnOff ) - self:F( AlertsLaunchesOnOff ) - - self.AlertsLaunchesOnOff = AlertsLaunchesOnOff - if self.AlertsLaunchesOnOff == true then - MESSAGE:New( "Alerts Launches ON", 15, "Menu" ):ToAll() - else - MESSAGE:New( "Alerts Launches OFF", 15, "Menu" ):ToAll() - end - - return self -end - ---- Sets by default the display of range information of missiles ON of OFF. --- @param #MISSILETRAINER self --- @param #boolean DetailsRangeOnOff true or false --- @return #MISSILETRAINER self -function MISSILETRAINER:InitRangeOnOff( DetailsRangeOnOff ) - self:F( DetailsRangeOnOff ) - - self.DetailsRangeOnOff = DetailsRangeOnOff - if self.DetailsRangeOnOff == true then - MESSAGE:New( "Range display ON", 15, "Menu" ):ToAll() - else - MESSAGE:New( "Range display OFF", 15, "Menu" ):ToAll() - end - - return self -end - ---- Sets by default the display of bearing information of missiles ON of OFF. --- @param #MISSILETRAINER self --- @param #boolean DetailsBearingOnOff true or false --- @return #MISSILETRAINER self -function MISSILETRAINER:InitBearingOnOff( DetailsBearingOnOff ) - self:F( DetailsBearingOnOff ) - - self.DetailsBearingOnOff = DetailsBearingOnOff - if self.DetailsBearingOnOff == true then - MESSAGE:New( "Bearing display OFF", 15, "Menu" ):ToAll() - else - MESSAGE:New( "Bearing display OFF", 15, "Menu" ):ToAll() - end - - return self -end - ---- Enables / Disables the menus. --- @param #MISSILETRAINER self --- @param #boolean MenusOnOff true or false --- @return #MISSILETRAINER self -function MISSILETRAINER:InitMenusOnOff( MenusOnOff ) - self:F( MenusOnOff ) - - self.MenusOnOff = MenusOnOff - if self.MenusOnOff == true then - MESSAGE:New( "Menus are ENABLED (only when a player rejoins a slot)", 15, "Menu" ):ToAll() - else - MESSAGE:New( "Menus are DISABLED", 15, "Menu" ):ToAll() - end - - return self -end - - --- Menu functions - -function MISSILETRAINER._MenuMessages( MenuParameters ) - - local self = MenuParameters.MenuSelf - - if MenuParameters.MessagesOnOff ~= nil then - self:InitMessagesOnOff( MenuParameters.MessagesOnOff ) - end - - if MenuParameters.TrackingToAll ~= nil then - self:InitTrackingToAll( MenuParameters.TrackingToAll ) - end - - if MenuParameters.TrackingOnOff ~= nil then - self:InitTrackingOnOff( MenuParameters.TrackingOnOff ) - end - - if MenuParameters.TrackingFrequency ~= nil then - self:InitTrackingFrequency( MenuParameters.TrackingFrequency ) - end - - if MenuParameters.AlertsToAll ~= nil then - self:InitAlertsToAll( MenuParameters.AlertsToAll ) - end - - if MenuParameters.AlertsHitsOnOff ~= nil then - self:InitAlertsHitsOnOff( MenuParameters.AlertsHitsOnOff ) - end - - if MenuParameters.AlertsLaunchesOnOff ~= nil then - self:InitAlertsLaunchesOnOff( MenuParameters.AlertsLaunchesOnOff ) - end - - if MenuParameters.DetailsRangeOnOff ~= nil then - self:InitRangeOnOff( MenuParameters.DetailsRangeOnOff ) - end - - if MenuParameters.DetailsBearingOnOff ~= nil then - self:InitBearingOnOff( MenuParameters.DetailsBearingOnOff ) - end - - if MenuParameters.Distance ~= nil then - self.Distance = MenuParameters.Distance - MESSAGE:New( "Hit detection distance set to " .. self.Distance .. " meters", 15, "Menu" ):ToAll() - end - -end - ---- Detects if an SA site was shot with an anti radiation missile. In this case, take evasive actions based on the skill level set within the ME. --- @param #MISSILETRAINER self --- @param Event#EVENTDATA Event -function MISSILETRAINER:_EventShot( Event ) - self:F( { Event } ) - - local TrainerSourceDCSUnit = Event.IniDCSUnit - local TrainerSourceDCSUnitName = Event.IniDCSUnitName - local TrainerWeapon = Event.Weapon -- Identify the weapon fired - local TrainerWeaponName = Event.WeaponName -- return weapon type - - self:T( "Missile Launched = " .. TrainerWeaponName ) - - local TrainerTargetDCSUnit = TrainerWeapon:getTarget() -- Identify target - local TrainerTargetDCSUnitName = Unit.getName( TrainerTargetDCSUnit ) - local TrainerTargetSkill = _DATABASE.Templates.Units[TrainerTargetDCSUnitName].Template.skill - - self:T(TrainerTargetDCSUnitName ) - - local Client = self.DBClients:FindClient( TrainerTargetDCSUnitName ) - if Client then - - local TrainerSourceUnit = UNIT:Find( TrainerSourceDCSUnit ) - local TrainerTargetUnit = UNIT:Find( TrainerTargetDCSUnit ) - - if self.MessagesOnOff == true and self.AlertsLaunchesOnOff == true then - - local Message = MESSAGE:New( - string.format( "%s launched a %s", - TrainerSourceUnit:GetTypeName(), - TrainerWeaponName - ) .. self:_AddRange( Client, TrainerWeapon ) .. self:_AddBearing( Client, TrainerWeapon ), 5, "Launch Alert" ) - - if self.AlertsToAll then - Message:ToAll() - else - Message:ToClient( Client ) - end - end - - local ClientID = Client:GetID() - self:T( ClientID ) - local MissileData = {} - MissileData.TrainerSourceUnit = TrainerSourceUnit - MissileData.TrainerWeapon = TrainerWeapon - MissileData.TrainerTargetUnit = TrainerTargetUnit - MissileData.TrainerWeaponTypeName = TrainerWeapon:getTypeName() - MissileData.TrainerWeaponLaunched = true - table.insert( self.TrackingMissiles[ClientID].MissileData, MissileData ) - --self:T( self.TrackingMissiles ) - end -end - -function MISSILETRAINER:_AddRange( Client, TrainerWeapon ) - - local RangeText = "" - - if self.DetailsRangeOnOff then - - local PositionMissile = TrainerWeapon:getPoint() - local PositionTarget = Client:GetPointVec3() - - local Range = ( ( PositionMissile.x - PositionTarget.x )^2 + - ( PositionMissile.y - PositionTarget.y )^2 + - ( PositionMissile.z - PositionTarget.z )^2 - ) ^ 0.5 / 1000 - - RangeText = string.format( ", at %4.2fkm", Range ) - end - - return RangeText -end - -function MISSILETRAINER:_AddBearing( Client, TrainerWeapon ) - - local BearingText = "" - - if self.DetailsBearingOnOff then - - local PositionMissile = TrainerWeapon:getPoint() - local PositionTarget = Client:GetPointVec3() - - self:T2( { PositionTarget, PositionMissile }) - - local DirectionVector = { x = PositionMissile.x - PositionTarget.x, y = PositionMissile.y - PositionTarget.y, z = PositionMissile.z - PositionTarget.z } - local DirectionRadians = math.atan2( DirectionVector.z, DirectionVector.x ) - --DirectionRadians = DirectionRadians + routines.getNorthCorrection( PositionTarget ) - if DirectionRadians < 0 then - DirectionRadians = DirectionRadians + 2 * math.pi - end - local DirectionDegrees = DirectionRadians * 180 / math.pi - - BearingText = string.format( ", %d degrees", DirectionDegrees ) - end - - return BearingText -end - - -function MISSILETRAINER:_TrackMissiles() - self:F2() - - - local ShowMessages = false - if self.MessagesOnOff and self.MessageLastTime + self.TrackingFrequency <= timer.getTime() then - self.MessageLastTime = timer.getTime() - ShowMessages = true - end - - -- ALERTS PART - - -- Loop for all Player Clients to check the alerts and deletion of missiles. - for ClientDataID, ClientData in pairs( self.TrackingMissiles ) do - - local Client = ClientData.Client - self:T2( { Client:GetName() } ) - - for MissileDataID, MissileData in pairs( ClientData.MissileData ) do - self:T3( MissileDataID ) - - local TrainerSourceUnit = MissileData.TrainerSourceUnit - local TrainerWeapon = MissileData.TrainerWeapon - local TrainerTargetUnit = MissileData.TrainerTargetUnit - local TrainerWeaponTypeName = MissileData.TrainerWeaponTypeName - local TrainerWeaponLaunched = MissileData.TrainerWeaponLaunched - - if Client and Client:IsAlive() and TrainerSourceUnit and TrainerSourceUnit:IsAlive() and TrainerWeapon and TrainerWeapon:isExist() and TrainerTargetUnit and TrainerTargetUnit:IsAlive() then - local PositionMissile = TrainerWeapon:getPosition().p - local PositionTarget = Client:GetPointVec3() - - local Distance = ( ( PositionMissile.x - PositionTarget.x )^2 + - ( PositionMissile.y - PositionTarget.y )^2 + - ( PositionMissile.z - PositionTarget.z )^2 - ) ^ 0.5 / 1000 - - if Distance <= self.Distance then - -- Hit alert - TrainerWeapon:destroy() - if self.MessagesOnOff == true and self.AlertsHitsOnOff == true then - - self:T( "killed" ) - - local Message = MESSAGE:New( - string.format( "%s launched by %s killed %s", - TrainerWeapon:getTypeName(), - TrainerSourceUnit:GetTypeName(), - TrainerTargetUnit:GetPlayerName() - ), 15, "Hit Alert" ) - - if self.AlertsToAll == true then - Message:ToAll() - else - Message:ToClient( Client ) - end - - MissileData = nil - table.remove( ClientData.MissileData, MissileDataID ) - self:T(ClientData.MissileData) - end - end - else - if not ( TrainerWeapon and TrainerWeapon:isExist() ) then - if self.MessagesOnOff == true and self.AlertsLaunchesOnOff == true then - -- Weapon does not exist anymore. Delete from Table - local Message = MESSAGE:New( - string.format( "%s launched by %s self destructed!", - TrainerWeaponTypeName, - TrainerSourceUnit:GetTypeName() - ), 5, "Tracking" ) - - if self.AlertsToAll == true then - Message:ToAll() - else - Message:ToClient( Client ) - end - end - MissileData = nil - table.remove( ClientData.MissileData, MissileDataID ) - self:T( ClientData.MissileData ) - end - end - end - end - - if ShowMessages == true and self.MessagesOnOff == true and self.TrackingOnOff == true then -- Only do this when tracking information needs to be displayed. - - -- TRACKING PART - - -- For the current client, the missile range and bearing details are displayed To the Player Client. - -- For the other clients, the missile range and bearing details are displayed To the other Player Clients. - -- To achieve this, a cross loop is done for each Player Client <-> Other Player Client missile information. - - -- Main Player Client loop - for ClientDataID, ClientData in pairs( self.TrackingMissiles ) do - - local Client = ClientData.Client - self:T2( { Client:GetName() } ) - - - ClientData.MessageToClient = "" - ClientData.MessageToAll = "" - - -- Other Players Client loop - for TrackingDataID, TrackingData in pairs( self.TrackingMissiles ) do - - for MissileDataID, MissileData in pairs( TrackingData.MissileData ) do - self:T3( MissileDataID ) - - local TrainerSourceUnit = MissileData.TrainerSourceUnit - local TrainerWeapon = MissileData.TrainerWeapon - local TrainerTargetUnit = MissileData.TrainerTargetUnit - local TrainerWeaponTypeName = MissileData.TrainerWeaponTypeName - local TrainerWeaponLaunched = MissileData.TrainerWeaponLaunched - - if Client and Client:IsAlive() and TrainerSourceUnit and TrainerSourceUnit:IsAlive() and TrainerWeapon and TrainerWeapon:isExist() and TrainerTargetUnit and TrainerTargetUnit:IsAlive() then - - if ShowMessages == true then - local TrackingTo - TrackingTo = string.format( " -> %s", - TrainerWeaponTypeName - ) - - if ClientDataID == TrackingDataID then - if ClientData.MessageToClient == "" then - ClientData.MessageToClient = "Missiles to You:\n" - end - ClientData.MessageToClient = ClientData.MessageToClient .. TrackingTo .. self:_AddRange( ClientData.Client, TrainerWeapon ) .. self:_AddBearing( ClientData.Client, TrainerWeapon ) .. "\n" - else - if self.TrackingToAll == true then - if ClientData.MessageToAll == "" then - ClientData.MessageToAll = "Missiles to other Players:\n" - end - ClientData.MessageToAll = ClientData.MessageToAll .. TrackingTo .. self:_AddRange( ClientData.Client, TrainerWeapon ) .. self:_AddBearing( ClientData.Client, TrainerWeapon ) .. " ( " .. TrainerTargetUnit:GetPlayerName() .. " )\n" - end - end - end - end - end - end - - -- Once the Player Client and the Other Player Client tracking messages are prepared, show them. - if ClientData.MessageToClient ~= "" or ClientData.MessageToAll ~= "" then - local Message = MESSAGE:New( ClientData.MessageToClient .. ClientData.MessageToAll, 1, "Tracking" ):ToClient( Client ) - end - end - end - - return true -end ---- This module contains the PATROLZONE class. --- --- === --- --- 1) @{Patrol#PATROLZONE} class, extends @{Base#BASE} --- =================================================== --- The @{Patrol#PATROLZONE} class implements the core functions to patrol a @{Zone}. --- --- 1.1) PATROLZONE constructor: --- ---------------------------- --- @{PatrolZone#PATROLZONE.New}(): Creates a new PATROLZONE object. --- --- 1.2) Modify the PATROLZONE parameters: --- -------------------------------------- --- The following methods are available to modify the parameters of a PATROLZONE object: --- --- * @{PatrolZone#PATROLZONE.SetGroup}(): Set the AI Patrol Group. --- * @{PatrolZone#PATROLZONE.SetSpeed}(): Set the patrol speed of the AI, for the next patrol. --- * @{PatrolZone#PATROLZONE.SetAltitude}(): Set altitude of the AI, for the next patrol. --- --- 1.3) Manage the out of fuel in the PATROLZONE: --- ---------------------------------------------- --- When the PatrolGroup is out of fuel, it is required that a new PatrolGroup is started, before the old PatrolGroup can return to the home base. --- Therefore, with a parameter and a calculation of the distance to the home base, the fuel treshold is calculated. --- When the fuel treshold is reached, the PatrolGroup will continue for a given time its patrol task in orbit, while a new PatrolGroup is targetted to the PATROLZONE. --- Once the time is finished, the old PatrolGroup will return to the base. --- Use the method @{PatrolZone#PATROLZONE.ManageFuel}() to have this proces in place. --- --- === --- --- @module PatrolZone --- @author FlightControl - - ---- PATROLZONE class --- @type PATROLZONE --- @field Group#GROUP PatrolGroup The @{Group} patrolling. --- @field Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed. --- @field DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. --- @field DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. --- @field DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Group} in km/h. --- @field DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Group} in km/h. --- @extends Base#BASE -PATROLZONE = { - ClassName = "PATROLZONE", -} - ---- Creates a new PATROLZONE object, taking a @{Group} object as a parameter. The GROUP needs to be alive. --- @param #PATROLZONE self --- @param Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed. --- @param DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. --- @param DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. --- @param DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Group} in km/h. --- @param DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Group} in km/h. --- @return #PATROLZONE self --- @usage --- -- Define a new PATROLZONE Object. This PatrolArea will patrol a group within PatrolZone between 3000 and 6000 meters, with a variying speed between 600 and 900 km/h. --- PatrolZone = ZONE:New( 'PatrolZone' ) --- PatrolGroup = GROUP:FindByName( "Patrol Group" ) --- PatrolArea = PATROLZONE:New( PatrolGroup, PatrolZone, 3000, 6000, 600, 900 ) -function PATROLZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed ) - - -- Inherits from BASE - local self = BASE:Inherit( self, BASE:New() ) - - self.PatrolZone = PatrolZone - self.PatrolFloorAltitude = PatrolFloorAltitude - self.PatrolCeilingAltitude = PatrolCeilingAltitude - self.PatrolMinSpeed = PatrolMinSpeed - self.PatrolMaxSpeed = PatrolMaxSpeed - - return self -end - ---- Set the @{Group} to act as the Patroller. --- @param #PATROLZONE self --- @param Group#GROUP PatrolGroup The @{Group} patrolling. --- @return #PATROLZONE self -function PATROLZONE:SetGroup( PatrolGroup ) - - self.PatrolGroup = PatrolGroup - self.PatrolGroupTemplateName = PatrolGroup:GetName() - self:NewPatrolRoute() - - if not self.PatrolOutOfFuelMonitor then - self.PatrolOutOfFuelMonitor = SCHEDULER:New( nil, _MonitorOutOfFuelScheduled, { self }, 1, 120, 0 ) - self.SpawnPatrolGroup = SPAWN:New( self.PatrolGroupTemplateName ) - end - - return self -end - ---- Sets (modifies) the minimum and maximum speed of the patrol. --- @param #PATROLZONE self --- @param DCSTypes#Speed PatrolMinSpeed The minimum speed of the @{Group} in km/h. --- @param DCSTypes#Speed PatrolMaxSpeed The maximum speed of the @{Group} in km/h. --- @return #PATROLZONE self -function PATROLZONE:SetSpeed( PatrolMinSpeed, PatrolMaxSpeed ) - self:F2( { PatrolMinSpeed, PatrolMaxSpeed } ) - - self.PatrolMinSpeed = PatrolMinSpeed - self.PatrolMaxSpeed = PatrolMaxSpeed -end - ---- Sets the floor and ceiling altitude of the patrol. --- @param #PATROLZONE self --- @param DCSTypes#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. --- @param DCSTypes#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. --- @return #PATROLZONE self -function PATROLZONE:SetAltitude( PatrolFloorAltitude, PatrolCeilingAltitude ) - self:F2( { PatrolFloorAltitude, PatrolCeilingAltitude } ) - - self.PatrolFloorAltitude = PatrolFloorAltitude - self.PatrolCeilingAltitude = PatrolCeilingAltitude -end - - - ---- @param Group#GROUP PatrolGroup -function _NewPatrolRoute( PatrolGroup ) - - PatrolGroup:T( "NewPatrolRoute" ) - local PatrolZone = PatrolGroup:GetState( PatrolGroup, "PatrolZone" ) -- PatrolZone#PATROLZONE - PatrolZone:NewPatrolRoute() -end - ---- Defines a new patrol route using the @{PatrolZone} parameters and settings. --- @param #PATROLZONE self --- @return #PATROLZONE self -function PATROLZONE:NewPatrolRoute() - - self:F2() - - local PatrolRoute = {} - - if self.PatrolGroup:IsAlive() then - --- Determine if the PatrolGroup is within the PatrolZone. - -- If not, make a waypoint within the to that the PatrolGroup will fly at maximum speed to that point. - --- --- Calculate the current route point. --- local CurrentVec2 = self.PatrolGroup:GetVec2() --- local CurrentAltitude = self.PatrolGroup:GetUnit(1):GetAltitude() --- local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) --- local CurrentRoutePoint = CurrentPointVec3:RoutePointAir( --- POINT_VEC3.RoutePointAltType.BARO, --- POINT_VEC3.RoutePointType.TurningPoint, --- POINT_VEC3.RoutePointAction.TurningPoint, --- ToPatrolZoneSpeed, --- true --- ) --- --- PatrolRoute[#PatrolRoute+1] = CurrentRoutePoint - - self:T2( PatrolRoute ) - - if self.PatrolGroup:IsNotInZone( self.PatrolZone ) then - --- Find a random 2D point in PatrolZone. - local ToPatrolZoneVec2 = self.PatrolZone:GetRandomVec2() - self:T2( ToPatrolZoneVec2 ) - - --- Define Speed and Altitude. - local ToPatrolZoneAltitude = math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ) - local ToPatrolZoneSpeed = self.PatrolMaxSpeed - self:T2( ToPatrolZoneSpeed ) - - --- Obtain a 3D @{Point} from the 2D point + altitude. - local ToPatrolZonePointVec3 = POINT_VEC3:New( ToPatrolZoneVec2.x, ToPatrolZoneAltitude, ToPatrolZoneVec2.y ) - - --- Create a route point of type air. - local ToPatrolZoneRoutePoint = ToPatrolZonePointVec3:RoutePointAir( - POINT_VEC3.RoutePointAltType.BARO, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - ToPatrolZoneSpeed, - true - ) - - PatrolRoute[#PatrolRoute+1] = ToPatrolZoneRoutePoint - - end - - --- Define a random point in the @{Zone}. The AI will fly to that point within the zone. - - --- Find a random 2D point in PatrolZone. - local ToTargetVec2 = self.PatrolZone:GetRandomVec2() - self:T2( ToTargetVec2 ) - - --- Define Speed and Altitude. - local ToTargetAltitude = math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ) - local ToTargetSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed ) - self:T2( { self.PatrolMinSpeed, self.PatrolMaxSpeed, ToTargetSpeed } ) - - --- Obtain a 3D @{Point} from the 2D point + altitude. - local ToTargetPointVec3 = POINT_VEC3:New( ToTargetVec2.x, ToTargetAltitude, ToTargetVec2.y ) - - --- Create a route point of type air. - local ToTargetRoutePoint = ToTargetPointVec3:RoutePointAir( - POINT_VEC3.RoutePointAltType.BARO, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - ToTargetSpeed, - true - ) - - --ToTargetPointVec3:SmokeRed() - - PatrolRoute[#PatrolRoute+1] = ToTargetRoutePoint - - --- Now we're going to do something special, we're going to call a function from a waypoint action at the PatrolGroup... - self.PatrolGroup:WayPointInitialize( PatrolRoute ) - - --- Do a trick, link the NewPatrolRoute function of the PATROLGROUP object to the PatrolGroup in a temporary variable ... - self.PatrolGroup:SetState( self.PatrolGroup, "PatrolZone", self ) - self.PatrolGroup:WayPointFunction( #PatrolRoute, 1, "_NewPatrolRoute" ) - - --- NOW ROUTE THE GROUP! - self.PatrolGroup:WayPointExecute( 1, 2 ) - end - -end - ---- When the PatrolGroup is out of fuel, it is required that a new PatrolGroup is started, before the old PatrolGroup can return to the home base. --- Therefore, with a parameter and a calculation of the distance to the home base, the fuel treshold is calculated. --- When the fuel treshold is reached, the PatrolGroup will continue for a given time its patrol task in orbit, while a new PatrolGroup is targetted to the PATROLZONE. --- Once the time is finished, the old PatrolGroup will return to the base. --- @param #PATROLZONE self --- @param #number PatrolFuelTresholdPercentage The treshold in percentage (between 0 and 1) when the PatrolGroup is considered to get out of fuel. --- @param #number PatrolOutOfFuelOrbitTime The amount of seconds the out of fuel PatrolGroup will orbit before returning to the base. --- @return #PATROLZONE self -function PATROLZONE:ManageFuel( PatrolFuelTresholdPercentage, PatrolOutOfFuelOrbitTime ) - - self.PatrolManageFuel = true - self.PatrolFuelTresholdPercentage = PatrolFuelTresholdPercentage - self.PatrolOutOfFuelOrbitTime = PatrolOutOfFuelOrbitTime - - if self.PatrolGroup then - self.PatrolOutOfFuelMonitor = SCHEDULER:New( self, self._MonitorOutOfFuelScheduled, {}, 1, 120, 0 ) - self.SpawnPatrolGroup = SPAWN:New( self.PatrolGroupTemplateName ) - end - return self -end - ---- @param #PATROLZONE self -function _MonitorOutOfFuelScheduled( self ) - self:F2( "_MonitorOutOfFuelScheduled" ) - - if self.PatrolGroup and self.PatrolGroup:IsAlive() then - - local Fuel = self.PatrolGroup:GetUnit(1):GetFuel() - if Fuel < self.PatrolFuelTresholdPercentage then - local OldPatrolGroup = self.PatrolGroup - local PatrolGroupTemplate = self.PatrolGroup:GetTemplate() - - local OrbitTask = OldPatrolGroup:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed ) - local TimedOrbitTask = OldPatrolGroup:TaskControlled( OrbitTask, OldPatrolGroup:TaskCondition(nil,nil,nil,nil,self.PatrolOutOfFuelOrbitTime,nil ) ) - OldPatrolGroup:SetTask( TimedOrbitTask, 10 ) - - local NewPatrolGroup = self.SpawnPatrolGroup:Spawn() - self.PatrolGroup = NewPatrolGroup - self:NewPatrolRoute() - end - else - self.PatrolOutOfFuelMonitor:Stop() - end -end--- This module contains the AIBALANCER class. --- --- === --- --- 1) @{AIBalancer#AIBALANCER} class, extends @{Base#BASE} --- ================================================ --- The @{AIBalancer#AIBALANCER} class controls the dynamic spawning of AI GROUPS depending on a SET_CLIENT. --- There will be as many AI GROUPS spawned as there at CLIENTS in SET_CLIENT not spawned. --- --- 1.1) AIBALANCER construction method: --- ------------------------------------ --- Create a new AIBALANCER object with the @{#AIBALANCER.New} method: --- --- * @{#AIBALANCER.New}: Creates a new AIBALANCER object. --- --- 1.2) AIBALANCER returns AI to Airbases: --- --------------------------------------- --- You can configure to have the AI to return to: --- --- * @{#AIBALANCER.ReturnToHomeAirbase}: Returns the AI to the home @{Airbase#AIRBASE}. --- * @{#AIBALANCER.ReturnToNearestAirbases}: Returns the AI to the nearest friendly @{Airbase#AIRBASE}. --- --- 1.3) AIBALANCER allows AI to patrol specific zones: --- --------------------------------------------------- --- Use @{AIBalancer#AIBALANCER.SetPatrolZone}() to specify a zone where the AI needs to patrol. --- --- --- === --- --- CREDITS --- ======= --- **Dutch_Baron (James)** Who you can search on the Eagle Dynamics Forums. --- Working together with James has resulted in the creation of the AIBALANCER class. --- James has shared his ideas on balancing AI with air units, and together we made a first design which you can use now :-) --- --- **SNAFU** --- Had a couple of mails with the guys to validate, if the same concept in the GCI/CAP script could be reworked within MOOSE. --- None of the script code has been used however within the new AIBALANCER moose class. --- --- @module AIBalancer --- @author FlightControl - ---- AIBALANCER class --- @type AIBALANCER --- @field Set#SET_CLIENT SetClient --- @field Spawn#SPAWN SpawnAI --- @field #boolean ToNearestAirbase --- @field Set#SET_AIRBASE ReturnAirbaseSet --- @field DCSTypes#Distance ReturnTresholdRange --- @field #boolean ToHomeAirbase --- @field PatrolZone#PATROLZONE PatrolZone --- @extends Base#BASE -AIBALANCER = { - ClassName = "AIBALANCER", - PatrolZones = {}, - AIGroups = {}, -} - ---- Creates a new AIBALANCER object, building a set of units belonging to a coalitions, categories, countries, types or with defined prefix names. --- @param #AIBALANCER self --- @param SetClient A SET_CLIENT object that will contain the CLIENT objects to be monitored if they are alive or not (joined by a player). --- @param SpawnAI A SPAWN object that will spawn the AI units required, balancing the SetClient. --- @return #AIBALANCER self -function AIBALANCER:New( SetClient, SpawnAI ) - - -- Inherits from BASE - local self = BASE:Inherit( self, BASE:New() ) - - self.SetClient = SetClient - if type( SpawnAI ) == "table" then - if SpawnAI.ClassName and SpawnAI.ClassName == "SPAWN" then - self.SpawnAI = { SpawnAI } - else - local SpawnObjects = true - for SpawnObjectID, SpawnObject in pairs( SpawnAI ) do - if SpawnObject.ClassName and SpawnObject.ClassName == "SPAWN" then - self:E( SpawnObject.ClassName ) - else - self:E( "other object" ) - SpawnObjects = false - end - end - if SpawnObjects == true then - self.SpawnAI = SpawnAI - else - error( "No SPAWN object given in parameter SpawnAI, either as a single object or as a table of objects!" ) - end - end - end - - self.ToNearestAirbase = false - self.ReturnHomeAirbase = false - - self.AIMonitorSchedule = SCHEDULER:New( self, self._ClientAliveMonitorScheduler, {}, 1, 10, 0 ) - - return self -end - ---- Returns the AI to the nearest friendly @{Airbase#AIRBASE}. --- @param #AIBALANCER self --- @param DCSTypes#Distance ReturnTresholdRange If there is an enemy @{Client#CLIENT} within the ReturnTresholdRange given in meters, the AI will not return to the nearest @{Airbase#AIRBASE}. --- @param Set#SET_AIRBASE ReturnAirbaseSet The SET of @{Set#SET_AIRBASE}s to evaluate where to return to. -function AIBALANCER:ReturnToNearestAirbases( ReturnTresholdRange, ReturnAirbaseSet ) - - self.ToNearestAirbase = true - self.ReturnTresholdRange = ReturnTresholdRange - self.ReturnAirbaseSet = ReturnAirbaseSet -end - ---- Returns the AI to the home @{Airbase#AIRBASE}. --- @param #AIBALANCER self --- @param DCSTypes#Distance ReturnTresholdRange If there is an enemy @{Client#CLIENT} within the ReturnTresholdRange given in meters, the AI will not return to the nearest @{Airbase#AIRBASE}. -function AIBALANCER:ReturnToHomeAirbase( ReturnTresholdRange ) - - self.ToHomeAirbase = true - self.ReturnTresholdRange = ReturnTresholdRange -end - ---- Let the AI patrol a @{Zone} with a given Speed range and Altitude range. --- @param #AIBALANCER self --- @param PatrolZone#PATROLZONE PatrolZone The @{PatrolZone} where the AI needs to patrol. --- @return PatrolZone#PATROLZONE self -function AIBALANCER:SetPatrolZone( PatrolZone ) - - self.PatrolZone = PatrolZone -end - ---- @param #AIBALANCER self -function AIBALANCER:_ClientAliveMonitorScheduler() - - self.SetClient:ForEachClient( - --- @param Client#CLIENT Client - function( Client ) - local ClientAIAliveState = Client:GetState( self, 'AIAlive' ) - self:T( ClientAIAliveState ) - if Client:IsAlive() then - if ClientAIAliveState == true then - Client:SetState( self, 'AIAlive', false ) - - local AIGroup = self.AIGroups[Client.UnitName] -- Group#GROUP - --- local PatrolZone = Client:GetState( self, "PatrolZone" ) --- if PatrolZone then --- PatrolZone = nil --- Client:ClearState( self, "PatrolZone" ) --- end - - if self.ToNearestAirbase == false and self.ToHomeAirbase == false then - AIGroup:Destroy() - else - -- We test if there is no other CLIENT within the self.ReturnTresholdRange of the first unit of the AI group. - -- If there is a CLIENT, the AI stays engaged and will not return. - -- If there is no CLIENT within the self.ReturnTresholdRange, then the unit will return to the Airbase return method selected. - - local PlayerInRange = { Value = false } - local RangeZone = ZONE_RADIUS:New( 'RangeZone', AIGroup:GetVec2(), self.ReturnTresholdRange ) - - self:E( RangeZone ) - - _DATABASE:ForEachPlayer( - --- @param Unit#UNIT RangeTestUnit - function( RangeTestUnit, RangeZone, AIGroup, PlayerInRange ) - self:E( { PlayerInRange, RangeTestUnit.UnitName, RangeZone.ZoneName } ) - if RangeTestUnit:IsInZone( RangeZone ) == true then - self:E( "in zone" ) - if RangeTestUnit:GetCoalition() ~= AIGroup:GetCoalition() then - self:E( "in range" ) - PlayerInRange.Value = true - end - end - end, - - --- @param Zone#ZONE_RADIUS RangeZone - -- @param Group#GROUP AIGroup - function( RangeZone, AIGroup, PlayerInRange ) - local AIGroupTemplate = AIGroup:GetTemplate() - if PlayerInRange.Value == false then - if self.ToHomeAirbase == true then - local WayPointCount = #AIGroupTemplate.route.points - local SwitchWayPointCommand = AIGroup:CommandSwitchWayPoint( 1, WayPointCount, 1 ) - AIGroup:SetCommand( SwitchWayPointCommand ) - AIGroup:MessageToRed( "Returning to home base ...", 30 ) - else - -- Okay, we need to send this Group back to the nearest base of the Coalition of the AI. - --TODO: i need to rework the POINT_VEC2 thing. - local PointVec2 = POINT_VEC2:New( AIGroup:GetVec2().x, AIGroup:GetVec2().y ) - local ClosestAirbase = self.ReturnAirbaseSet:FindNearestAirbaseFromPointVec2( PointVec2 ) - self:T( ClosestAirbase.AirbaseName ) - AIGroup:MessageToRed( "Returning to " .. ClosestAirbase:GetName().. " ...", 30 ) - local RTBRoute = AIGroup:RouteReturnToAirbase( ClosestAirbase ) - AIGroupTemplate.route = RTBRoute - AIGroup:Respawn( AIGroupTemplate ) - end - end - end - , RangeZone, AIGroup, PlayerInRange - ) - - end - end - else - if not ClientAIAliveState or ClientAIAliveState == false then - Client:SetState( self, 'AIAlive', true ) - - - -- OK, spawn a new group from the SpawnAI objects provided. - local SpawnAICount = #self.SpawnAI - local SpawnAIIndex = math.random( 1, SpawnAICount ) - local AIGroup = self.SpawnAI[SpawnAIIndex]:Spawn() - AIGroup:E( "spawning new AIGroup" ) - --TODO: need to rework UnitName thing ... - self.AIGroups[Client.UnitName] = AIGroup - - --- Now test if the AIGroup needs to patrol a zone, otherwise let it follow its route... - if self.PatrolZone then - self.PatrolZones[#self.PatrolZones+1] = PATROLZONE:New( - self.PatrolZone.PatrolZone, - self.PatrolZone.PatrolFloorAltitude, - self.PatrolZone.PatrolCeilingAltitude, - self.PatrolZone.PatrolMinSpeed, - self.PatrolZone.PatrolMaxSpeed - ) - - if self.PatrolZone.PatrolManageFuel == true then - self.PatrolZones[#self.PatrolZones]:ManageFuel( self.PatrolZone.PatrolFuelTresholdPercentage, self.PatrolZone.PatrolOutOfFuelOrbitTime ) - end - self.PatrolZones[#self.PatrolZones]:SetGroup( AIGroup ) - - --self.PatrolZones[#self.PatrolZones+1] = PatrolZone - - --Client:SetState( self, "PatrolZone", PatrolZone ) - end - end - end - end - ) - return true -end - - - ---- This module contains the AIRBASEPOLICE classes. --- --- === --- --- 1) @{AirbasePolice#AIRBASEPOLICE_BASE} class, extends @{Base#BASE} --- ================================================================== --- The @{AirbasePolice#AIRBASEPOLICE_BASE} class provides the main methods to monitor CLIENT behaviour at airbases. --- CLIENTS should not be allowed to: --- --- * Don't taxi faster than 40 km/h. --- * Don't take-off on taxiways. --- * Avoid to hit other planes on the airbase. --- * Obey ground control orders. --- --- 2) @{AirbasePolice#AIRBASEPOLICE_CAUCASUS} class, extends @{AirbasePolice#AIRBASEPOLICE_BASE} --- ============================================================================================= --- All the airbases on the caucasus map can be monitored using this class. --- If you want to monitor specific airbases, you need to use the @{#AIRBASEPOLICE_BASE.Monitor}() method, which takes a table or airbase names. --- The following names can be given: --- * AnapaVityazevo --- * Batumi --- * Beslan --- * Gelendzhik --- * Gudauta --- * Kobuleti --- * KrasnodarCenter --- * KrasnodarPashkovsky --- * Krymsk --- * Kutaisi --- * MaykopKhanskaya --- * MineralnyeVody --- * Mozdok --- * Nalchik --- * Novorossiysk --- * SenakiKolkhi --- * SochiAdler --- * Soganlug --- * SukhumiBabushara --- * TbilisiLochini --- * Vaziani --- --- 3) @{AirbasePolice#AIRBASEPOLICE_NEVADA} class, extends @{AirbasePolice#AIRBASEPOLICE_BASE} --- ============================================================================================= --- All the airbases on the NEVADA map can be monitored using this class. --- If you want to monitor specific airbases, you need to use the @{#AIRBASEPOLICE_BASE.Monitor}() method, which takes a table or airbase names. --- The following names can be given: --- * Nellis --- * McCarran --- * Creech --- * Groom Lake --- --- @module AirbasePolice --- @author Flight Control & DUTCH BARON - - - - - ---- @type AIRBASEPOLICE_BASE --- @field Set#SET_CLIENT SetClient --- @extends Base#BASE - -AIRBASEPOLICE_BASE = { - ClassName = "AIRBASEPOLICE_BASE", - SetClient = nil, - Airbases = nil, - AirbaseNames = nil, -} - - ---- Creates a new AIRBASEPOLICE_BASE object. --- @param #AIRBASEPOLICE_BASE self --- @param SetClient A SET_CLIENT object that will contain the CLIENT objects to be monitored if they follow the rules of the airbase. --- @param Airbases A table of Airbase Names. --- @return #AIRBASEPOLICE_BASE self -function AIRBASEPOLICE_BASE:New( SetClient, Airbases ) - - -- Inherits from BASE - local self = BASE:Inherit( self, BASE:New() ) - self:E( { self.ClassName, SetClient, Airbases } ) - - self.SetClient = SetClient - self.Airbases = Airbases - - for AirbaseID, Airbase in pairs( self.Airbases ) do - Airbase.ZoneBoundary = ZONE_POLYGON_BASE:New( "Boundary", Airbase.PointsBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() - for PointsRunwayID, PointsRunway in pairs( Airbase.PointsRunways ) do - Airbase.ZoneRunways[PointsRunwayID] = ZONE_POLYGON_BASE:New( "Runway " .. PointsRunwayID, PointsRunway ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() - end - end - --- -- Template --- local TemplateBoundary = GROUP:FindByName( "Template Boundary" ) --- self.Airbases.Template.ZoneBoundary = ZONE_POLYGON:New( "Template Boundary", TemplateBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() --- --- local TemplateRunway1 = GROUP:FindByName( "Template Runway 1" ) --- self.Airbases.Template.ZoneRunways[1] = ZONE_POLYGON:New( "Template Runway 1", TemplateRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() - - self.SetClient:ForEachClient( - --- @param Client#CLIENT Client - function( Client ) - Client:SetState( self, "Speeding", false ) - Client:SetState( self, "Warnings", 0) - Client:SetState( self, "Taxi", false ) - end - ) - - self.AirbaseMonitor = SCHEDULER:New( self, self._AirbaseMonitor, {}, 0, 2, 0.05 ) - - return self -end - ---- @type AIRBASEPOLICE_BASE.AirbaseNames --- @list <#string> - ---- Monitor a table of airbase names. --- @param #AIRBASEPOLICE_BASE self --- @param #AIRBASEPOLICE_BASE.AirbaseNames AirbaseNames A list of AirbaseNames to monitor. If this parameters is nil, then all airbases will be monitored. --- @return #AIRBASEPOLICE_BASE self -function AIRBASEPOLICE_BASE:Monitor( AirbaseNames ) - - if AirbaseNames then - if type( AirbaseNames ) == "table" then - self.AirbaseNames = AirbaseNames - else - self.AirbaseNames = { AirbaseNames } - end - end -end - ---- @param #AIRBASEPOLICE_BASE self -function AIRBASEPOLICE_BASE:_AirbaseMonitor() - - for AirbaseID, Airbase in pairs( self.Airbases ) do - - if not self.AirbaseNames or self.AirbaseNames[AirbaseID] then - - self:E( AirbaseID ) - - self.SetClient:ForEachClientInZone( Airbase.ZoneBoundary, - - --- @param Client#CLIENT Client - function( Client ) - - self:E( Client.UnitName ) - if Client:IsAlive() then - local NotInRunwayZone = true - for ZoneRunwayID, ZoneRunway in pairs( Airbase.ZoneRunways ) do - NotInRunwayZone = ( Client:IsNotInZone( ZoneRunway ) == true ) and NotInRunwayZone or false - end - - if NotInRunwayZone then - local Taxi = self:GetState( self, "Taxi" ) - self:E( Taxi ) - if Taxi == false then - Client:Message( "Welcome at " .. AirbaseID .. ". The maximum taxiing speed is " .. Airbase.MaximumSpeed " km/h.", 20, "ATC" ) - self:SetState( self, "Taxi", true ) - end - - -- TODO: GetVelocityKMH function usage - local VelocityVec3 = Client:GetVelocity() - local Velocity = ( VelocityVec3.x ^ 2 + VelocityVec3.y ^ 2 + VelocityVec3.z ^ 2 ) ^ 0.5 -- in meters / sec - local Velocity = Velocity * 3.6 -- now it is in km/h. - -- MESSAGE:New( "Velocity = " .. Velocity, 1 ):ToAll() - local IsAboveRunway = Client:IsAboveRunway() - local IsOnGround = Client:InAir() == false - self:T( IsAboveRunway, IsOnGround ) - - if IsAboveRunway and IsOnGround then - - if Velocity > Airbase.MaximumSpeed then - local IsSpeeding = Client:GetState( self, "Speeding" ) - - if IsSpeeding == true then - local SpeedingWarnings = Client:GetState( self, "Warnings" ) - self:T( SpeedingWarnings ) - - if SpeedingWarnings <= 3 then - Client:Message( "You are speeding on the taxiway! Slow down or you will be removed from this airbase! Your current velocity is " .. string.format( "%2.0f km/h", Velocity ), 5, "Warning " .. SpeedingWarnings .. " / 3" ) - Client:SetState( self, "Warnings", SpeedingWarnings + 1 ) - else - MESSAGE:New( "Player " .. Client:GetPlayerName() .. " has been removed from the airbase, due to a speeding violation ...", 10, "Airbase Police" ):ToAll() - Client:GetGroup():Destroy() - Client:SetState( self, "Speeding", false ) - Client:SetState( self, "Warnings", 0 ) - end - - else - Client:Message( "You are speeding on the taxiway, slow down now! Your current velocity is " .. string.format( "%2.0f km/h", Velocity ), 5, "Attention! " ) - Client:SetState( self, "Speeding", true ) - Client:SetState( self, "Warnings", 1 ) - end - - else - Client:SetState( self, "Speeding", false ) - Client:SetState( self, "Warnings", 0 ) - end - end - - else - Client:SetState( self, "Speeding", false ) - Client:SetState( self, "Warnings", 0 ) - local Taxi = self:GetState( self, "Taxi" ) - if Taxi == true then - Client:Message( "You have progressed to the runway ... Await take-off clearance ...", 20, "ATC" ) - self:SetState( self, "Taxi", false ) - end - end - end - end - ) - end - end - - return true -end - - ---- @type AIRBASEPOLICE_CAUCASUS --- @field Set#SET_CLIENT SetClient --- @extends #AIRBASEPOLICE_BASE - -AIRBASEPOLICE_CAUCASUS = { - ClassName = "AIRBASEPOLICE_CAUCASUS", - Airbases = { - AnapaVityazevo = { - PointsBoundary = { - [1]={["y"]=242234.85714287,["x"]=-6616.5714285726,}, - [2]={["y"]=241060.57142858,["x"]=-5585.142857144,}, - [3]={["y"]=243806.2857143,["x"]=-3962.2857142868,}, - [4]={["y"]=245240.57142858,["x"]=-4816.5714285726,}, - [5]={["y"]=244783.42857144,["x"]=-5630.8571428583,}, - [6]={["y"]=243800.57142858,["x"]=-5065.142857144,}, - [7]={["y"]=242232.00000001,["x"]=-6622.2857142868,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=242140.57142858,["x"]=-6478.8571428583,}, - [2]={["y"]=242188.57142858,["x"]=-6522.0000000011,}, - [3]={["y"]=244124.2857143,["x"]=-4344.0000000011,}, - [4]={["y"]=244068.2857143,["x"]=-4296.5714285726,}, - [5]={["y"]=242140.57142858,["x"]=-6480.0000000011,} - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - Batumi = { - PointsBoundary = { - [1]={["y"]=617567.14285714,["x"]=-355313.14285715,}, - [2]={["y"]=616181.42857142,["x"]=-354800.28571429,}, - [3]={["y"]=616007.14285714,["x"]=-355128.85714286,}, - [4]={["y"]=618230,["x"]=-356914.57142858,}, - [5]={["y"]=618727.14285714,["x"]=-356166,}, - [6]={["y"]=617572.85714285,["x"]=-355308.85714286,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=616442.28571429,["x"]=-355090.28571429,}, - [2]={["y"]=618450.57142857,["x"]=-356522,}, - [3]={["y"]=618407.71428571,["x"]=-356584.85714286,}, - [4]={["y"]=618361.99999999,["x"]=-356554.85714286,}, - [5]={["y"]=618324.85714285,["x"]=-356599.14285715,}, - [6]={["y"]=618250.57142856,["x"]=-356543.42857143,}, - [7]={["y"]=618257.7142857,["x"]=-356496.28571429,}, - [8]={["y"]=618237.7142857,["x"]=-356459.14285715,}, - [9]={["y"]=616555.71428571,["x"]=-355258.85714286,}, - [10]={["y"]=616486.28571428,["x"]=-355280.57142858,}, - [11]={["y"]=616410.57142856,["x"]=-355227.71428572,}, - [12]={["y"]=616441.99999999,["x"]=-355179.14285715,}, - [13]={["y"]=616401.99999999,["x"]=-355147.71428572,}, - [14]={["y"]=616441.42857142,["x"]=-355092.57142858,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - Beslan = { - PointsBoundary = { - [1]={["y"]=842082.57142857,["x"]=-148445.14285715,}, - [2]={["y"]=845237.71428572,["x"]=-148639.71428572,}, - [3]={["y"]=845232,["x"]=-148765.42857143,}, - [4]={["y"]=844220.57142857,["x"]=-149168.28571429,}, - [5]={["y"]=843274.85714286,["x"]=-149125.42857143,}, - [6]={["y"]=842077.71428572,["x"]=-148554,}, - [7]={["y"]=842083.42857143,["x"]=-148445.42857143,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=842104.57142857,["x"]=-148460.57142857,}, - [2]={["y"]=845225.71428572,["x"]=-148656,}, - [3]={["y"]=845220.57142858,["x"]=-148750,}, - [4]={["y"]=842098.85714286,["x"]=-148556.28571429,}, - [5]={["y"]=842104,["x"]=-148460.28571429,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - Gelendzhik = { - PointsBoundary = { - [1]={["y"]=297856.00000001,["x"]=-51151.428571429,}, - [2]={["y"]=299044.57142858,["x"]=-49720.000000001,}, - [3]={["y"]=298861.71428572,["x"]=-49580.000000001,}, - [4]={["y"]=298198.85714286,["x"]=-49842.857142858,}, - [5]={["y"]=297990.28571429,["x"]=-50151.428571429,}, - [6]={["y"]=297696.00000001,["x"]=-51054.285714286,}, - [7]={["y"]=297850.28571429,["x"]=-51160.000000001,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=297834.00000001,["x"]=-51107.428571429,}, - [2]={["y"]=297786.57142858,["x"]=-51068.857142858,}, - [3]={["y"]=298946.57142858,["x"]=-49686.000000001,}, - [4]={["y"]=298993.14285715,["x"]=-49725.714285715,}, - [5]={["y"]=297835.14285715,["x"]=-51107.714285715,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - Gudauta = { - PointsBoundary = { - [1]={["y"]=517246.57142857,["x"]=-197850.28571429,}, - [2]={["y"]=516749.42857142,["x"]=-198070.28571429,}, - [3]={["y"]=515755.14285714,["x"]=-197598.85714286,}, - [4]={["y"]=515369.42857142,["x"]=-196538.85714286,}, - [5]={["y"]=515623.71428571,["x"]=-195618.85714286,}, - [6]={["y"]=515946.57142857,["x"]=-195510.28571429,}, - [7]={["y"]=517243.71428571,["x"]=-197858.85714286,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=517096.57142857,["x"]=-197804.57142857,}, - [2]={["y"]=515880.85714285,["x"]=-195590.28571429,}, - [3]={["y"]=515812.28571428,["x"]=-195628.85714286,}, - [4]={["y"]=517036.57142857,["x"]=-197834.57142857,}, - [5]={["y"]=517097.99999999,["x"]=-197807.42857143,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - Kobuleti = { - PointsBoundary = { - [1]={["y"]=634427.71428571,["x"]=-318290.28571429,}, - [2]={["y"]=635033.42857143,["x"]=-317550.2857143,}, - [3]={["y"]=635864.85714286,["x"]=-317333.14285715,}, - [4]={["y"]=636967.71428571,["x"]=-317261.71428572,}, - [5]={["y"]=637144.85714286,["x"]=-317913.14285715,}, - [6]={["y"]=634630.57142857,["x"]=-318687.42857144,}, - [7]={["y"]=634424.85714286,["x"]=-318290.2857143,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=634509.71428571,["x"]=-318339.42857144,}, - [2]={["y"]=636767.42857143,["x"]=-317516.57142858,}, - [3]={["y"]=636790,["x"]=-317575.71428572,}, - [4]={["y"]=634531.42857143,["x"]=-318398.00000001,}, - [5]={["y"]=634510.28571429,["x"]=-318339.71428572,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - KrasnodarCenter = { - PointsBoundary = { - [1]={["y"]=366680.28571429,["x"]=11699.142857142,}, - [2]={["y"]=366654.28571429,["x"]=11225.142857142,}, - [3]={["y"]=367497.14285715,["x"]=11082.285714285,}, - [4]={["y"]=368025.71428572,["x"]=10396.57142857,}, - [5]={["y"]=369854.28571429,["x"]=11367.999999999,}, - [6]={["y"]=369840.00000001,["x"]=11910.857142856,}, - [7]={["y"]=366682.57142858,["x"]=11697.999999999,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=369205.42857144,["x"]=11789.142857142,}, - [2]={["y"]=369209.71428572,["x"]=11714.857142856,}, - [3]={["y"]=366699.71428572,["x"]=11581.714285713,}, - [4]={["y"]=366698.28571429,["x"]=11659.142857142,}, - [5]={["y"]=369208.85714286,["x"]=11788.57142857,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - KrasnodarPashkovsky = { - PointsBoundary = { - [1]={["y"]=386754,["x"]=6476.5714285703,}, - [2]={["y"]=389182.57142858,["x"]=8722.2857142846,}, - [3]={["y"]=388832.57142858,["x"]=9086.5714285703,}, - [4]={["y"]=386961.14285715,["x"]=7707.9999999989,}, - [5]={["y"]=385404,["x"]=9179.4285714274,}, - [6]={["y"]=383239.71428572,["x"]=7386.5714285703,}, - [7]={["y"]=383954,["x"]=6486.5714285703,}, - [8]={["y"]=385775.42857143,["x"]=8097.9999999989,}, - [9]={["y"]=386804,["x"]=7319.4285714274,}, - [10]={["y"]=386375.42857143,["x"]=6797.9999999989,}, - [11]={["y"]=386746.85714286,["x"]=6472.2857142846,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=385891.14285715,["x"]=8416.5714285703,}, - [2]={["y"]=385842.28571429,["x"]=8467.9999999989,}, - [3]={["y"]=384180.85714286,["x"]=6917.1428571417,}, - [4]={["y"]=384228.57142858,["x"]=6867.7142857132,}, - [5]={["y"]=385891.14285715,["x"]=8416.5714285703,}, - }, - [2] = { - [1]={["y"]=386714.85714286,["x"]=6674.857142856,}, - [2]={["y"]=386757.71428572,["x"]=6627.7142857132,}, - [3]={["y"]=389028.57142858,["x"]=8741.4285714275,}, - [4]={["y"]=388981.71428572,["x"]=8790.5714285703,}, - [5]={["y"]=386714.57142858,["x"]=6674.5714285703,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - Krymsk = { - PointsBoundary = { - [1]={["y"]=293338.00000001,["x"]=-7575.4285714297,}, - [2]={["y"]=295199.42857144,["x"]=-5434.0000000011,}, - [3]={["y"]=295595.14285715,["x"]=-6239.7142857154,}, - [4]={["y"]=294152.2857143,["x"]=-8325.4285714297,}, - [5]={["y"]=293345.14285715,["x"]=-7596.8571428582,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=293522.00000001,["x"]=-7567.4285714297,}, - [2]={["y"]=293578.57142858,["x"]=-7616.0000000011,}, - [3]={["y"]=295246.00000001,["x"]=-5591.142857144,}, - [4]={["y"]=295187.71428573,["x"]=-5546.0000000011,}, - [5]={["y"]=293523.14285715,["x"]=-7568.2857142868,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - Kutaisi = { - PointsBoundary = { - [1]={["y"]=682087.42857143,["x"]=-284512.85714286,}, - [2]={["y"]=685387.42857143,["x"]=-283662.85714286,}, - [3]={["y"]=685294.57142857,["x"]=-284977.14285715,}, - [4]={["y"]=682744.57142857,["x"]=-286505.71428572,}, - [5]={["y"]=682094.57142857,["x"]=-284527.14285715,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=682638,["x"]=-285202.28571429,}, - [2]={["y"]=685050.28571429,["x"]=-284507.42857144,}, - [3]={["y"]=685068.85714286,["x"]=-284578.85714286,}, - [4]={["y"]=682657.42857143,["x"]=-285264.28571429,}, - [5]={["y"]=682638.28571429,["x"]=-285202.85714286,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - MaykopKhanskaya = { - PointsBoundary = { - [1]={["y"]=456876.28571429,["x"]=-27665.42857143,}, - [2]={["y"]=457800,["x"]=-28392.857142858,}, - [3]={["y"]=459368.57142857,["x"]=-26378.571428573,}, - [4]={["y"]=459425.71428572,["x"]=-25242.857142858,}, - [5]={["y"]=458961.42857143,["x"]=-24964.285714287,}, - [6]={["y"]=456878.57142857,["x"]=-27667.714285715,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=457005.42857143,["x"]=-27668.000000001,}, - [2]={["y"]=459028.85714286,["x"]=-25168.857142858,}, - [3]={["y"]=459082.57142857,["x"]=-25216.857142858,}, - [4]={["y"]=457060,["x"]=-27714.285714287,}, - [5]={["y"]=457004.57142857,["x"]=-27669.714285715,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - MineralnyeVody = { - PointsBoundary = { - [1]={["y"]=703857.14285714,["x"]=-50226.000000002,}, - [2]={["y"]=707385.71428571,["x"]=-51911.714285716,}, - [3]={["y"]=707595.71428571,["x"]=-51434.857142859,}, - [4]={["y"]=707900,["x"]=-51568.857142859,}, - [5]={["y"]=707542.85714286,["x"]=-52326.000000002,}, - [6]={["y"]=706628.57142857,["x"]=-52568.857142859,}, - [7]={["y"]=705142.85714286,["x"]=-51790.285714288,}, - [8]={["y"]=703678.57142857,["x"]=-50611.714285716,}, - [9]={["y"]=703857.42857143,["x"]=-50226.857142859,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=703904,["x"]=-50352.571428573,}, - [2]={["y"]=707596.28571429,["x"]=-52094.571428573,}, - [3]={["y"]=707560.57142858,["x"]=-52161.714285716,}, - [4]={["y"]=703871.71428572,["x"]=-50420.571428573,}, - [5]={["y"]=703902,["x"]=-50352.000000002,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - Mozdok = { - PointsBoundary = { - [1]={["y"]=832123.42857143,["x"]=-83608.571428573,}, - [2]={["y"]=835916.28571429,["x"]=-83144.285714288,}, - [3]={["y"]=835474.28571429,["x"]=-84170.571428573,}, - [4]={["y"]=832911.42857143,["x"]=-84470.571428573,}, - [5]={["y"]=832487.71428572,["x"]=-85565.714285716,}, - [6]={["y"]=831573.42857143,["x"]=-85351.42857143,}, - [7]={["y"]=832123.71428572,["x"]=-83610.285714288,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=832201.14285715,["x"]=-83699.428571431,}, - [2]={["y"]=832212.57142857,["x"]=-83780.571428574,}, - [3]={["y"]=835730.28571429,["x"]=-83335.714285717,}, - [4]={["y"]=835718.85714286,["x"]=-83246.571428574,}, - [5]={["y"]=832200.57142857,["x"]=-83700.000000002,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - Nalchik = { - PointsBoundary = { - [1]={["y"]=759370,["x"]=-125502.85714286,}, - [2]={["y"]=761384.28571429,["x"]=-124177.14285714,}, - [3]={["y"]=761472.85714286,["x"]=-124325.71428572,}, - [4]={["y"]=761092.85714286,["x"]=-125048.57142857,}, - [5]={["y"]=760295.71428572,["x"]=-125685.71428572,}, - [6]={["y"]=759444.28571429,["x"]=-125734.28571429,}, - [7]={["y"]=759375.71428572,["x"]=-125511.42857143,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=759454.28571429,["x"]=-125551.42857143,}, - [2]={["y"]=759492.85714286,["x"]=-125610.85714286,}, - [3]={["y"]=761406.28571429,["x"]=-124304.28571429,}, - [4]={["y"]=761361.14285714,["x"]=-124239.71428572,}, - [5]={["y"]=759456,["x"]=-125552.57142857,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - Novorossiysk = { - PointsBoundary = { - [1]={["y"]=278677.71428573,["x"]=-41656.571428572,}, - [2]={["y"]=278446.2857143,["x"]=-41453.714285715,}, - [3]={["y"]=278989.14285716,["x"]=-40188.000000001,}, - [4]={["y"]=279717.71428573,["x"]=-39968.000000001,}, - [5]={["y"]=280020.57142859,["x"]=-40208.000000001,}, - [6]={["y"]=278674.85714287,["x"]=-41660.857142858,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=278673.14285716,["x"]=-41615.142857144,}, - [2]={["y"]=278625.42857144,["x"]=-41570.571428572,}, - [3]={["y"]=279835.42857144,["x"]=-40226.000000001,}, - [4]={["y"]=279882.2857143,["x"]=-40270.000000001,}, - [5]={["y"]=278672.00000001,["x"]=-41614.857142858,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - SenakiKolkhi = { - PointsBoundary = { - [1]={["y"]=646036.57142857,["x"]=-281778.85714286,}, - [2]={["y"]=646045.14285714,["x"]=-281191.71428571,}, - [3]={["y"]=647032.28571429,["x"]=-280598.85714285,}, - [4]={["y"]=647669.42857143,["x"]=-281273.14285714,}, - [5]={["y"]=648323.71428571,["x"]=-281370.28571428,}, - [6]={["y"]=648520.85714286,["x"]=-281978.85714285,}, - [7]={["y"]=646039.42857143,["x"]=-281783.14285714,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=646060.85714285,["x"]=-281736,}, - [2]={["y"]=646056.57142857,["x"]=-281631.71428571,}, - [3]={["y"]=648442.28571428,["x"]=-281840.28571428,}, - [4]={["y"]=648432.28571428,["x"]=-281918.85714286,}, - [5]={["y"]=646063.71428571,["x"]=-281738.85714286,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - SochiAdler = { - PointsBoundary = { - [1]={["y"]=460642.28571428,["x"]=-164861.71428571,}, - [2]={["y"]=462820.85714285,["x"]=-163368.85714286,}, - [3]={["y"]=463649.42857142,["x"]=-163340.28571429,}, - [4]={["y"]=463835.14285714,["x"]=-164040.28571429,}, - [5]={["y"]=462535.14285714,["x"]=-165654.57142857,}, - [6]={["y"]=460678,["x"]=-165247.42857143,}, - [7]={["y"]=460635.14285714,["x"]=-164876,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=460831.42857143,["x"]=-165180,}, - [2]={["y"]=460878.57142857,["x"]=-165257.14285714,}, - [3]={["y"]=463663.71428571,["x"]=-163793.14285714,}, - [4]={["y"]=463612.28571428,["x"]=-163697.42857143,}, - [5]={["y"]=460831.42857143,["x"]=-165177.14285714,}, - }, - [2] = { - [1]={["y"]=460831.42857143,["x"]=-165180,}, - [2]={["y"]=460878.57142857,["x"]=-165257.14285714,}, - [3]={["y"]=463663.71428571,["x"]=-163793.14285714,}, - [4]={["y"]=463612.28571428,["x"]=-163697.42857143,}, - [5]={["y"]=460831.42857143,["x"]=-165177.14285714,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - Soganlug = { - PointsBoundary = { - [1]={["y"]=894530.85714286,["x"]=-316928.28571428,}, - [2]={["y"]=896422.28571428,["x"]=-318622.57142857,}, - [3]={["y"]=896090.85714286,["x"]=-318934,}, - [4]={["y"]=894019.42857143,["x"]=-317119.71428571,}, - [5]={["y"]=894533.71428571,["x"]=-316925.42857143,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=894525.71428571,["x"]=-316964,}, - [2]={["y"]=896363.14285714,["x"]=-318634.28571428,}, - [3]={["y"]=896299.14285714,["x"]=-318702.85714286,}, - [4]={["y"]=894464,["x"]=-317031.71428571,}, - [5]={["y"]=894524.57142857,["x"]=-316963.71428571,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - SukhumiBabushara = { - PointsBoundary = { - [1]={["y"]=562541.14285714,["x"]=-219852.28571429,}, - [2]={["y"]=562691.14285714,["x"]=-219395.14285714,}, - [3]={["y"]=564326.85714286,["x"]=-219523.71428571,}, - [4]={["y"]=566262.57142857,["x"]=-221166.57142857,}, - [5]={["y"]=566069.71428571,["x"]=-221580.85714286,}, - [6]={["y"]=562534,["x"]=-219873.71428571,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=562684,["x"]=-219779.71428571,}, - [2]={["y"]=562717.71428571,["x"]=-219718,}, - [3]={["y"]=566046.85714286,["x"]=-221376.57142857,}, - [4]={["y"]=566012.28571428,["x"]=-221446.57142857,}, - [5]={["y"]=562684.57142857,["x"]=-219782.57142857,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - TbilisiLochini = { - PointsBoundary = { - [1]={["y"]=895172.85714286,["x"]=-314667.42857143,}, - [2]={["y"]=895337.42857143,["x"]=-314143.14285714,}, - [3]={["y"]=895990.28571429,["x"]=-314036,}, - [4]={["y"]=897730.28571429,["x"]=-315284.57142857,}, - [5]={["y"]=897901.71428571,["x"]=-316284.57142857,}, - [6]={["y"]=897684.57142857,["x"]=-316618.85714286,}, - [7]={["y"]=895173.14285714,["x"]=-314667.42857143,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=895261.14285715,["x"]=-314652.28571428,}, - [2]={["y"]=897654.57142857,["x"]=-316523.14285714,}, - [3]={["y"]=897711.71428571,["x"]=-316450.28571429,}, - [4]={["y"]=895327.42857143,["x"]=-314568.85714286,}, - [5]={["y"]=895261.71428572,["x"]=-314656,}, - }, - [2] = { - [1]={["y"]=895605.71428572,["x"]=-314724.57142857,}, - [2]={["y"]=897639.71428572,["x"]=-316148,}, - [3]={["y"]=897683.42857143,["x"]=-316087.14285714,}, - [4]={["y"]=895650,["x"]=-314660,}, - [5]={["y"]=895606,["x"]=-314724.85714286,} - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - Vaziani = { - PointsBoundary = { - [1]={["y"]=902122,["x"]=-318163.71428572,}, - [2]={["y"]=902678.57142857,["x"]=-317594,}, - [3]={["y"]=903275.71428571,["x"]=-317405.42857143,}, - [4]={["y"]=903418.57142857,["x"]=-317891.14285714,}, - [5]={["y"]=904292.85714286,["x"]=-318748.28571429,}, - [6]={["y"]=904542,["x"]=-319740.85714286,}, - [7]={["y"]=904042,["x"]=-320166.57142857,}, - [8]={["y"]=902121.42857143,["x"]=-318164.85714286,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=902239.14285714,["x"]=-318190.85714286,}, - [2]={["y"]=904014.28571428,["x"]=-319994.57142857,}, - [3]={["y"]=904064.85714285,["x"]=-319945.14285715,}, - [4]={["y"]=902294.57142857,["x"]=-318146,}, - [5]={["y"]=902247.71428571,["x"]=-318190.85714286,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - }, -} - ---- Creates a new AIRBASEPOLICE_CAUCASUS object. --- @param #AIRBASEPOLICE_CAUCASUS self --- @param SetClient A SET_CLIENT object that will contain the CLIENT objects to be monitored if they follow the rules of the airbase. --- @return #AIRBASEPOLICE_CAUCASUS self -function AIRBASEPOLICE_CAUCASUS:New( SetClient ) - - -- Inherits from BASE - local self = BASE:Inherit( self, AIRBASEPOLICE_BASE:New( SetClient, self.Airbases ) ) - - -- -- AnapaVityazevo - -- local AnapaVityazevoBoundary = GROUP:FindByName( "AnapaVityazevo Boundary" ) - -- self.Airbases.AnapaVityazevo.ZoneBoundary = ZONE_POLYGON:New( "AnapaVityazevo Boundary", AnapaVityazevoBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() - -- - -- local AnapaVityazevoRunway1 = GROUP:FindByName( "AnapaVityazevo Runway 1" ) - -- self.Airbases.AnapaVityazevo.ZoneRunways[1] = ZONE_POLYGON:New( "AnapaVityazevo Runway 1", AnapaVityazevoRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() - -- - -- - -- - -- -- Batumi - -- local BatumiBoundary = GROUP:FindByName( "Batumi Boundary" ) - -- self.Airbases.Batumi.ZoneBoundary = ZONE_POLYGON:New( "Batumi Boundary", BatumiBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() - -- - -- local BatumiRunway1 = GROUP:FindByName( "Batumi Runway 1" ) - -- self.Airbases.Batumi.ZoneRunways[1] = ZONE_POLYGON:New( "Batumi Runway 1", BatumiRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() - -- - -- - -- - -- -- Beslan - -- local BeslanBoundary = GROUP:FindByName( "Beslan Boundary" ) - -- self.Airbases.Beslan.ZoneBoundary = ZONE_POLYGON:New( "Beslan Boundary", BeslanBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() - -- - -- local BeslanRunway1 = GROUP:FindByName( "Beslan Runway 1" ) - -- self.Airbases.Beslan.ZoneRunways[1] = ZONE_POLYGON:New( "Beslan Runway 1", BeslanRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() - -- - -- - -- - -- -- Gelendzhik - -- local GelendzhikBoundary = GROUP:FindByName( "Gelendzhik Boundary" ) - -- self.Airbases.Gelendzhik.ZoneBoundary = ZONE_POLYGON:New( "Gelendzhik Boundary", GelendzhikBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() - -- - -- local GelendzhikRunway1 = GROUP:FindByName( "Gelendzhik Runway 1" ) - -- self.Airbases.Gelendzhik.ZoneRunways[1] = ZONE_POLYGON:New( "Gelendzhik Runway 1", GelendzhikRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() - -- - -- - -- - -- -- Gudauta - -- local GudautaBoundary = GROUP:FindByName( "Gudauta Boundary" ) - -- self.Airbases.Gudauta.ZoneBoundary = ZONE_POLYGON:New( "Gudauta Boundary", GudautaBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() - -- - -- local GudautaRunway1 = GROUP:FindByName( "Gudauta Runway 1" ) - -- self.Airbases.Gudauta.ZoneRunways[1] = ZONE_POLYGON:New( "Gudauta Runway 1", GudautaRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() - -- - -- - -- - -- -- Kobuleti - -- local KobuletiBoundary = GROUP:FindByName( "Kobuleti Boundary" ) - -- self.Airbases.Kobuleti.ZoneBoundary = ZONE_POLYGON:New( "Kobuleti Boundary", KobuletiBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() - -- - -- local KobuletiRunway1 = GROUP:FindByName( "Kobuleti Runway 1" ) - -- self.Airbases.Kobuleti.ZoneRunways[1] = ZONE_POLYGON:New( "Kobuleti Runway 1", KobuletiRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() - -- - -- - -- - -- -- KrasnodarCenter - -- local KrasnodarCenterBoundary = GROUP:FindByName( "KrasnodarCenter Boundary" ) - -- self.Airbases.KrasnodarCenter.ZoneBoundary = ZONE_POLYGON:New( "KrasnodarCenter Boundary", KrasnodarCenterBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() - -- - -- local KrasnodarCenterRunway1 = GROUP:FindByName( "KrasnodarCenter Runway 1" ) - -- self.Airbases.KrasnodarCenter.ZoneRunways[1] = ZONE_POLYGON:New( "KrasnodarCenter Runway 1", KrasnodarCenterRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() - -- - -- - -- - -- -- KrasnodarPashkovsky - -- local KrasnodarPashkovskyBoundary = GROUP:FindByName( "KrasnodarPashkovsky Boundary" ) - -- self.Airbases.KrasnodarPashkovsky.ZoneBoundary = ZONE_POLYGON:New( "KrasnodarPashkovsky Boundary", KrasnodarPashkovskyBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() - -- - -- local KrasnodarPashkovskyRunway1 = GROUP:FindByName( "KrasnodarPashkovsky Runway 1" ) - -- self.Airbases.KrasnodarPashkovsky.ZoneRunways[1] = ZONE_POLYGON:New( "KrasnodarPashkovsky Runway 1", KrasnodarPashkovskyRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() - -- local KrasnodarPashkovskyRunway2 = GROUP:FindByName( "KrasnodarPashkovsky Runway 2" ) - -- self.Airbases.KrasnodarPashkovsky.ZoneRunways[2] = ZONE_POLYGON:New( "KrasnodarPashkovsky Runway 2", KrasnodarPashkovskyRunway2 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() - -- - -- - -- - -- -- Krymsk - -- local KrymskBoundary = GROUP:FindByName( "Krymsk Boundary" ) - -- self.Airbases.Krymsk.ZoneBoundary = ZONE_POLYGON:New( "Krymsk Boundary", KrymskBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() - -- - -- local KrymskRunway1 = GROUP:FindByName( "Krymsk Runway 1" ) - -- self.Airbases.Krymsk.ZoneRunways[1] = ZONE_POLYGON:New( "Krymsk Runway 1", KrymskRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() - -- - -- - -- - -- -- Kutaisi - -- local KutaisiBoundary = GROUP:FindByName( "Kutaisi Boundary" ) - -- self.Airbases.Kutaisi.ZoneBoundary = ZONE_POLYGON:New( "Kutaisi Boundary", KutaisiBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() - -- - -- local KutaisiRunway1 = GROUP:FindByName( "Kutaisi Runway 1" ) - -- self.Airbases.Kutaisi.ZoneRunways[1] = ZONE_POLYGON:New( "Kutaisi Runway 1", KutaisiRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() - -- - -- - -- - -- -- MaykopKhanskaya - -- local MaykopKhanskayaBoundary = GROUP:FindByName( "MaykopKhanskaya Boundary" ) - -- self.Airbases.MaykopKhanskaya.ZoneBoundary = ZONE_POLYGON:New( "MaykopKhanskaya Boundary", MaykopKhanskayaBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() - -- - -- local MaykopKhanskayaRunway1 = GROUP:FindByName( "MaykopKhanskaya Runway 1" ) - -- self.Airbases.MaykopKhanskaya.ZoneRunways[1] = ZONE_POLYGON:New( "MaykopKhanskaya Runway 1", MaykopKhanskayaRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() - -- - -- - -- - -- -- MineralnyeVody - -- local MineralnyeVodyBoundary = GROUP:FindByName( "MineralnyeVody Boundary" ) - -- self.Airbases.MineralnyeVody.ZoneBoundary = ZONE_POLYGON:New( "MineralnyeVody Boundary", MineralnyeVodyBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() - -- - -- local MineralnyeVodyRunway1 = GROUP:FindByName( "MineralnyeVody Runway 1" ) - -- self.Airbases.MineralnyeVody.ZoneRunways[1] = ZONE_POLYGON:New( "MineralnyeVody Runway 1", MineralnyeVodyRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() - -- - -- - -- - -- -- Mozdok - -- local MozdokBoundary = GROUP:FindByName( "Mozdok Boundary" ) - -- self.Airbases.Mozdok.ZoneBoundary = ZONE_POLYGON:New( "Mozdok Boundary", MozdokBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() - -- - -- local MozdokRunway1 = GROUP:FindByName( "Mozdok Runway 1" ) - -- self.Airbases.Mozdok.ZoneRunways[1] = ZONE_POLYGON:New( "Mozdok Runway 1", MozdokRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() - -- - -- - -- - -- -- Nalchik - -- local NalchikBoundary = GROUP:FindByName( "Nalchik Boundary" ) - -- self.Airbases.Nalchik.ZoneBoundary = ZONE_POLYGON:New( "Nalchik Boundary", NalchikBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() - -- - -- local NalchikRunway1 = GROUP:FindByName( "Nalchik Runway 1" ) - -- self.Airbases.Nalchik.ZoneRunways[1] = ZONE_POLYGON:New( "Nalchik Runway 1", NalchikRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() - -- - -- - -- - -- -- Novorossiysk - -- local NovorossiyskBoundary = GROUP:FindByName( "Novorossiysk Boundary" ) - -- self.Airbases.Novorossiysk.ZoneBoundary = ZONE_POLYGON:New( "Novorossiysk Boundary", NovorossiyskBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() - -- - -- local NovorossiyskRunway1 = GROUP:FindByName( "Novorossiysk Runway 1" ) - -- self.Airbases.Novorossiysk.ZoneRunways[1] = ZONE_POLYGON:New( "Novorossiysk Runway 1", NovorossiyskRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() - -- - -- - -- - -- -- SenakiKolkhi - -- local SenakiKolkhiBoundary = GROUP:FindByName( "SenakiKolkhi Boundary" ) - -- self.Airbases.SenakiKolkhi.ZoneBoundary = ZONE_POLYGON:New( "SenakiKolkhi Boundary", SenakiKolkhiBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() - -- - -- local SenakiKolkhiRunway1 = GROUP:FindByName( "SenakiKolkhi Runway 1" ) - -- self.Airbases.SenakiKolkhi.ZoneRunways[1] = ZONE_POLYGON:New( "SenakiKolkhi Runway 1", SenakiKolkhiRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() - -- - -- - -- - -- -- SochiAdler - -- local SochiAdlerBoundary = GROUP:FindByName( "SochiAdler Boundary" ) - -- self.Airbases.SochiAdler.ZoneBoundary = ZONE_POLYGON:New( "SochiAdler Boundary", SochiAdlerBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() - -- - -- local SochiAdlerRunway1 = GROUP:FindByName( "SochiAdler Runway 1" ) - -- self.Airbases.SochiAdler.ZoneRunways[1] = ZONE_POLYGON:New( "SochiAdler Runway 1", SochiAdlerRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() - -- local SochiAdlerRunway2 = GROUP:FindByName( "SochiAdler Runway 2" ) - -- self.Airbases.SochiAdler.ZoneRunways[2] = ZONE_POLYGON:New( "SochiAdler Runway 2", SochiAdlerRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() - -- - -- - -- - -- -- Soganlug - -- local SoganlugBoundary = GROUP:FindByName( "Soganlug Boundary" ) - -- self.Airbases.Soganlug.ZoneBoundary = ZONE_POLYGON:New( "Soganlug Boundary", SoganlugBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() - -- - -- local SoganlugRunway1 = GROUP:FindByName( "Soganlug Runway 1" ) - -- self.Airbases.Soganlug.ZoneRunways[1] = ZONE_POLYGON:New( "Soganlug Runway 1", SoganlugRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() - -- - -- - -- - -- -- SukhumiBabushara - -- local SukhumiBabusharaBoundary = GROUP:FindByName( "SukhumiBabushara Boundary" ) - -- self.Airbases.SukhumiBabushara.ZoneBoundary = ZONE_POLYGON:New( "SukhumiBabushara Boundary", SukhumiBabusharaBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() - -- - -- local SukhumiBabusharaRunway1 = GROUP:FindByName( "SukhumiBabushara Runway 1" ) - -- self.Airbases.SukhumiBabushara.ZoneRunways[1] = ZONE_POLYGON:New( "SukhumiBabushara Runway 1", SukhumiBabusharaRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() - -- - -- - -- - -- -- TbilisiLochini - -- local TbilisiLochiniBoundary = GROUP:FindByName( "TbilisiLochini Boundary" ) - -- self.Airbases.TbilisiLochini.ZoneBoundary = ZONE_POLYGON:New( "TbilisiLochini Boundary", TbilisiLochiniBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() - -- - -- local TbilisiLochiniRunway1 = GROUP:FindByName( "TbilisiLochini Runway 1" ) - -- self.Airbases.TbilisiLochini.ZoneRunways[1] = ZONE_POLYGON:New( "TbilisiLochini Runway 1", TbilisiLochiniRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() - -- - -- local TbilisiLochiniRunway2 = GROUP:FindByName( "TbilisiLochini Runway 2" ) - -- self.Airbases.TbilisiLochini.ZoneRunways[2] = ZONE_POLYGON:New( "TbilisiLochini Runway 2", TbilisiLochiniRunway2 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() - -- - -- - -- - -- -- Vaziani - -- local VazianiBoundary = GROUP:FindByName( "Vaziani Boundary" ) - -- self.Airbases.Vaziani.ZoneBoundary = ZONE_POLYGON:New( "Vaziani Boundary", VazianiBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() - -- - -- local VazianiRunway1 = GROUP:FindByName( "Vaziani Runway 1" ) - -- self.Airbases.Vaziani.ZoneRunways[1] = ZONE_POLYGON:New( "Vaziani Runway 1", VazianiRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() - -- - -- - -- - - - -- Template - -- local TemplateBoundary = GROUP:FindByName( "Template Boundary" ) - -- self.Airbases.Template.ZoneBoundary = ZONE_POLYGON:New( "Template Boundary", TemplateBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() - -- - -- local TemplateRunway1 = GROUP:FindByName( "Template Runway 1" ) - -- self.Airbases.Template.ZoneRunways[1] = ZONE_POLYGON:New( "Template Runway 1", TemplateRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() - - return self - -end - - - - ---- @type AIRBASEPOLICE_NEVADA --- @extends AirbasePolice#AIRBASEPOLICE_BASE -AIRBASEPOLICE_NEVADA = { - ClassName = "AIRBASEPOLICE_NEVADA", - Airbases = { - Nellis = { - PointsBoundary = { - [1]={["y"]=-17814.714285714,["x"]=-399823.14285714,}, - [2]={["y"]=-16875.857142857,["x"]=-398763.14285714,}, - [3]={["y"]=-16251.571428571,["x"]=-398988.85714286,}, - [4]={["y"]=-16163,["x"]=-398693.14285714,}, - [5]={["y"]=-16328.714285714,["x"]=-398034.57142857,}, - [6]={["y"]=-15943,["x"]=-397571.71428571,}, - [7]={["y"]=-15711.571428571,["x"]=-397551.71428571,}, - [8]={["y"]=-15748.714285714,["x"]=-396806,}, - [9]={["y"]=-16288.714285714,["x"]=-396517.42857143,}, - [10]={["y"]=-16751.571428571,["x"]=-396308.85714286,}, - [11]={["y"]=-17263,["x"]=-396234.57142857,}, - [12]={["y"]=-17577.285714286,["x"]=-396640.28571429,}, - [13]={["y"]=-17614.428571429,["x"]=-397400.28571429,}, - [14]={["y"]=-19405.857142857,["x"]=-399428.85714286,}, - [15]={["y"]=-19234.428571429,["x"]=-399683.14285714,}, - [16]={["y"]=-18708.714285714,["x"]=-399408.85714286,}, - [17]={["y"]=-18397.285714286,["x"]=-399657.42857143,}, - [18]={["y"]=-17814.428571429,["x"]=-399823.42857143,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=-18687,["x"]=-399380.28571429,}, - [2]={["y"]=-18620.714285714,["x"]=-399436.85714286,}, - [3]={["y"]=-16217.857142857,["x"]=-396596.85714286,}, - [4]={["y"]=-16300.142857143,["x"]=-396530,}, - [5]={["y"]=-18687,["x"]=-399380.85714286,}, - }, - [2] = { - [1]={["y"]=-18451.571428572,["x"]=-399580.57142857,}, - [2]={["y"]=-18392.142857143,["x"]=-399628.57142857,}, - [3]={["y"]=-16011,["x"]=-396806.85714286,}, - [4]={["y"]=-16074.714285714,["x"]=-396751.71428572,}, - [5]={["y"]=-18451.571428572,["x"]=-399580.85714285,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - McCarran = { - PointsBoundary = { - [1]={["y"]=-29455.285714286,["x"]=-416277.42857142,}, - [2]={["y"]=-28860.142857143,["x"]=-416492,}, - [3]={["y"]=-25044.428571429,["x"]=-416344.85714285,}, - [4]={["y"]=-24580.142857143,["x"]=-415959.14285714,}, - [5]={["y"]=-25073,["x"]=-415630.57142857,}, - [6]={["y"]=-25087.285714286,["x"]=-415130.57142857,}, - [7]={["y"]=-25830.142857143,["x"]=-414866.28571428,}, - [8]={["y"]=-26658.714285715,["x"]=-414880.57142857,}, - [9]={["y"]=-26973,["x"]=-415273.42857142,}, - [10]={["y"]=-27380.142857143,["x"]=-415187.71428571,}, - [11]={["y"]=-27715.857142857,["x"]=-414144.85714285,}, - [12]={["y"]=-27551.571428572,["x"]=-413473.42857142,}, - [13]={["y"]=-28630.142857143,["x"]=-413201.99999999,}, - [14]={["y"]=-29494.428571429,["x"]=-415437.71428571,}, - [15]={["y"]=-29455.571428572,["x"]=-416277.71428571,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=-29408.428571429,["x"]=-416016.28571428,}, - [2]={["y"]=-29408.142857144,["x"]=-416105.42857142,}, - [3]={["y"]=-24680.714285715,["x"]=-416003.14285713,}, - [4]={["y"]=-24681.857142858,["x"]=-415926.57142856,}, - [5]={["y"]=-29408.42857143,["x"]=-416016.57142856,}, - }, - [2] = { - [1]={["y"]=-28575.571428572,["x"]=-416303.14285713,}, - [2]={["y"]=-28575.571428572,["x"]=-416382.57142856,}, - [3]={["y"]=-25111.000000001,["x"]=-416309.7142857,}, - [4]={["y"]=-25111.000000001,["x"]=-416249.14285713,}, - [5]={["y"]=-28575.571428572,["x"]=-416303.7142857,}, - }, - [3] = { - [1]={["y"]=-29331.000000001,["x"]=-416275.42857141,}, - [2]={["y"]=-29259.000000001,["x"]=-416306.85714284,}, - [3]={["y"]=-28005.571428572,["x"]=-413449.7142857,}, - [4]={["y"]=-28068.714285715,["x"]=-413422.85714284,}, - [5]={["y"]=-29331.000000001,["x"]=-416275.7142857,}, - }, - [4] = { - [1]={["y"]=-29073.285714286,["x"]=-416386.57142856,}, - [2]={["y"]=-28997.285714286,["x"]=-416417.42857141,}, - [3]={["y"]=-27697.571428572,["x"]=-413464.57142856,}, - [4]={["y"]=-27767.857142858,["x"]=-413434.28571427,}, - [5]={["y"]=-29073.000000001,["x"]=-416386.85714284,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - Creech = { - PointsBoundary = { - [1]={["y"]=-74522.714285715,["x"]=-360887.99999998,}, - [2]={["y"]=-74197,["x"]=-360556.57142855,}, - [3]={["y"]=-74402.714285715,["x"]=-359639.42857141,}, - [4]={["y"]=-74637,["x"]=-359279.42857141,}, - [5]={["y"]=-75759.857142857,["x"]=-359005.14285712,}, - [6]={["y"]=-75834.142857143,["x"]=-359045.14285712,}, - [7]={["y"]=-75902.714285714,["x"]=-359782.28571427,}, - [8]={["y"]=-76099.857142857,["x"]=-360399.42857141,}, - [9]={["y"]=-77314.142857143,["x"]=-360219.42857141,}, - [10]={["y"]=-77728.428571429,["x"]=-360445.14285713,}, - [11]={["y"]=-77585.571428571,["x"]=-360585.14285713,}, - [12]={["y"]=-76471.285714286,["x"]=-360819.42857141,}, - [13]={["y"]=-76325.571428571,["x"]=-360942.28571427,}, - [14]={["y"]=-74671.857142857,["x"]=-360927.7142857,}, - [15]={["y"]=-74522.714285714,["x"]=-360888.85714284,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=-74237.571428571,["x"]=-360591.7142857,}, - [2]={["y"]=-74234.428571429,["x"]=-360493.71428571,}, - [3]={["y"]=-77605.285714286,["x"]=-360399.14285713,}, - [4]={["y"]=-77608.714285715,["x"]=-360498.85714285,}, - [5]={["y"]=-74237.857142857,["x"]=-360591.7142857,}, - }, - [2] = { - [1]={["y"]=-75807.571428572,["x"]=-359073.42857142,}, - [2]={["y"]=-74770.142857144,["x"]=-360581.71428571,}, - [3]={["y"]=-74641.285714287,["x"]=-360585.42857142,}, - [4]={["y"]=-75734.142857144,["x"]=-359023.14285714,}, - [5]={["y"]=-75807.285714287,["x"]=-359073.42857142,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - GroomLake = { - PointsBoundary = { - [1]={["y"]=-88916.714285714,["x"]=-289102.28571425,}, - [2]={["y"]=-87023.571428572,["x"]=-290388.57142857,}, - [3]={["y"]=-85916.428571429,["x"]=-290674.28571428,}, - [4]={["y"]=-87645.000000001,["x"]=-286567.14285714,}, - [5]={["y"]=-88380.714285715,["x"]=-286388.57142857,}, - [6]={["y"]=-89670.714285715,["x"]=-283524.28571428,}, - [7]={["y"]=-89797.857142858,["x"]=-283567.14285714,}, - [8]={["y"]=-88635.000000001,["x"]=-286749.99999999,}, - [9]={["y"]=-89177.857142858,["x"]=-287207.14285714,}, - [10]={["y"]=-89092.142857144,["x"]=-288892.85714285,}, - [11]={["y"]=-88917.000000001,["x"]=-289102.85714285,}, - }, - PointsRunways = { - [1] = { - [1]={["y"]=-86039.000000001,["x"]=-290606.28571428,}, - [2]={["y"]=-85965.285714287,["x"]=-290573.99999999,}, - [3]={["y"]=-87692.714285715,["x"]=-286634.85714285,}, - [4]={["y"]=-87756.714285715,["x"]=-286663.99999999,}, - [5]={["y"]=-86038.714285715,["x"]=-290606.85714285,}, - }, - [2] = { - [1]={["y"]=-86808.428571429,["x"]=-290375.7142857,}, - [2]={["y"]=-86732.714285715,["x"]=-290344.28571427,}, - [3]={["y"]=-89672.714285714,["x"]=-283546.57142855,}, - [4]={["y"]=-89772.142857143,["x"]=-283587.71428569,}, - [5]={["y"]=-86808.142857143,["x"]=-290375.7142857,}, - }, - }, - ZoneBoundary = {}, - ZoneRunways = {}, - MaximumSpeed = 50, - }, - }, -} - ---- Creates a new AIRBASEPOLICE_NEVADA object. --- @param #AIRBASEPOLICE_NEVADA self --- @param SetClient A SET_CLIENT object that will contain the CLIENT objects to be monitored if they follow the rules of the airbase. --- @return #AIRBASEPOLICE_NEVADA self -function AIRBASEPOLICE_NEVADA:New( SetClient ) - - -- Inherits from BASE - local self = BASE:Inherit( self, AIRBASEPOLICE_BASE:New( SetClient, self.Airbases ) ) - --- -- Nellis --- local NellisBoundary = GROUP:FindByName( "Nellis Boundary" ) --- self.Airbases.Nellis.ZoneBoundary = ZONE_POLYGON:New( "Nellis Boundary", NellisBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() --- --- local NellisRunway1 = GROUP:FindByName( "Nellis Runway 1" ) --- self.Airbases.Nellis.ZoneRunways[1] = ZONE_POLYGON:New( "Nellis Runway 1", NellisRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() --- --- local NellisRunway2 = GROUP:FindByName( "Nellis Runway 2" ) --- self.Airbases.Nellis.ZoneRunways[2] = ZONE_POLYGON:New( "Nellis Runway 2", NellisRunway2 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() --- --- -- McCarran --- local McCarranBoundary = GROUP:FindByName( "McCarran Boundary" ) --- self.Airbases.McCarran.ZoneBoundary = ZONE_POLYGON:New( "McCarran Boundary", McCarranBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() --- --- local McCarranRunway1 = GROUP:FindByName( "McCarran Runway 1" ) --- self.Airbases.McCarran.ZoneRunways[1] = ZONE_POLYGON:New( "McCarran Runway 1", McCarranRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() --- --- local McCarranRunway2 = GROUP:FindByName( "McCarran Runway 2" ) --- self.Airbases.McCarran.ZoneRunways[2] = ZONE_POLYGON:New( "McCarran Runway 2", McCarranRunway2 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() --- --- local McCarranRunway3 = GROUP:FindByName( "McCarran Runway 3" ) --- self.Airbases.McCarran.ZoneRunways[3] = ZONE_POLYGON:New( "McCarran Runway 3", McCarranRunway3 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() --- --- local McCarranRunway4 = GROUP:FindByName( "McCarran Runway 4" ) --- self.Airbases.McCarran.ZoneRunways[4] = ZONE_POLYGON:New( "McCarran Runway 4", McCarranRunway4 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() --- --- -- Creech --- local CreechBoundary = GROUP:FindByName( "Creech Boundary" ) --- self.Airbases.Creech.ZoneBoundary = ZONE_POLYGON:New( "Creech Boundary", CreechBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() --- --- local CreechRunway1 = GROUP:FindByName( "Creech Runway 1" ) --- self.Airbases.Creech.ZoneRunways[1] = ZONE_POLYGON:New( "Creech Runway 1", CreechRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() --- --- local CreechRunway2 = GROUP:FindByName( "Creech Runway 2" ) --- self.Airbases.Creech.ZoneRunways[2] = ZONE_POLYGON:New( "Creech Runway 2", CreechRunway2 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() --- --- -- Groom Lake --- local GroomLakeBoundary = GROUP:FindByName( "GroomLake Boundary" ) --- self.Airbases.GroomLake.ZoneBoundary = ZONE_POLYGON:New( "GroomLake Boundary", GroomLakeBoundary ):SmokeZone(POINT_VEC3.SmokeColor.White):Flush() --- --- local GroomLakeRunway1 = GROUP:FindByName( "GroomLake Runway 1" ) --- self.Airbases.GroomLake.ZoneRunways[1] = ZONE_POLYGON:New( "GroomLake Runway 1", GroomLakeRunway1 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() --- --- local GroomLakeRunway2 = GROUP:FindByName( "GroomLake Runway 2" ) --- self.Airbases.GroomLake.ZoneRunways[2] = ZONE_POLYGON:New( "GroomLake Runway 2", GroomLakeRunway2 ):SmokeZone(POINT_VEC3.SmokeColor.Red):Flush() - -end - - - - - - --- This module contains the DETECTION classes. --- --- === --- --- 1) @{Detection#DETECTION_BASE} class, extends @{Base#BASE} --- ========================================================== --- The @{Detection#DETECTION_BASE} class defines the core functions to administer detected objects. --- The @{Detection#DETECTION_BASE} class will detect objects within the battle zone for a list of @{Group}s detecting targets following (a) detection method(s). --- --- 1.1) DETECTION_BASE constructor --- ------------------------------- --- Construct a new DETECTION_BASE instance using the @{Detection#DETECTION_BASE.New}() method. --- --- 1.2) DETECTION_BASE initialization --- ---------------------------------- --- By default, detection will return detected objects with all the detection sensors available. --- However, you can ask how the objects were found with specific detection methods. --- If you use one of the below methods, the detection will work with the detection method specified. --- You can specify to apply multiple detection methods. --- --- Use the following functions to report the objects it detected using the methods Visual, Optical, Radar, IRST, RWR, DLINK: --- --- * @{Detection#DETECTION_BASE.InitDetectVisual}(): Detected using Visual. --- * @{Detection#DETECTION_BASE.InitDetectOptical}(): Detected using Optical. --- * @{Detection#DETECTION_BASE.InitDetectRadar}(): Detected using Radar. --- * @{Detection#DETECTION_BASE.InitDetectIRST}(): Detected using IRST. --- * @{Detection#DETECTION_BASE.InitDetectRWR}(): Detected using RWR. --- * @{Detection#DETECTION_BASE.InitDetectDLINK}(): Detected using DLINK. --- --- 1.3) Obtain objects detected by DETECTION_BASE --- ---------------------------------------------- --- DETECTION_BASE builds @{Set}s of objects detected. These @{Set#SET_BASE}s can be retrieved using the method @{Detection#DETECTION_BASE.GetDetectedSets}(). --- The method will return a list (table) of @{Set#SET_BASE} objects. --- --- === --- --- 2) @{Detection#DETECTION_AREAS} class, extends @{Detection#DETECTION_BASE} --- =============================================================================== --- The @{Detection#DETECTION_AREAS} class will detect units within the battle zone for a list of @{Group}s detecting targets following (a) detection method(s), --- and will build a list (table) of @{Set#SET_UNIT}s containing the @{Unit#UNIT}s detected. --- The class is group the detected units within zones given a DetectedZoneRange parameter. --- A set with multiple detected zones will be created as there are groups of units detected. --- --- 2.1) Retrieve the Detected Unit sets and Detected Zones --- ------------------------------------------------------- --- The DetectedUnitSets methods are implemented in @{Detection#DECTECTION_BASE} and the DetectedZones methods is implemented in @{Detection#DETECTION_AREAS}. --- --- Retrieve the DetectedUnitSets with the method @{Detection#DETECTION_BASE.GetDetectedSets}(). A table will be return of @{Set#SET_UNIT}s. --- To understand the amount of sets created, use the method @{Detection#DETECTION_BASE.GetDetectedSetCount}(). --- If you want to obtain a specific set from the DetectedSets, use the method @{Detection#DETECTION_BASE.GetDetectedSet}() with a given index. --- --- Retrieve the formed @{Zone@ZONE_UNIT}s as a result of the grouping the detected units within the DetectionZoneRange, use the method @{Detection#DETECTION_BASE.GetDetectionZones}(). --- To understand the amount of zones created, use the method @{Detection#DETECTION_BASE.GetDetectionZoneCount}(). --- If you want to obtain a specific zone from the DetectedZones, use the method @{Detection#DETECTION_BASE.GetDetectionZone}() with a given index. --- --- 1.4) Flare or Smoke detected units --- ---------------------------------- --- Use the methods @{Detection#DETECTION_AREAS.FlareDetectedUnits}() or @{Detection#DETECTION_AREAS.SmokeDetectedUnits}() to flare or smoke the detected units when a new detection has taken place. --- --- 1.5) Flare or Smoke detected zones --- ---------------------------------- --- Use the methods @{Detection#DETECTION_AREAS.FlareDetectedZones}() or @{Detection#DETECTION_AREAS.SmokeDetectedZones}() to flare or smoke the detected zones when a new detection has taken place. --- --- === --- --- ### Contributions: Mechanist - Concept & Testing --- ### Authors: FlightControl : Design & Programming --- --- @module Detection - - - ---- DETECTION_BASE class --- @type DETECTION_BASE --- @field Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Forward Air Controller role. --- @field DCSTypes#Distance DetectionRange The range till which targets are accepted to be detected. --- @field #DETECTION_BASE.DetectedObjects DetectedObjects The list of detected objects. --- @field #table DetectedObjectsIdentified Map of the DetectedObjects identified. --- @field #number DetectionRun --- @extends Base#BASE -DETECTION_BASE = { - ClassName = "DETECTION_BASE", - DetectionSetGroup = nil, - DetectionRange = nil, - DetectedObjects = {}, - DetectionRun = 0, - DetectedObjectsIdentified = {}, -} - ---- @type DETECTION_BASE.DetectedObjects --- @list <#DETECTION_BASE.DetectedObject> - ---- @type DETECTION_BASE.DetectedObject --- @field #string Name --- @field #boolean Visible --- @field #string Type --- @field #number Distance --- @field #boolean Identified - ---- DETECTION constructor. --- @param #DETECTION_BASE self --- @param Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Forward Air Controller role. --- @param DCSTypes#Distance DetectionRange The range till which targets are accepted to be detected. --- @return #DETECTION_BASE self -function DETECTION_BASE:New( DetectionSetGroup, DetectionRange ) - - -- Inherits from BASE - local self = BASE:Inherit( self, BASE:New() ) - - self.DetectionSetGroup = DetectionSetGroup - self.DetectionRange = DetectionRange - - self:InitDetectVisual( false ) - self:InitDetectOptical( false ) - self:InitDetectRadar( false ) - self:InitDetectRWR( false ) - self:InitDetectIRST( false ) - self:InitDetectDLINK( false ) - - return self -end - ---- Detect Visual. --- @param #DETECTION_BASE self --- @param #boolean DetectVisual --- @return #DETECTION_BASE self -function DETECTION_BASE:InitDetectVisual( DetectVisual ) - - self.DetectVisual = DetectVisual -end - ---- Detect Optical. --- @param #DETECTION_BASE self --- @param #boolean DetectOptical --- @return #DETECTION_BASE self -function DETECTION_BASE:InitDetectOptical( DetectOptical ) - self:F2() - - self.DetectOptical = DetectOptical -end - ---- Detect Radar. --- @param #DETECTION_BASE self --- @param #boolean DetectRadar --- @return #DETECTION_BASE self -function DETECTION_BASE:InitDetectRadar( DetectRadar ) - self:F2() - - self.DetectRadar = DetectRadar -end - ---- Detect IRST. --- @param #DETECTION_BASE self --- @param #boolean DetectIRST --- @return #DETECTION_BASE self -function DETECTION_BASE:InitDetectIRST( DetectIRST ) - self:F2() - - self.DetectIRST = DetectIRST -end - ---- Detect RWR. --- @param #DETECTION_BASE self --- @param #boolean DetectRWR --- @return #DETECTION_BASE self -function DETECTION_BASE:InitDetectRWR( DetectRWR ) - self:F2() - - self.DetectRWR = DetectRWR -end - ---- Detect DLINK. --- @param #DETECTION_BASE self --- @param #boolean DetectDLINK --- @return #DETECTION_BASE self -function DETECTION_BASE:InitDetectDLINK( DetectDLINK ) - self:F2() - - self.DetectDLINK = DetectDLINK -end - ---- Determines if a detected object has already been identified during detection processing. --- @param #DETECTION_BASE self --- @param #DETECTION_BASE.DetectedObject DetectedObject --- @return #boolean true if already identified. -function DETECTION_BASE:IsDetectedObjectIdentified( DetectedObject ) - self:F3( DetectedObject.Name ) - - local DetectedObjectName = DetectedObject.Name - local DetectedObjectIdentified = self.DetectedObjectsIdentified[DetectedObjectName] == true - self:T3( DetectedObjectIdentified ) - return DetectedObjectIdentified -end - ---- Identifies a detected object during detection processing. --- @param #DETECTION_BASE self --- @param #DETECTION_BASE.DetectedObject DetectedObject -function DETECTION_BASE:IdentifyDetectedObject( DetectedObject ) - self:F( DetectedObject.Name ) - - local DetectedObjectName = DetectedObject.Name - self.DetectedObjectsIdentified[DetectedObjectName] = true -end - ---- UnIdentify a detected object during detection processing. --- @param #DETECTION_BASE self --- @param #DETECTION_BASE.DetectedObject DetectedObject -function DETECTION_BASE:UnIdentifyDetectedObject( DetectedObject ) - - local DetectedObjectName = DetectedObject.Name - self.DetectedObjectsIdentified[DetectedObjectName] = false -end - ---- UnIdentify all detected objects during detection processing. --- @param #DETECTION_BASE self -function DETECTION_BASE:UnIdentifyAllDetectedObjects() - - self.DetectedObjectsIdentified = {} -- Table will be garbage collected. -end - ---- Gets a detected object with a given name. --- @param #DETECTION_BASE self --- @param #string ObjectName --- @return #DETECTION_BASE.DetectedObject -function DETECTION_BASE:GetDetectedObject( ObjectName ) - self:F3( ObjectName ) - - if ObjectName then - local DetectedObject = self.DetectedObjects[ObjectName] - - -- Only return detected objects that are alive! - local DetectedUnit = UNIT:FindByName( ObjectName ) - if DetectedUnit and DetectedUnit:IsAlive() then - if self:IsDetectedObjectIdentified( DetectedObject ) == false then - return DetectedObject - end - end - end - - return nil -end - ---- Get the detected @{Set#SET_BASE}s. --- @param #DETECTION_BASE self --- @return #DETECTION_BASE.DetectedSets DetectedSets -function DETECTION_BASE:GetDetectedSets() - - local DetectionSets = self.DetectedSets - return DetectionSets -end - ---- Get the amount of SETs with detected objects. --- @param #DETECTION_BASE self --- @return #number Count -function DETECTION_BASE:GetDetectedSetCount() - - local DetectionSetCount = #self.DetectedSets - return DetectionSetCount -end - ---- Get a SET of detected objects using a given numeric index. --- @param #DETECTION_BASE self --- @param #number Index --- @return Set#SET_BASE -function DETECTION_BASE:GetDetectedSet( Index ) - - local DetectionSet = self.DetectedSets[Index] - if DetectionSet then - return DetectionSet - end - - return nil -end - ---- Get the detection Groups. --- @param #DETECTION_BASE self --- @return Group#GROUP -function DETECTION_BASE:GetDetectionSetGroup() - - local DetectionSetGroup = self.DetectionSetGroup - return DetectionSetGroup -end - ---- Make a DetectionSet table. This function will be overridden in the derived clsses. --- @param #DETECTION_BASE self --- @return #DETECTION_BASE self -function DETECTION_BASE:CreateDetectionSets() - self:F2() - - self:E( "Error, in DETECTION_BASE class..." ) - -end - - ---- Schedule the DETECTION construction. --- @param #DETECTION_BASE self --- @param #number DelayTime The delay in seconds to wait the reporting. --- @param #number RepeatInterval The repeat interval in seconds for the reporting to happen repeatedly. --- @return #DETECTION_BASE self -function DETECTION_BASE:Schedule( DelayTime, RepeatInterval ) - self:F2() - - self.ScheduleDelayTime = DelayTime - self.ScheduleRepeatInterval = RepeatInterval - - self.DetectionScheduler = SCHEDULER:New(self, self._DetectionScheduler, { self, "Detection" }, DelayTime, RepeatInterval ) - return self -end - - ---- Form @{Set}s of detected @{Unit#UNIT}s in an array of @{Set#SET_BASE}s. --- @param #DETECTION_BASE self -function DETECTION_BASE:_DetectionScheduler( SchedulerName ) - self:F2( { SchedulerName } ) - - self.DetectionRun = self.DetectionRun + 1 - - self:UnIdentifyAllDetectedObjects() -- Resets the DetectedObjectsIdentified table - - for DetectionGroupID, DetectionGroupData in pairs( self.DetectionSetGroup:GetSet() ) do - local DetectionGroup = DetectionGroupData -- Group#GROUP - - if DetectionGroup:IsAlive() then - - local DetectionGroupName = DetectionGroup:GetName() - - local DetectionDetectedTargets = DetectionGroup:GetDetectedTargets( - self.DetectVisual, - self.DetectOptical, - self.DetectRadar, - self.DetectIRST, - self.DetectRWR, - self.DetectDLINK - ) - - for DetectionDetectedTargetID, DetectionDetectedTarget in pairs( DetectionDetectedTargets ) do - local DetectionObject = DetectionDetectedTarget.object -- DCSObject#Object - self:T2( DetectionObject ) - - if DetectionObject and DetectionObject:isExist() and DetectionObject.id_ < 50000000 then - - local DetectionDetectedObjectName = DetectionObject:getName() - - local DetectionDetectedObjectPositionVec3 = DetectionObject:getPoint() - local DetectionGroupPositionVec3 = DetectionGroup:GetPointVec3() - - local Distance = ( ( DetectionDetectedObjectPositionVec3.x - DetectionGroupPositionVec3.x )^2 + - ( DetectionDetectedObjectPositionVec3.y - DetectionGroupPositionVec3.y )^2 + - ( DetectionDetectedObjectPositionVec3.z - DetectionGroupPositionVec3.z )^2 - ) ^ 0.5 / 1000 - - self:T2( { DetectionGroupName, DetectionDetectedObjectName, Distance } ) - - if Distance <= self.DetectionRange then - - if not self.DetectedObjects[DetectionDetectedObjectName] then - self.DetectedObjects[DetectionDetectedObjectName] = {} - end - self.DetectedObjects[DetectionDetectedObjectName].Name = DetectionDetectedObjectName - self.DetectedObjects[DetectionDetectedObjectName].Visible = DetectionDetectedTarget.visible - self.DetectedObjects[DetectionDetectedObjectName].Type = DetectionDetectedTarget.type - self.DetectedObjects[DetectionDetectedObjectName].Distance = DetectionDetectedTarget.distance - else - -- if beyond the DetectionRange then nullify... - if self.DetectedObjects[DetectionDetectedObjectName] then - self.DetectedObjects[DetectionDetectedObjectName] = nil - end - end - end - end - - self:T2( self.DetectedObjects ) - - -- okay, now we have a list of detected object names ... - -- Sort the table based on distance ... - table.sort( self.DetectedObjects, function( a, b ) return a.Distance < b.Distance end ) - end - end - - if self.DetectedObjects then - self:CreateDetectionSets() - end - - return true -end - - - ---- DETECTION_AREAS class --- @type DETECTION_AREAS --- @field DCSTypes#Distance DetectionZoneRange The range till which targets are grouped upon the first detected target. --- @field #DETECTION_AREAS.DetectedAreas DetectedAreas A list of areas containing the set of @{Unit}s, @{Zone}s, the center @{Unit} within the zone, and ID of each area that was detected within a DetectionZoneRange. --- @extends Detection#DETECTION_BASE -DETECTION_AREAS = { - ClassName = "DETECTION_AREAS", - DetectedAreas = { n = 0 }, - DetectionZoneRange = nil, -} - ---- @type DETECTION_AREAS.DetectedAreas --- @list <#DETECTION_AREAS.DetectedArea> - ---- @type DETECTION_AREAS.DetectedArea --- @field Set#SET_UNIT Set -- The Set of Units in the detected area. --- @field Zone#ZONE_UNIT Zone -- The Zone of the detected area. --- @field #boolean Changed Documents if the detected area has changes. --- @field #table Changes A list of the changes reported on the detected area. (It is up to the user of the detected area to consume those changes). --- @field #number AreaID -- The identifier of the detected area. --- @field #boolean FriendliesNearBy Indicates if there are friendlies within the detected area. --- @field Unit#UNIT NearestFAC The nearest FAC near the Area. - - ---- DETECTION_AREAS constructor. --- @param Detection#DETECTION_AREAS self --- @param Set#SET_GROUP DetectionSetGroup The @{Set} of GROUPs in the Forward Air Controller role. --- @param DCSTypes#Distance DetectionRange The range till which targets are accepted to be detected. --- @param DCSTypes#Distance DetectionZoneRange The range till which targets are grouped upon the first detected target. --- @return Detection#DETECTION_AREAS self -function DETECTION_AREAS:New( DetectionSetGroup, DetectionRange, DetectionZoneRange ) - - -- Inherits from DETECTION_BASE - local self = BASE:Inherit( self, DETECTION_BASE:New( DetectionSetGroup, DetectionRange ) ) - - self.DetectionZoneRange = DetectionZoneRange - - self._SmokeDetectedUnits = false - self._FlareDetectedUnits = false - self._SmokeDetectedZones = false - self._FlareDetectedZones = false - - self:Schedule( 0, 15 ) - - return self -end - ---- Add a detected @{#DETECTION_AREAS.DetectedArea}. --- @param Set#SET_UNIT Set -- The Set of Units in the detected area. --- @param Zone#ZONE_UNIT Zone -- The Zone of the detected area. --- @return #DETECTION_AREAS.DetectedArea DetectedArea -function DETECTION_AREAS:AddDetectedArea( Set, Zone ) - local DetectedAreas = self:GetDetectedAreas() - DetectedAreas.n = self:GetDetectedAreaCount() + 1 - DetectedAreas[DetectedAreas.n] = {} - local DetectedArea = DetectedAreas[DetectedAreas.n] - DetectedArea.Set = Set - DetectedArea.Zone = Zone - DetectedArea.Removed = false - DetectedArea.AreaID = DetectedAreas.n - - return DetectedArea -end - ---- Remove a detected @{#DETECTION_AREAS.DetectedArea} with a given Index. --- @param #DETECTION_AREAS self --- @param #number Index The Index of the detection are to be removed. --- @return #nil -function DETECTION_AREAS:RemoveDetectedArea( Index ) - local DetectedAreas = self:GetDetectedAreas() - local DetectedAreaCount = self:GetDetectedAreaCount() - local DetectedArea = DetectedAreas[Index] - local DetectedAreaSet = DetectedArea.Set - DetectedArea[Index] = nil - return nil -end - - ---- Get the detected @{#DETECTION_AREAS.DetectedAreas}. --- @param #DETECTION_AREAS self --- @return #DETECTION_AREAS.DetectedAreas DetectedAreas -function DETECTION_AREAS:GetDetectedAreas() - - local DetectedAreas = self.DetectedAreas - return DetectedAreas -end - ---- Get the amount of @{#DETECTION_AREAS.DetectedAreas}. --- @param #DETECTION_AREAS self --- @return #number DetectedAreaCount -function DETECTION_AREAS:GetDetectedAreaCount() - - local DetectedAreaCount = self.DetectedAreas.n - return DetectedAreaCount -end - ---- Get the @{Set#SET_UNIT} of a detecttion area using a given numeric index. --- @param #DETECTION_AREAS self --- @param #number Index --- @return Set#SET_UNIT DetectedSet -function DETECTION_AREAS:GetDetectedSet( Index ) - - local DetectedSetUnit = self.DetectedAreas[Index].Set - if DetectedSetUnit then - return DetectedSetUnit - end - - return nil -end - ---- Get the @{Zone#ZONE_UNIT} of a detection area using a given numeric index. --- @param #DETECTION_AREAS self --- @param #number Index --- @return Zone#ZONE_UNIT DetectedZone -function DETECTION_AREAS:GetDetectedZone( Index ) - - local DetectedZone = self.DetectedAreas[Index].Zone - if DetectedZone then - return DetectedZone - end - - return nil -end - ---- Background worker function to determine if there are friendlies nearby ... --- @param #DETECTION_AREAS self --- @param Unit#UNIT ReportUnit -function DETECTION_AREAS:ReportFriendliesNearBy( ReportGroupData ) - self:F2() - - local DetectedArea = ReportGroupData.DetectedArea -- Detection#DETECTION_AREAS.DetectedArea - local DetectedSet = ReportGroupData.DetectedArea.Set - local DetectedZone = ReportGroupData.DetectedArea.Zone - local DetectedZoneUnit = DetectedZone.ZoneUNIT - - DetectedArea.FriendliesNearBy = false - - local SphereSearch = { - id = world.VolumeType.SPHERE, - params = { - point = DetectedZoneUnit:GetPointVec3(), - radius = 6000, - } - - } - - --- @param DCSUnit#Unit FoundDCSUnit - -- @param Group#GROUP ReportGroup - -- @param Set#SET_GROUP ReportSetGroup - local FindNearByFriendlies = function( FoundDCSUnit, ReportGroupData ) - - local DetectedArea = ReportGroupData.DetectedArea -- Detection#DETECTION_AREAS.DetectedArea - local DetectedSet = ReportGroupData.DetectedArea.Set - local DetectedZone = ReportGroupData.DetectedArea.Zone - local DetectedZoneUnit = DetectedZone.ZoneUNIT -- Unit#UNIT - local ReportSetGroup = ReportGroupData.ReportSetGroup - - local EnemyCoalition = DetectedZoneUnit:GetCoalition() - - local FoundUnitCoalition = FoundDCSUnit:getCoalition() - local FoundUnitName = FoundDCSUnit:getName() - local FoundUnitGroupName = FoundDCSUnit:getGroup():getName() - local EnemyUnitName = DetectedZoneUnit:GetName() - local FoundUnitInReportSetGroup = ReportSetGroup:FindGroup( FoundUnitGroupName ) ~= nil - - self:T3( { "Friendlies search:", FoundUnitName, FoundUnitCoalition, EnemyUnitName, EnemyCoalition, FoundUnitInReportSetGroup } ) - - if FoundUnitCoalition ~= EnemyCoalition and FoundUnitInReportSetGroup == false then - DetectedArea.FriendliesNearBy = true - return false - end - - return true - end - - world.searchObjects( Object.Category.UNIT, SphereSearch, FindNearByFriendlies, ReportGroupData ) - -end - - - ---- Returns if there are friendlies nearby the FAC units ... --- @param #DETECTION_AREAS self --- @return #boolean trhe if there are friendlies nearby -function DETECTION_AREAS:IsFriendliesNearBy( DetectedArea ) - - self:T3( DetectedArea.FriendliesNearBy ) - return DetectedArea.FriendliesNearBy or false -end - ---- Calculate the maxium A2G threat level of the DetectedArea. --- @param #DETECTION_AREAS self --- @param #DETECTION_AREAS.DetectedArea DetectedArea -function DETECTION_AREAS:CalculateThreatLevelA2G( DetectedArea ) - - local MaxThreatLevelA2G = 0 - for UnitName, UnitData in pairs( DetectedArea.Set:GetSet() ) do - local ThreatUnit = UnitData -- Unit#UNIT - local ThreatLevelA2G = ThreatUnit:GetThreatLevel() - if ThreatLevelA2G > MaxThreatLevelA2G then - MaxThreatLevelA2G = ThreatLevelA2G - end - end - - self:T3( MaxThreatLevelA2G ) - DetectedArea.MaxThreatLevelA2G = MaxThreatLevelA2G - -end - ---- Find the nearest FAC of the DetectedArea. --- @param #DETECTION_AREAS self --- @param #DETECTION_AREAS.DetectedArea DetectedArea --- @return Unit#UNIT The nearest FAC unit -function DETECTION_AREAS:NearestFAC( DetectedArea ) - - local NearestFAC = nil - local MinDistance = 1000000000 -- Units are not further than 1000000 km away from an area :-) - - for FACGroupName, FACGroupData in pairs( self.DetectionSetGroup:GetSet() ) do - for FACUnit, FACUnitData in pairs( FACGroupData:GetUnits() ) do - local FACUnit = FACUnitData -- Unit#UNIT - if FACUnit:IsActive() then - local Vec3 = FACUnit:GetPointVec3() - local PointVec3 = POINT_VEC3:NewFromVec3( Vec3 ) - local Distance = PointVec3:Get2DDistance(POINT_VEC3:NewFromVec3( FACUnit:GetPointVec3() ) ) - self:E( "Distance", Distance ) - if Distance < MinDistance then - MinDistance = Distance - NearestFAC = FACUnit - end - end - end - end - - self:E( { NearestFAC.UnitName, MinDistance } ) - DetectedArea.NearestFAC = NearestFAC - -end - ---- Returns the A2G threat level of the units in the DetectedArea --- @param #DETECTION_AREAS self --- @param #DETECTION_AREAS.DetectedArea DetectedArea --- @return #number a scale from 0 to 10. -function DETECTION_AREAS:GetTreatLevelA2G( DetectedArea ) - - self:T3( DetectedArea.MaxThreatLevelA2G ) - return DetectedArea.MaxThreatLevelA2G -end - - - ---- Smoke the detected units --- @param #DETECTION_AREAS self --- @return #DETECTION_AREAS self -function DETECTION_AREAS:SmokeDetectedUnits() - self:F2() - - self._SmokeDetectedUnits = true - return self -end - ---- Flare the detected units --- @param #DETECTION_AREAS self --- @return #DETECTION_AREAS self -function DETECTION_AREAS:FlareDetectedUnits() - self:F2() - - self._FlareDetectedUnits = true - return self -end - ---- Smoke the detected zones --- @param #DETECTION_AREAS self --- @return #DETECTION_AREAS self -function DETECTION_AREAS:SmokeDetectedZones() - self:F2() - - self._SmokeDetectedZones = true - return self -end - ---- Flare the detected zones --- @param #DETECTION_AREAS self --- @return #DETECTION_AREAS self -function DETECTION_AREAS:FlareDetectedZones() - self:F2() - - self._FlareDetectedZones = true - return self -end - ---- Add a change to the detected zone. --- @param #DETECTION_AREAS self --- @param #DETECTION_AREAS.DetectedArea DetectedArea --- @param #string ChangeCode --- @return #DETECTION_AREAS self -function DETECTION_AREAS:AddChangeArea( DetectedArea, ChangeCode, AreaUnitType ) - - DetectedArea.Changed = true - local AreaID = DetectedArea.AreaID - - DetectedArea.Changes = DetectedArea.Changes or {} - DetectedArea.Changes[ChangeCode] = DetectedArea.Changes[ChangeCode] or {} - DetectedArea.Changes[ChangeCode].AreaID = AreaID - DetectedArea.Changes[ChangeCode].AreaUnitType = AreaUnitType - - self:T( { "Change on Detection Area:", DetectedArea.AreaID, ChangeCode, AreaUnitType } ) - - return self -end - - ---- Add a change to the detected zone. --- @param #DETECTION_AREAS self --- @param #DETECTION_AREAS.DetectedArea DetectedArea --- @param #string ChangeCode --- @param #string ChangeUnitType --- @return #DETECTION_AREAS self -function DETECTION_AREAS:AddChangeUnit( DetectedArea, ChangeCode, ChangeUnitType ) - - DetectedArea.Changed = true - local AreaID = DetectedArea.AreaID - - DetectedArea.Changes = DetectedArea.Changes or {} - DetectedArea.Changes[ChangeCode] = DetectedArea.Changes[ChangeCode] or {} - DetectedArea.Changes[ChangeCode][ChangeUnitType] = DetectedArea.Changes[ChangeCode][ChangeUnitType] or 0 - DetectedArea.Changes[ChangeCode][ChangeUnitType] = DetectedArea.Changes[ChangeCode][ChangeUnitType] + 1 - DetectedArea.Changes[ChangeCode].AreaID = AreaID - - self:T( { "Change on Detection Area:", DetectedArea.AreaID, ChangeCode, ChangeUnitType } ) - - return self -end - ---- Make text documenting the changes of the detected zone. --- @param #DETECTION_AREAS self --- @param #DETECTION_AREAS.DetectedArea DetectedArea --- @return #string The Changes text -function DETECTION_AREAS:GetChangeText( DetectedArea ) - self:F( DetectedArea ) - - local MT = {} - - for ChangeCode, ChangeData in pairs( DetectedArea.Changes ) do - - if ChangeCode == "AA" then - MT[#MT+1] = "Detected new area " .. ChangeData.AreaID .. ". The center target is a " .. ChangeData.AreaUnitType .. "." - end - - if ChangeCode == "RAU" then - MT[#MT+1] = "Changed area " .. ChangeData.AreaID .. ". Removed the center target " .. ChangeData.AreaUnitType "." - end - - if ChangeCode == "AAU" then - MT[#MT+1] = "Changed area " .. ChangeData.AreaID .. ". The new center target is a " .. ChangeData.AreaUnitType "." - end - - if ChangeCode == "RA" then - MT[#MT+1] = "Removed old area " .. ChangeData.AreaID .. ". No more targets in this area." - end - - if ChangeCode == "AU" then - local MTUT = {} - for ChangeUnitType, ChangeUnitCount in pairs( ChangeData ) do - if ChangeUnitType ~= "AreaID" then - MTUT[#MTUT+1] = ChangeUnitCount .. " of " .. ChangeUnitType - end - end - MT[#MT+1] = "Detected for area " .. ChangeData.AreaID .. " new target(s) " .. table.concat( MTUT, ", " ) .. "." - end - - if ChangeCode == "RU" then - local MTUT = {} - for ChangeUnitType, ChangeUnitCount in pairs( ChangeData ) do - if ChangeUnitType ~= "AreaID" then - MTUT[#MTUT+1] = ChangeUnitCount .. " of " .. ChangeUnitType - end - end - MT[#MT+1] = "Removed for area " .. ChangeData.AreaID .. " invisible or destroyed target(s) " .. table.concat( MTUT, ", " ) .. "." - end - - end - - return table.concat( MT, "\n" ) - -end - - ---- Accepts changes from the detected zone. --- @param #DETECTION_AREAS self --- @param #DETECTION_AREAS.DetectedArea DetectedArea --- @return #DETECTION_AREAS self -function DETECTION_AREAS:AcceptChanges( DetectedArea ) - - DetectedArea.Changed = false - DetectedArea.Changes = {} - - return self -end - - ---- Make a DetectionSet table. This function will be overridden in the derived clsses. --- @param #DETECTION_AREAS self --- @return #DETECTION_AREAS self -function DETECTION_AREAS:CreateDetectionSets() - self:F2() - - -- First go through all detected sets, and check if there are new detected units, match all existing detected units and identify undetected units. - -- Regroup when needed, split groups when needed. - for DetectedAreaID, DetectedAreaData in ipairs( self.DetectedAreas ) do - - local DetectedArea = DetectedAreaData -- #DETECTION_AREAS.DetectedArea - if DetectedArea then - - local DetectedSet = DetectedArea.Set - - local AreaExists = false -- This flag will determine of the detected area is still existing. - - -- First test if the center unit is detected in the detection area. - self:T3( DetectedArea.Zone.ZoneUNIT.UnitName ) - local DetectedZoneObject = self:GetDetectedObject( DetectedArea.Zone.ZoneUNIT.UnitName ) - self:T3( { "Detecting Zone Object", DetectedArea.AreaID, DetectedArea.Zone, DetectedZoneObject } ) - - if DetectedZoneObject then - - --self:IdentifyDetectedObject( DetectedZoneObject ) - AreaExists = true - - - - else - -- The center object of the detected area has not been detected. Find an other unit of the set to become the center of the area. - -- First remove the center unit from the set. - DetectedSet:RemoveUnitsByName( DetectedArea.Zone.ZoneUNIT.UnitName ) - - self:AddChangeArea( DetectedArea, 'RAU', DetectedArea.Zone.ZoneUNIT:GetTypeName() ) - - -- Then search for a new center area unit within the set. Note that the new area unit candidate must be within the area range. - for DetectedUnitName, DetectedUnitData in pairs( DetectedSet:GetSet() ) do - - local DetectedUnit = DetectedUnitData -- Unit#UNIT - local DetectedObject = self:GetDetectedObject( DetectedUnit.UnitName ) - - -- The DetectedObject can be nil when the DetectedUnit is not alive anymore or it is not in the DetectedObjects map. - -- If the DetectedUnit was already identified, DetectedObject will be nil. - if DetectedObject then - self:IdentifyDetectedObject( DetectedObject ) - AreaExists = true - - -- Assign the Unit as the new center unit of the detected area. - DetectedArea.Zone = ZONE_UNIT:New( DetectedUnit:GetName(), DetectedUnit, self.DetectionZoneRange ) - - self:AddChangeArea( DetectedArea, "AAU", DetectedArea.Zone.ZoneUNIT:GetTypeName() ) - - -- We don't need to add the DetectedObject to the area set, because it is already there ... - break - end - end - end - - -- Now we've determined the center unit of the area, now we can iterate the units in the detected area. - -- Note that the position of the area may have moved due to the center unit repositioning. - -- If no center unit was identified, then the detected area does not exist anymore and should be deleted, as there are no valid units that can be the center unit. - if AreaExists then - - -- ok, we found the center unit of the area, now iterate through the detected area set and see which units are still within the center unit zone ... - -- Those units within the zone are flagged as Identified. - -- If a unit was not found in the set, remove it from the set. This may be added later to other existing or new sets. - for DetectedUnitName, DetectedUnitData in pairs( DetectedSet:GetSet() ) do - - local DetectedUnit = DetectedUnitData -- Unit#UNIT - local DetectedObject = nil - if DetectedUnit:IsAlive() then - --self:E(DetectedUnit:GetName()) - DetectedObject = self:GetDetectedObject( DetectedUnit:GetName() ) - end - if DetectedObject then - - -- Check if the DetectedUnit is within the DetectedArea.Zone - if DetectedUnit:IsInZone( DetectedArea.Zone ) then - - -- Yes, the DetectedUnit is within the DetectedArea.Zone, no changes, DetectedUnit can be kept within the Set. - self:IdentifyDetectedObject( DetectedObject ) - - else - -- No, the DetectedUnit is not within the DetectedArea.Zone, remove DetectedUnit from the Set. - DetectedSet:Remove( DetectedUnitName ) - self:AddChangeUnit( DetectedArea, "RU", DetectedUnit:GetTypeName() ) - end - - else - -- There was no DetectedObject, remove DetectedUnit from the Set. - self:AddChangeUnit( DetectedArea, "RU", "destroyed target" ) - DetectedSet:Remove( DetectedUnitName ) - - -- The DetectedObject has been identified, because it does not exist ... - -- self:IdentifyDetectedObject( DetectedObject ) - end - end - else - self:RemoveDetectedArea( DetectedAreaID ) - self:AddChangeArea( DetectedArea, "RA" ) - end - end - end - - -- We iterated through the existing detection areas and: - -- - We checked which units are still detected in each detection area. Those units were flagged as Identified. - -- - We recentered the detection area to new center units where it was needed. - -- - -- Now we need to loop through the unidentified detected units and see where they belong: - -- - They can be added to a new detection area and become the new center unit. - -- - They can be added to a new detection area. - for DetectedUnitName, DetectedObjectData in pairs( self.DetectedObjects ) do - - local DetectedObject = self:GetDetectedObject( DetectedUnitName ) - - if DetectedObject then - - -- We found an unidentified unit outside of any existing detection area. - local DetectedUnit = UNIT:FindByName( DetectedUnitName ) -- Unit#UNIT - - local AddedToDetectionArea = false - - for DetectedAreaID, DetectedAreaData in ipairs( self.DetectedAreas ) do - - local DetectedArea = DetectedAreaData -- #DETECTION_AREAS.DetectedArea - if DetectedArea then - self:T( "Detection Area #" .. DetectedArea.AreaID ) - local DetectedSet = DetectedArea.Set - if not self:IsDetectedObjectIdentified( DetectedObject ) and DetectedUnit:IsInZone( DetectedArea.Zone ) then - self:IdentifyDetectedObject( DetectedObject ) - DetectedSet:AddUnit( DetectedUnit ) - AddedToDetectionArea = true - self:AddChangeUnit( DetectedArea, "AU", DetectedUnit:GetTypeName() ) - end - end - end - - if AddedToDetectionArea == false then - - -- New detection area - local DetectedArea = self:AddDetectedArea( - SET_UNIT:New(), - ZONE_UNIT:New( DetectedUnitName, DetectedUnit, self.DetectionZoneRange ) - ) - --self:E( DetectedArea.Zone.ZoneUNIT.UnitName ) - DetectedArea.Set:AddUnit( DetectedUnit ) - self:AddChangeArea( DetectedArea, "AA", DetectedUnit:GetTypeName() ) - end - end - end - - -- Now all the tests should have been build, now make some smoke and flares... - -- We also report here the friendlies within the detected areas. - - for DetectedAreaID, DetectedAreaData in ipairs( self.DetectedAreas ) do - - local DetectedArea = DetectedAreaData -- #DETECTION_AREAS.DetectedArea - local DetectedSet = DetectedArea.Set - local DetectedZone = DetectedArea.Zone - - self:ReportFriendliesNearBy( { DetectedArea = DetectedArea, ReportSetGroup = self.DetectionSetGroup } ) -- Fill the Friendlies table - self:CalculateThreatLevelA2G( DetectedArea ) -- Calculate A2G threat level - self:NearestFAC( DetectedArea ) - - if DETECTION_AREAS._SmokeDetectedUnits or self._SmokeDetectedUnits then - DetectedZone.ZoneUNIT:SmokeRed() - end - DetectedSet:ForEachUnit( - --- @param Unit#UNIT DetectedUnit - function( DetectedUnit ) - if DetectedUnit:IsAlive() then - self:T( "Detected Set #" .. DetectedArea.AreaID .. ":" .. DetectedUnit:GetName() ) - if DETECTION_AREAS._FlareDetectedUnits or self._FlareDetectedUnits then - DetectedUnit:FlareGreen() - end - if DETECTION_AREAS._SmokeDetectedUnits or self._SmokeDetectedUnits then - DetectedUnit:SmokeGreen() - end - end - end - ) - if DETECTION_AREAS._FlareDetectedZones or self._FlareDetectedZones then - DetectedZone:FlareZone( POINT_VEC3.SmokeColor.White, 30, math.random( 0,90 ) ) - end - if DETECTION_AREAS._SmokeDetectedZones or self._SmokeDetectedZones then - DetectedZone:SmokeZone( POINT_VEC3.SmokeColor.White, 30 ) - end - end - -end - - ---- This module contains the DETECTION_MANAGER class and derived classes. --- --- === --- --- 1) @{DetectionManager#DETECTION_MANAGER} class, extends @{Base#BASE} --- ==================================================================== --- The @{DetectionManager#DETECTION_MANAGER} class defines the core functions to report detected objects to groups. --- Reportings can be done in several manners, and it is up to the derived classes if DETECTION_MANAGER to model the reporting behaviour. --- --- 1.1) DETECTION_MANAGER constructor: --- ----------------------------------- --- * @{DetectionManager#DETECTION_MANAGER.New}(): Create a new DETECTION_MANAGER instance. --- --- 1.2) DETECTION_MANAGER reporting: --- --------------------------------- --- Derived DETECTION_MANAGER classes will reports detected units using the method @{DetectionManager#DETECTION_MANAGER.ReportDetected}(). This method implements polymorphic behaviour. --- --- The time interval in seconds of the reporting can be changed using the methods @{DetectionManager#DETECTION_MANAGER.SetReportInterval}(). --- To control how long a reporting message is displayed, use @{DetectionManager#DETECTION_MANAGER.SetReportDisplayTime}(). --- Derived classes need to implement the method @{DetectionManager#DETECTION_MANAGER.GetReportDisplayTime}() to use the correct display time for displayed messages during a report. --- --- Reporting can be started and stopped using the methods @{DetectionManager#DETECTION_MANAGER.StartReporting}() and @{DetectionManager#DETECTION_MANAGER.StopReporting}() respectively. --- If an ad-hoc report is requested, use the method @{DetectionManager#DETECTION_MANAGER#ReportNow}(). --- --- The default reporting interval is every 60 seconds. The reporting messages are displayed 15 seconds. --- --- === --- --- 2) @{DetectionManager#DETECTION_REPORTING} class, extends @{DetectionManager#DETECTION_MANAGER} --- ========================================================================================= --- The @{DetectionManager#DETECTION_REPORTING} class implements detected units reporting. Reporting can be controlled using the reporting methods available in the @{DetectionManager#DETECTION_MANAGER} class. --- --- 2.1) DETECTION_REPORTING constructor: --- ------------------------------- --- The @{DetectionManager#DETECTION_REPORTING.New}() method creates a new DETECTION_REPORTING instance. --- --- === --- --- 3) @{#DETECTION_DISPATCHER} class, extends @{#DETECTION_MANAGER} --- ================================================================ --- The @{#DETECTION_DISPATCHER} class implements the dynamic dispatching of tasks upon groups of detected units determined a @{Set} of FAC (groups). --- The FAC will detect units, will group them, and will dispatch @{Task}s to groups. Depending on the type of target detected, different tasks will be dispatched. --- Find a summary below describing for which situation a task type is created: --- --- * **CAS Task**: Is created when there are enemy ground units within range of the FAC, while there are friendly units in the FAC perimeter. --- * **BAI Task**: Is created when there are enemy ground units within range of the FAC, while there are NO other friendly units within the FAC perimeter. --- * **SEAD Task**: Is created when there are enemy ground units wihtin range of the FAC, with air search radars. --- --- Other task types will follow... --- --- 3.1) DETECTION_DISPATCHER constructor: --- -------------------------------------- --- The @{#DETECTION_DISPATCHER.New}() method creates a new DETECTION_DISPATCHER instance. --- --- === --- --- ### Contributions: Mechanist, Prof_Hilactic, FlightControl - Concept & Testing --- ### Author: FlightControl - Framework Design & Programming --- --- @module DetectionManager - -do -- DETECTION MANAGER - - --- DETECTION_MANAGER class. - -- @type DETECTION_MANAGER - -- @field Set#SET_GROUP SetGroup The groups to which the FAC will report to. - -- @field Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects. - -- @extends Base#BASE - DETECTION_MANAGER = { - ClassName = "DETECTION_MANAGER", - SetGroup = nil, - Detection = nil, - } - - --- FAC constructor. - -- @param #DETECTION_MANAGER self - -- @param Set#SET_GROUP SetGroup - -- @param Detection#DETECTION_BASE Detection - -- @return #DETECTION_MANAGER self - function DETECTION_MANAGER:New( SetGroup, Detection ) - - -- Inherits from BASE - local self = BASE:Inherit( self, BASE:New() ) -- Detection#DETECTION_MANAGER - - self.SetGroup = SetGroup - self.Detection = Detection - - self:SetReportInterval( 30 ) - self:SetReportDisplayTime( 25 ) - - return self - end - - --- Set the reporting time interval. - -- @param #DETECTION_MANAGER self - -- @param #number ReportInterval The interval in seconds when a report needs to be done. - -- @return #DETECTION_MANAGER self - function DETECTION_MANAGER:SetReportInterval( ReportInterval ) - self:F2() - - self._ReportInterval = ReportInterval - end - - - --- Set the reporting message display time. - -- @param #DETECTION_MANAGER self - -- @param #number ReportDisplayTime The display time in seconds when a report needs to be done. - -- @return #DETECTION_MANAGER self - function DETECTION_MANAGER:SetReportDisplayTime( ReportDisplayTime ) - self:F2() - - self._ReportDisplayTime = ReportDisplayTime - end - - --- Get the reporting message display time. - -- @param #DETECTION_MANAGER self - -- @return #number ReportDisplayTime The display time in seconds when a report needs to be done. - function DETECTION_MANAGER:GetReportDisplayTime() - self:F2() - - return self._ReportDisplayTime - end - - - - --- Reports the detected items to the @{Set#SET_GROUP}. - -- @param #DETECTION_MANAGER self - -- @param Detection#DETECTION_BASE Detection - -- @return #DETECTION_MANAGER self - function DETECTION_MANAGER:ReportDetected( Detection ) - self:F2() - - end - - --- Schedule the FAC reporting. - -- @param #DETECTION_MANAGER self - -- @param #number DelayTime The delay in seconds to wait the reporting. - -- @param #number ReportInterval The repeat interval in seconds for the reporting to happen repeatedly. - -- @return #DETECTION_MANAGER self - function DETECTION_MANAGER:Schedule( DelayTime, ReportInterval ) - self:F2() - - self._ScheduleDelayTime = DelayTime - - self:SetReportInterval( ReportInterval ) - - self.FacScheduler = SCHEDULER:New(self, self._FacScheduler, { self, "DetectionManager" }, self._ScheduleDelayTime, self._ReportInterval ) - return self - end - - --- Report the detected @{Unit#UNIT}s detected within the @{Detection#DETECTION_BASE} object to the @{Set#SET_GROUP}s. - -- @param #DETECTION_MANAGER self - function DETECTION_MANAGER:_FacScheduler( SchedulerName ) - self:F2( { SchedulerName } ) - - return self:ProcessDetected( self.Detection ) - --- self.SetGroup:ForEachGroup( --- --- @param Group#GROUP Group --- function( Group ) --- if Group:IsAlive() then --- return self:ProcessDetected( self.Detection ) --- end --- end --- ) - --- return true - end - -end - - -do -- DETECTION_REPORTING - - --- DETECTION_REPORTING class. - -- @type DETECTION_REPORTING - -- @field Set#SET_GROUP SetGroup The groups to which the FAC will report to. - -- @field Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects. - -- @extends #DETECTION_MANAGER - DETECTION_REPORTING = { - ClassName = "DETECTION_REPORTING", - } - - - --- DETECTION_REPORTING constructor. - -- @param #DETECTION_REPORTING self - -- @param Set#SET_GROUP SetGroup - -- @param Detection#DETECTION_AREAS Detection - -- @return #DETECTION_REPORTING self - function DETECTION_REPORTING:New( SetGroup, Detection ) - - -- Inherits from DETECTION_MANAGER - local self = BASE:Inherit( self, DETECTION_MANAGER:New( SetGroup, Detection ) ) -- #DETECTION_REPORTING - - self:Schedule( 1, 30 ) - return self - end - - --- Creates a string of the detected items in a @{Detection}. - -- @param #DETECTION_MANAGER self - -- @param Set#SET_UNIT DetectedSet The detected Set created by the @{Detection#DETECTION_BASE} object. - -- @return #DETECTION_MANAGER self - function DETECTION_REPORTING:GetDetectedItemsText( DetectedSet ) - self:F2() - - local MT = {} -- Message Text - local UnitTypes = {} - - for DetectedUnitID, DetectedUnitData in pairs( DetectedSet:GetSet() ) do - local DetectedUnit = DetectedUnitData -- Unit#UNIT - if DetectedUnit:IsAlive() then - local UnitType = DetectedUnit:GetTypeName() - - if not UnitTypes[UnitType] then - UnitTypes[UnitType] = 1 - else - UnitTypes[UnitType] = UnitTypes[UnitType] + 1 - end - end - end - - for UnitTypeID, UnitType in pairs( UnitTypes ) do - MT[#MT+1] = UnitType .. " of " .. UnitTypeID - end - - return table.concat( MT, ", " ) - end - - - - --- Reports the detected items to the @{Set#SET_GROUP}. - -- @param #DETECTION_REPORTING self - -- @param Group#GROUP Group The @{Group} object to where the report needs to go. - -- @param Detection#DETECTION_AREAS Detection The detection created by the @{Detection#DETECTION_BASE} object. - -- @return #boolean Return true if you want the reporting to continue... false will cancel the reporting loop. - function DETECTION_REPORTING:ProcessDetected( Group, Detection ) - self:F2( Group ) - - self:E( Group ) - local DetectedMsg = {} - for DetectedAreaID, DetectedAreaData in pairs( Detection:GetDetectedAreas() ) do - local DetectedArea = DetectedAreaData -- Detection#DETECTION_AREAS.DetectedArea - DetectedMsg[#DetectedMsg+1] = " - Group #" .. DetectedAreaID .. ": " .. self:GetDetectedItemsText( DetectedArea.Set ) - end - local FACGroup = Detection:GetDetectionGroups() - FACGroup:MessageToGroup( "Reporting detected target groups:\n" .. table.concat( DetectedMsg, "\n" ), self:GetReportDisplayTime(), Group ) - - return true - end - -end - -do -- DETECTION_DISPATCHER - - --- DETECTION_DISPATCHER class. - -- @type DETECTION_DISPATCHER - -- @field Set#SET_GROUP SetGroup The groups to which the FAC will report to. - -- @field Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects. - -- @field Mission#MISSION Mission - -- @field Group#GROUP CommandCenter - -- @extends DetectionManager#DETECTION_MANAGER - DETECTION_DISPATCHER = { - ClassName = "DETECTION_DISPATCHER", - Mission = nil, - CommandCenter = nil, - Detection = nil, - } - - - --- DETECTION_DISPATCHER constructor. - -- @param #DETECTION_DISPATCHER self - -- @param Set#SET_GROUP SetGroup - -- @param Detection#DETECTION_BASE Detection - -- @return #DETECTION_DISPATCHER self - function DETECTION_DISPATCHER:New( Mission, CommandCenter, SetGroup, Detection ) - - -- Inherits from DETECTION_MANAGER - local self = BASE:Inherit( self, DETECTION_MANAGER:New( SetGroup, Detection ) ) -- #DETECTION_DISPATCHER - - self.Detection = Detection - self.CommandCenter = CommandCenter - self.Mission = Mission - - self:Schedule( 30 ) - return self - end - - - --- Creates a SEAD task when there are targets for it. - -- @param #DETECTION_DISPATCHER self - -- @param Detection#DETECTION_AREAS.DetectedArea DetectedArea - -- @return Set#SET_UNIT TargetSetUnit: The target set of units. - -- @return #nil If there are no targets to be set. - function DETECTION_DISPATCHER:EvaluateSEAD( DetectedArea ) - self:F( { DetectedArea.AreaID } ) - - local DetectedSet = DetectedArea.Set - local DetectedZone = DetectedArea.Zone - - -- Determine if the set has radar targets. If it does, construct a SEAD task. - local RadarCount = DetectedSet:HasSEAD() - - if RadarCount > 0 then - - -- Here we're doing something advanced... We're copying the DetectedSet, but making a new Set only with SEADable Radar units in it. - local TargetSetUnit = SET_UNIT:New() - TargetSetUnit:SetDatabase( DetectedSet ) - TargetSetUnit:FilterHasSEAD() - TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection. - - return TargetSetUnit - end - - return nil - end - - --- Creates a CAS task when there are targets for it. - -- @param #DETECTION_DISPATCHER self - -- @param Detection#DETECTION_AREAS.DetectedArea DetectedArea - -- @return Task#TASK_BASE - function DETECTION_DISPATCHER:EvaluateCAS( DetectedArea ) - self:F( { DetectedArea.AreaID } ) - - local DetectedSet = DetectedArea.Set - local DetectedZone = DetectedArea.Zone - - - -- Determine if the set has radar targets. If it does, construct a SEAD task. - local GroundUnitCount = DetectedSet:HasGroundUnits() - local FriendliesNearBy = self.Detection:IsFriendliesNearBy( DetectedArea ) - - if GroundUnitCount > 0 and FriendliesNearBy == true then - - -- Copy the Set - local TargetSetUnit = SET_UNIT:New() - TargetSetUnit:SetDatabase( DetectedSet ) - TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection. - - return TargetSetUnit - end - - return nil - end - - --- Creates a BAI task when there are targets for it. - -- @param #DETECTION_DISPATCHER self - -- @param Detection#DETECTION_AREAS.DetectedArea DetectedArea - -- @return Task#TASK_BASE - function DETECTION_DISPATCHER:EvaluateBAI( DetectedArea, FriendlyCoalition ) - self:F( { DetectedArea.AreaID } ) - - local DetectedSet = DetectedArea.Set - local DetectedZone = DetectedArea.Zone - - - -- Determine if the set has radar targets. If it does, construct a SEAD task. - local GroundUnitCount = DetectedSet:HasGroundUnits() - local FriendliesNearBy = self.Detection:IsFriendliesNearBy( DetectedArea ) - - if GroundUnitCount > 0 and FriendliesNearBy == false then - - -- Copy the Set - local TargetSetUnit = SET_UNIT:New() - TargetSetUnit:SetDatabase( DetectedSet ) - TargetSetUnit:FilterOnce() -- Filter but don't do any events!!! Elements are added manually upon each detection. - - return TargetSetUnit - end - - return nil - end - - --- Evaluates the removal of the Task from the Mission. - -- Can only occur when the DetectedArea is Changed AND the state of the Task is "Planned". - -- @param #DETECTION_DISPATCHER self - -- @param Mission#MISSION Mission - -- @param Task#TASK_BASE Task - -- @param Detection#DETECTION_AREAS.DetectedArea DetectedArea - -- @return Task#TASK_BASE - function DETECTION_DISPATCHER:EvaluateRemoveTask( Mission, Task, DetectedArea ) - - if Task then - if Task:IsStatePlanned() and DetectedArea.Changed == true then - Mission:RemoveTaskMenu( Task ) - Task = Mission:RemoveTask( Task ) - end - end - - return Task - end - - - --- Assigns tasks in relation to the detected items to the @{Set#SET_GROUP}. - -- @param #DETECTION_DISPATCHER self - -- @param Detection#DETECTION_AREAS Detection The detection created by the @{Detection#DETECTION_AREAS} object. - -- @return #boolean Return true if you want the task assigning to continue... false will cancel the loop. - function DETECTION_DISPATCHER:ProcessDetected( Detection ) - self:F2() - - local AreaMsg = {} - local TaskMsg = {} - local ChangeMsg = {} - - local Mission = self.Mission - - --- First we need to the detected targets. - for DetectedAreaID, DetectedAreaData in ipairs( Detection:GetDetectedAreas() ) do - - local DetectedArea = DetectedAreaData -- Detection#DETECTION_AREAS.DetectedArea - local DetectedSet = DetectedArea.Set - local DetectedZone = DetectedArea.Zone - self:E( { "Targets in DetectedArea", DetectedArea.AreaID, DetectedSet:Count(), tostring( DetectedArea ) } ) - DetectedSet:Flush() - - local AreaID = DetectedArea.AreaID - - -- Evaluate SEAD Tasking - local SEADTask = Mission:GetTask( "SEAD." .. AreaID ) - SEADTask = self:EvaluateRemoveTask( Mission, SEADTask, DetectedArea ) - if not SEADTask then - local TargetSetUnit = self:EvaluateSEAD( DetectedArea ) -- Returns a SetUnit if there are targets to be SEADed... - if TargetSetUnit then - SEADTask = Mission:AddTask( TASK_SEAD:New( Mission, self.SetGroup, "SEAD." .. AreaID, TargetSetUnit , DetectedZone ) ):StatePlanned() - end - end - if SEADTask and SEADTask:IsStatePlanned() then - SEADTask:SetPlannedMenu() - TaskMsg[#TaskMsg+1] = " - " .. SEADTask:GetStateString() .. " SEAD " .. AreaID .. " - " .. SEADTask.TargetSetUnit:GetUnitTypesText() - end - - -- Evaluate CAS Tasking - local CASTask = Mission:GetTask( "CAS." .. AreaID ) - CASTask = self:EvaluateRemoveTask( Mission, CASTask, DetectedArea ) - if not CASTask then - local TargetSetUnit = self:EvaluateCAS( DetectedArea ) -- Returns a SetUnit if there are targets to be SEADed... - if TargetSetUnit then - CASTask = Mission:AddTask( TASK_A2G:New( Mission, self.SetGroup, "CAS." .. AreaID, "CAS", TargetSetUnit , DetectedZone, DetectedArea.NearestFAC ) ):StatePlanned() - end - end - if CASTask and CASTask:IsStatePlanned() then - CASTask:SetPlannedMenu() - TaskMsg[#TaskMsg+1] = " - " .. CASTask:GetStateString() .. " CAS " .. AreaID .. " - " .. CASTask.TargetSetUnit:GetUnitTypesText() - end - - -- Evaluate BAI Tasking - local BAITask = Mission:GetTask( "BAI." .. AreaID ) - BAITask = self:EvaluateRemoveTask( Mission, BAITask, DetectedArea ) - if not BAITask then - local TargetSetUnit = self:EvaluateBAI( DetectedArea, self.CommandCenter:GetCoalition() ) -- Returns a SetUnit if there are targets to be SEADed... - if TargetSetUnit then - BAITask = Mission:AddTask( TASK_A2G:New( Mission, self.SetGroup, "BAI." .. AreaID, "BAI", TargetSetUnit , DetectedZone, DetectedArea.NearestFAC ) ):StatePlanned() - end - end - if BAITask and BAITask:IsStatePlanned() then - BAITask:SetPlannedMenu() - TaskMsg[#TaskMsg+1] = " - " .. BAITask:GetStateString() .. " BAI " .. AreaID .. " - " .. BAITask.TargetSetUnit:GetUnitTypesText() - end - - if #TaskMsg > 0 then - - local ThreatLevel = Detection:GetTreatLevelA2G( DetectedArea ) - - local DetectedAreaVec3 = DetectedZone:GetPointVec3() - local DetectedAreaPointVec3 = POINT_VEC3:New( DetectedAreaVec3.x, DetectedAreaVec3.y, DetectedAreaVec3.z ) - local DetectedAreaPointLL = DetectedAreaPointVec3:ToStringLL( 3, true ) - AreaMsg[#AreaMsg+1] = string.format( " - Area #%d - %s - Threat Level [%s] (%2d)", - DetectedAreaID, - DetectedAreaPointLL, - string.rep( "â– ", ThreatLevel ), - ThreatLevel - ) - - -- Loop through the changes ... - local ChangeText = Detection:GetChangeText( DetectedArea ) - - if ChangeText ~= "" then - ChangeMsg[#ChangeMsg+1] = string.gsub( string.gsub( ChangeText, "\n", "%1 - " ), "^.", " - %1" ) - end - end - - -- OK, so the tasking has been done, now delete the changes reported for the area. - Detection:AcceptChanges( DetectedArea ) - - end - - if #AreaMsg > 0 then - for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - if not TaskGroup:GetState( TaskGroup, "Assigned" ) then - self.CommandCenter:MessageToGroup( - string.format( "HQ Reporting - Target areas for mission '%s':\nAreas:\n%s\n\nTasks:\n%s\n\nChanges:\n%s ", - self.Mission:GetName(), - table.concat( AreaMsg, "\n" ), - table.concat( TaskMsg, "\n" ), - table.concat( ChangeMsg, "\n" ) - ), self:GetReportDisplayTime(), TaskGroup - ) - end - end - end - - return true - end - -end--- This module contains the STATEMACHINE class. --- This development is based on a state machine implementation made by Conroy Kyle. --- The state machine can be found here: https://github.com/kyleconroy/lua-state-machine --- --- I've taken the development and enhanced it to make the state machine hierarchical... --- It is a fantastic development, this module. --- --- === --- --- 1) @{Workflow#STATEMACHINE} class, extends @{Base#BASE} --- ============================================== --- --- 1.1) Add or remove objects from the STATEMACHINE --- -------------------------------------------- --- @module StateMachine --- @author FlightControl - - ---- STATEMACHINE class --- @type STATEMACHINE -STATEMACHINE = { - ClassName = "STATEMACHINE", -} - ---- Creates a new STATEMACHINE object. --- @param #STATEMACHINE self --- @return #STATEMACHINE -function STATEMACHINE:New( options ) - - -- Inherits from BASE - local self = BASE:Inherit( self, BASE:New() ) - - - --local self = routines.utils.deepCopy( self ) -- Create a new self instance - - assert(options.events) - - --local MT = {} - --setmetatable( self, MT ) - --self.__index = self - - self.options = options - self.current = options.initial or 'none' - self.events = {} - self.subs = {} - self.endstates = {} - - for _, event in ipairs(options.events or {}) do - local name = event.name - self[name] = self[name] or self:_create_transition(name) - self.events[name] = self.events[name] or { map = {} } - self:_add_to_map(self.events[name].map, event) - end - - for name, callback in pairs(options.callbacks or {}) do - self[name] = callback - end - - for name, sub in pairs( options.subs or {} ) do - self:_submap( self.subs, sub, name ) - end - - for name, endstate in pairs( options.endstates or {} ) do - self.endstates[endstate] = endstate - end - - return self -end - - -function STATEMACHINE:_submap( subs, sub, name ) - self:E( { sub = sub, name = name } ) - subs[sub.onstateparent] = subs[sub.onstateparent] or {} - subs[sub.onstateparent][sub.oneventparent] = subs[sub.onstateparent][sub.oneventparent] or {} - local Index = #subs[sub.onstateparent][sub.oneventparent] + 1 - subs[sub.onstateparent][sub.oneventparent][Index] = {} - subs[sub.onstateparent][sub.oneventparent][Index].fsm = sub.fsm - subs[sub.onstateparent][sub.oneventparent][Index].event = sub.event - subs[sub.onstateparent][sub.oneventparent][Index].returnevents = sub.returnevents -- these events need to be given to find the correct continue event ... if none given, the processing will stop. - subs[sub.onstateparent][sub.oneventparent][Index].name = name - subs[sub.onstateparent][sub.oneventparent][Index].fsmparent = self -end - - -function STATEMACHINE:_call_handler(handler, params) - if handler then - return handler(unpack(params)) - end -end - -function STATEMACHINE:_create_transition(name) - self:E( { name = name } ) - return function(self, ...) - local can, to = self:can(name) - self:T( { name, can, to } ) - - if can then - local from = self.current - local params = { self, name, from, to, ... } - - if self:_call_handler(self["onbefore" .. name], params) == false - or self:_call_handler(self["onleave" .. from], params) == false then - return false - end - - self.current = to - - local execute = true - - local subtable = self:_gosub( to, name ) - for _, sub in pairs( subtable ) do - self:F( "calling sub: " .. sub.event ) - sub.fsm.fsmparent = self - sub.fsm.returnevents = sub.returnevents - sub.fsm[sub.event]( sub.fsm ) - execute = true - end - - local fsmparent, event = self:_isendstate( to ) - if fsmparent and event then - self:F( { "end state: ", fsmparent, event } ) - self:_call_handler(self["onenter" .. to] or self["on" .. to], params) - self:_call_handler(self["onafter" .. name] or self["on" .. name], params) - self:_call_handler(self["onstatechange"], params) - fsmparent[event]( fsmparent ) - execute = false - end - - if execute then - self:F( { "execute: " .. to, name } ) - self:_call_handler(self["onenter" .. to] or self["on" .. to], params) - self:_call_handler(self["onafter" .. name] or self["on" .. name], params) - self:_call_handler(self["onstatechange"], params) - end - - return true - end - - return false - end -end - -function STATEMACHINE:_gosub( parentstate, parentevent ) - local fsmtable = {} - if self.subs[parentstate] and self.subs[parentstate][parentevent] then - return self.subs[parentstate][parentevent] - else - return {} - end -end - -function STATEMACHINE:_isendstate( state ) - local fsmparent = self.fsmparent - if fsmparent and self.endstates[state] then - self:E( { state = state, endstates = self.endstates, endstate = self.endstates[state] } ) - local returnevent = nil - local fromstate = fsmparent.current - self:E( fromstate ) - self:E( self.returnevents ) - for _, eventname in pairs( self.returnevents ) do - local event = fsmparent.events[eventname] - self:E( event ) - local to = event and event.map[fromstate] or event.map['*'] - if to and to == state then - return fsmparent, eventname - else - self:E( { "could not find parent event name for state", fromstate, to } ) - end - end - end - - return nil -end - -function STATEMACHINE:_add_to_map(map, event) - if type(event.from) == 'string' then - map[event.from] = event.to - else - for _, from in ipairs(event.from) do - map[from] = event.to - end - end -end - -function STATEMACHINE:is(state) - return self.current == state -end - -function STATEMACHINE:can(e) - local event = self.events[e] - local to = event and event.map[self.current] or event.map['*'] - return to ~= nil, to -end - -function STATEMACHINE:cannot(e) - return not self:can(e) -end - -function STATEMACHINE:todot(filename) - local dotfile = io.open(filename,'w') - dotfile:write('digraph {\n') - local transition = function(event,from,to) - dotfile:write(string.format('%s -> %s [label=%s];\n',from,to,event)) - end - for _, event in pairs(self.options.events) do - if type(event.from) == 'table' then - for _, from in ipairs(event.from) do - transition(event.name,from,event.to) - end - else - transition(event.name,event.from,event.to) - end - end - dotfile:write('}\n') - dotfile:close() -end - ---- STATEMACHINE_PROCESS class --- @type STATEMACHINE_PROCESS --- @field Process#PROCESS Process --- @extends StateMachine#STATEMACHINE -STATEMACHINE_PROCESS = { - ClassName = "STATEMACHINE_PROCESS", -} - ---- Creates a new STATEMACHINE_PROCESS object. --- @param #STATEMACHINE_PROCESS self --- @return #STATEMACHINE_PROCESS -function STATEMACHINE_PROCESS:New( Process, options ) - - local FsmProcess = routines.utils.deepCopy( self ) -- Create a new self instance - local Parent = STATEMACHINE:New(options) - - setmetatable( FsmProcess, Parent ) - FsmProcess.__index = FsmProcess - - FsmProcess["onstatechange"] = Process.OnStateChange - FsmProcess.Process = Process - - return FsmProcess -end - -function STATEMACHINE_PROCESS:_call_handler( handler, params ) - if handler then - return handler( self.Process, unpack( params ) ) - end -end - ---- STATEMACHINE_TASK class --- @type STATEMACHINE_TASK --- @field Task#TASK_BASE Task --- @extends StateMachine#STATEMACHINE -STATEMACHINE_TASK = { - ClassName = "STATEMACHINE_TASK", -} - ---- Creates a new STATEMACHINE_TASK object. --- @param #STATEMACHINE_TASK self --- @return #STATEMACHINE_TASK -function STATEMACHINE_TASK:New( Task, TaskUnit, options ) - - local FsmTask = routines.utils.deepCopy( self ) -- Create a new self instance - local Parent = STATEMACHINE:New(options) - - setmetatable( FsmTask, Parent ) - FsmTask.__index = FsmTask - - FsmTask["onstatechange"] = Task.OnStateChange - FsmTask["onAssigned"] = Task.OnAssigned - FsmTask["onSuccess"] = Task.OnSuccess - FsmTask["onFailed"] = Task.OnFailed - - FsmTask.Task = Task - FsmTask.TaskUnit = TaskUnit - - return FsmTask -end - -function STATEMACHINE_TASK:_call_handler( handler, params ) - if handler then - return handler( self.Task, self.TaskUnit, unpack( params ) ) - end -end ---- @module Process - ---- The PROCESS class --- @type PROCESS --- @field Scheduler#SCHEDULER ProcessScheduler --- @field Unit#UNIT ProcessUnit --- @field Group#GROUP ProcessGroup --- @field Menu#MENU_GROUP MissionMenu --- @field Task#TASK_BASE Task --- @field StateMachine#STATEMACHINE_TASK Fsm --- @field #string ProcessName --- @extends Base#BASE -PROCESS = { - ClassName = "TASK", - ProcessScheduler = nil, - NextEvent = nil, - Scores = {}, -} - ---- Instantiates a new TASK Base. Should never be used. Interface Class. --- @param #PROCESS self --- @param #string ProcessName --- @param Task#TASK_BASE Task --- @param Unit#UNIT ProcessUnit --- @return #PROCESS self -function PROCESS:New( ProcessName, Task, ProcessUnit ) - local self = BASE:Inherit( self, BASE:New() ) - self:F() - - self.ProcessUnit = ProcessUnit - self.ProcessGroup = ProcessUnit:GetGroup() - self.MissionMenu = Task.Mission:GetMissionMenu( self.ProcessGroup ) - self.Task = Task - self.ProcessName = ProcessName - - self.AllowEvents = true - - return self -end - ---- @param #PROCESS self -function PROCESS:NextEvent( NextEvent, ... ) - if self.AllowEvents == true then - self.ProcessScheduler = SCHEDULER:New( self.Fsm, NextEvent, arg, 1 ) - end -end - ---- @param #PROCESS self -function PROCESS:StopEvents( ) - self:F2() - if self.ProcessScheduler then - self:E( "Stop" ) - self.ProcessScheduler:Stop() - self.ProcessScheduler = nil - self.AllowEvents = false - end -end - ---- Adds a score for the PROCESS to be achieved. --- @param #PROCESS self --- @param #string ProcessStatus is the status of the PROCESS when the score needs to be given. --- @param #string ScoreText is a text describing the score that is given according the status. --- @param #number Score is a number providing the score of the status. --- @return #PROCESS self -function PROCESS:AddScore( ProcessStatus, ScoreText, Score ) - self:F2( { ProcessStatus, ScoreText, Score } ) - - self.Scores[ProcessStatus] = self.Scores[ProcessStatus] or {} - self.Scores[ProcessStatus].ScoreText = ScoreText - self.Scores[ProcessStatus].Score = Score - return self -end - ---- StateMachine callback function for a PROCESS --- @param #PROCESS self --- @param StateMachine#STATEMACHINE_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To -function PROCESS:OnStateChange( Fsm, Event, From, To ) - self:E( { self.ProcessName, Event, From, To, self.ProcessUnit.UnitName } ) - - if self:IsTrace() then - MESSAGE:New( "Process " .. self.ProcessName .. " : " .. Event .. " changed to state " .. To, 15 ):ToAll() - end - - if self.Scores[To] then - - local Scoring = self.Task:GetScoring() - if Scoring then - Scoring:_AddMissionTaskScore( self.Task.Mission, self.ProcessUnit, self.Scores[To].ScoreText, self.Scores[To].Score ) - end - end -end - - ---- This module contains the TASK_ASSIGN classes. --- --- === --- --- 1) @{Task_Assign#TASK_ASSIGN_ACCEPT} class, extends @{Task#TASK_BASE} --- ===================================================================== --- The @{Task_Assign#TASK_ASSIGN_ACCEPT} class accepts by default a task for a player. No player intervention is allowed to reject the task. --- --- 2) @{Task_Assign#TASK_ASSIGN_MENU_ACCEPT} class, extends @{Task#TASK_BASE} --- ========================================================================== --- The @{Task_Assign#TASK_ASSIGN_MENU_ACCEPT} class accepts a task when the player accepts the task through an added menu option. --- This assignment type is useful to conditionally allow the player to choose whether or not he would accept the task. --- The assignment type also allows to reject the task. --- --- --- --- --- --- --- @module Task_Assign --- - - -do -- PROCESS_ASSIGN_ACCEPT - - --- PROCESS_ASSIGN_ACCEPT class - -- @type PROCESS_ASSIGN_ACCEPT - -- @field Task#TASK_BASE Task - -- @field Unit#UNIT ProcessUnit - -- @field Zone#ZONE_BASE TargetZone - -- @extends Task2#TASK2 - PROCESS_ASSIGN_ACCEPT = { - ClassName = "PROCESS_ASSIGN_ACCEPT", - } - - - --- Creates a new task assignment state machine. The process will accept the task by default, no player intervention accepted. - -- @param #PROCESS_ASSIGN_ACCEPT self - -- @param Task#TASK Task - -- @param Unit#UNIT Unit - -- @return #PROCESS_ASSIGN_ACCEPT self - function PROCESS_ASSIGN_ACCEPT:New( Task, ProcessUnit, TaskBriefing ) - - -- Inherits from BASE - local self = BASE:Inherit( self, PROCESS:New( "ASSIGN_ACCEPT", Task, ProcessUnit ) ) -- #PROCESS_ASSIGN_ACCEPT - - self.TaskBriefing = TaskBriefing - - self.Fsm = STATEMACHINE_PROCESS:New( self, { - initial = 'UnAssigned', - events = { - { name = 'Start', from = 'UnAssigned', to = 'Assigned' }, - { name = 'Fail', from = 'UnAssigned', to = 'Failed' }, - }, - callbacks = { - onAssign = self.OnAssign, - }, - endstates = { - 'Assigned', 'Failed' - }, - } ) - - return self - end - - --- StateMachine callback function for a TASK2 - -- @param #PROCESS_ASSIGN_ACCEPT self - -- @param StateMachine#STATEMACHINE_PROCESS Fsm - -- @param #string Event - -- @param #string From - -- @param #string To - function PROCESS_ASSIGN_ACCEPT:OnAssigned( Fsm, Event, From, To ) - self:E( { Event, From, To, self.ProcessUnit.UnitName} ) - - end - -end - - -do -- PROCESS_ASSIGN_MENU_ACCEPT - - --- PROCESS_ASSIGN_MENU_ACCEPT class - -- @type PROCESS_ASSIGN_MENU_ACCEPT - -- @field Task#TASK_BASE Task - -- @field Unit#UNIT ProcessUnit - -- @field Zone#ZONE_BASE TargetZone - -- @extends Task2#TASK2 - PROCESS_ASSIGN_MENU_ACCEPT = { - ClassName = "PROCESS_ASSIGN_MENU_ACCEPT", - } - - - --- Creates a new task assignment state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator. - -- @param #PROCESS_ASSIGN_MENU_ACCEPT self - -- @param Task#TASK Task - -- @param Unit#UNIT Unit - -- @return #PROCESS_ASSIGN_MENU_ACCEPT self - function PROCESS_ASSIGN_MENU_ACCEPT:New( Task, ProcessUnit, TaskBriefing ) - - -- Inherits from BASE - local self = BASE:Inherit( self, PROCESS:New( "ASSIGN_MENU_ACCEPT", Task, ProcessUnit ) ) -- #PROCESS_ASSIGN_MENU_ACCEPT - - self.TaskBriefing = TaskBriefing - - self.Fsm = STATEMACHINE_PROCESS:New( self, { - initial = 'UnAssigned', - events = { - { name = 'Start', from = 'UnAssigned', to = 'AwaitAccept' }, - { name = 'Assign', from = 'AwaitAccept', to = 'Assigned' }, - { name = 'Reject', from = 'AwaitAccept', to = 'Rejected' }, - { name = 'Fail', from = 'AwaitAccept', to = 'Rejected' }, - }, - callbacks = { - onStart = self.OnStart, - onAssign = self.OnAssign, - onReject = self.OnReject, - }, - endstates = { - 'Assigned', 'Rejected' - }, - } ) - - return self - end - - --- StateMachine callback function for a TASK2 - -- @param #PROCESS_ASSIGN_MENU_ACCEPT self - -- @param StateMachine#STATEMACHINE_TASK Fsm - -- @param #string Event - -- @param #string From - -- @param #string To - function PROCESS_ASSIGN_MENU_ACCEPT:OnStart( Fsm, Event, From, To ) - self:E( { Event, From, To, self.ProcessUnit.UnitName} ) - - MESSAGE:New( self.TaskBriefing .. "\nAccess the radio menu to accept the task. You have 30 seconds or the assignment will be cancelled.", 30, "Assignment" ):ToGroup( self.ProcessUnit:GetGroup() ) - self.MenuText = self.Task.TaskName - - local ProcessGroup = self.ProcessUnit:GetGroup() - self.Menu = MENU_GROUP:New( ProcessGroup, "Task " .. self.MenuText .. " acceptance" ) - self.MenuAcceptTask = MENU_GROUP_COMMAND:New( ProcessGroup, "Accept task " .. self.MenuText, self.Menu, self.MenuAssign, self ) - self.MenuRejectTask = MENU_GROUP_COMMAND:New( ProcessGroup, "Reject task " .. self.MenuText, self.Menu, self.MenuReject, self ) - end - - --- Menu function. - -- @param #PROCESS_ASSIGN_MENU_ACCEPT self - function PROCESS_ASSIGN_MENU_ACCEPT:MenuAssign() - self:E( ) - - self:NextEvent( self.Fsm.Assign ) - end - - --- Menu function. - -- @param #PROCESS_ASSIGN_MENU_ACCEPT self - function PROCESS_ASSIGN_MENU_ACCEPT:MenuReject() - self:E( ) - - self:NextEvent( self.Fsm.Reject ) - end - - --- StateMachine callback function for a TASK2 - -- @param #PROCESS_ASSIGN_MENU_ACCEPT self - -- @param StateMachine#STATEMACHINE_PROCESS Fsm - -- @param #string Event - -- @param #string From - -- @param #string To - function PROCESS_ASSIGN_MENU_ACCEPT:OnAssign( Fsm, Event, From, To ) - self:E( { Event, From, To, self.ProcessUnit.UnitName} ) - - self.Menu:Remove() - end - - --- StateMachine callback function for a TASK2 - -- @param #PROCESS_ASSIGN_MENU_ACCEPT self - -- @param StateMachine#STATEMACHINE_PROCESS Fsm - -- @param #string Event - -- @param #string From - -- @param #string To - function PROCESS_ASSIGN_MENU_ACCEPT:OnReject( Fsm, Event, From, To ) - self:E( { Event, From, To, self.ProcessUnit.UnitName} ) - - self.Menu:Remove() - self.Task:UnAssignFromUnit( self.ProcessUnit ) - self.ProcessUnit:Destroy() - end -end ---- @module Task_Route - ---- PROCESS_ROUTE class --- @type PROCESS_ROUTE --- @field Task#TASK TASK --- @field Unit#UNIT ProcessUnit --- @field Zone#ZONE_BASE TargetZone --- @extends Task2#TASK2 -PROCESS_ROUTE = { - ClassName = "PROCESS_ROUTE", -} - - ---- Creates a new routing state machine. The task will route a CLIENT to a ZONE until the CLIENT is within that ZONE. --- @param #PROCESS_ROUTE self --- @param Task#TASK Task --- @param Unit#UNIT Unit --- @return #PROCESS_ROUTE self -function PROCESS_ROUTE:New( Task, ProcessUnit, TargetZone ) - - -- Inherits from BASE - local self = BASE:Inherit( self, PROCESS:New( "ROUTE", Task, ProcessUnit ) ) -- #PROCESS_ROUTE - - self.TargetZone = TargetZone - self.DisplayInterval = 30 - self.DisplayCount = 30 - self.DisplayMessage = true - self.DisplayTime = 10 -- 10 seconds is the default - self.DisplayCategory = "HQ" -- Route is the default display category - - self.Fsm = STATEMACHINE_PROCESS:New( self, { - initial = 'UnArrived', - events = { - { name = 'Start', from = 'UnArrived', to = 'UnArrived' }, - { name = 'Fail', from = 'UnArrived', to = 'Failed' }, - }, - callbacks = { - onleaveUnArrived = self.OnLeaveUnArrived, - onFail = self.OnFail, - }, - endstates = { - 'Arrived', 'Failed' - }, - } ) - - return self -end - ---- Task Events - ---- StateMachine callback function for a TASK2 --- @param #PROCESS_ROUTE self --- @param StateMachine#STATEMACHINE_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To -function PROCESS_ROUTE:OnLeaveUnArrived( Fsm, Event, From, To ) - - if self.ProcessUnit:IsAlive() then - local IsInZone = self.ProcessUnit:IsInZone( self.TargetZone ) - - if self.DisplayCount >= self.DisplayInterval then - if not IsInZone then - local ZoneVec2 = self.TargetZone:GetVec2() - local ZonePointVec2 = POINT_VEC2:New( ZoneVec2.x, ZoneVec2.y ) - local TaskUnitVec2 = self.ProcessUnit:GetVec2() - local TaskUnitPointVec2 = POINT_VEC2:New( TaskUnitVec2.x, TaskUnitVec2.y ) - local RouteText = self.ProcessUnit:GetCallsign() .. ": Route to " .. TaskUnitPointVec2:GetBRText( ZonePointVec2 ) .. " km to target." - MESSAGE:New( RouteText, self.DisplayTime, self.DisplayCategory ):ToGroup( self.ProcessUnit:GetGroup() ) - end - self.DisplayCount = 1 - else - self.DisplayCount = self.DisplayCount + 1 - end - - --if not IsInZone then - self:NextEvent( Fsm.Start ) - --end - - return IsInZone -- if false, then the event will not be executed... - end - - return false - -end - ---- @module Process_Smoke - -do -- PROCESS_SMOKE_TARGETS - - --- PROCESS_SMOKE_TARGETS class - -- @type PROCESS_SMOKE_TARGETS - -- @field Task#TASK_BASE Task - -- @field Unit#UNIT ProcessUnit - -- @field Set#SET_UNIT TargetSetUnit - -- @field Zone#ZONE_BASE TargetZone - -- @extends Task2#TASK2 - PROCESS_SMOKE_TARGETS = { - ClassName = "PROCESS_SMOKE_TARGETS", - } - - - --- Creates a new task assignment state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator. - -- @param #PROCESS_SMOKE_TARGETS self - -- @param Task#TASK Task - -- @param Unit#UNIT Unit - -- @return #PROCESS_SMOKE_TARGETS self - function PROCESS_SMOKE_TARGETS:New( Task, ProcessUnit, TargetSetUnit, TargetZone ) - - -- Inherits from BASE - local self = BASE:Inherit( self, PROCESS:New( "ASSIGN_MENU_ACCEPT", Task, ProcessUnit ) ) -- #PROCESS_SMOKE_TARGETS - - self.TargetSetUnit = TargetSetUnit - self.TargetZone = TargetZone - - self.Fsm = STATEMACHINE_PROCESS:New( self, { - initial = 'None', - events = { - { name = 'Start', from = 'None', to = 'AwaitSmoke' }, - { name = 'Next', from = 'AwaitSmoke', to = 'Smoking' }, - { name = 'Next', from = 'Smoking', to = 'AwaitSmoke' }, - { name = 'Fail', from = 'Smoking', to = 'Failed' }, - { name = 'Fail', from = 'AwaitSmoke', to = 'Failed' }, - { name = 'Fail', from = 'None', to = 'Failed' }, - }, - callbacks = { - onStart = self.OnStart, - onNext = self.OnNext, - onSmoking = self.OnSmoking, - }, - endstates = { - }, - } ) - - return self - end - - --- StateMachine callback function for a TASK2 - -- @param #PROCESS_SMOKE_TARGETS self - -- @param StateMachine#STATEMACHINE_TASK Fsm - -- @param #string Event - -- @param #string From - -- @param #string To - function PROCESS_SMOKE_TARGETS:OnStart( Fsm, Event, From, To ) - self:E( { Event, From, To, self.ProcessUnit.UnitName} ) - - self:E("Set smoke menu") - - local ProcessGroup = self.ProcessUnit:GetGroup() - local MissionMenu = self.Task.Mission:GetMissionMenu( ProcessGroup ) - - local function MenuSmoke( MenuParam ) - self:E( MenuParam ) - local self = MenuParam.self - local SmokeColor = MenuParam.SmokeColor - self.SmokeColor = SmokeColor - self:NextEvent( self.Fsm.Next ) - end - - self.Menu = MENU_GROUP:New( ProcessGroup, "Target acquisition", MissionMenu ) - self.MenuSmokeBlue = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop blue smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Blue } ) - self.MenuSmokeGreen = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop green smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Green } ) - self.MenuSmokeOrange = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop Orange smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Orange } ) - self.MenuSmokeRed = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop Red smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.Red } ) - self.MenuSmokeWhite = MENU_GROUP_COMMAND:New( ProcessGroup, "Drop White smoke on targets", self.Menu, MenuSmoke, { self = self, SmokeColor = SMOKECOLOR.White } ) - end - - --- StateMachine callback function for a TASK2 - -- @param #PROCESS_SMOKE_TARGETS self - -- @param StateMachine#STATEMACHINE_PROCESS Fsm - -- @param #string Event - -- @param #string From - -- @param #string To - function PROCESS_SMOKE_TARGETS:OnSmoking( Fsm, Event, From, To ) - self:E( { Event, From, To, self.ProcessUnit.UnitName} ) - - self.TargetSetUnit:ForEachUnit( - --- @param Unit#UNIT SmokeUnit - function( SmokeUnit ) - if math.random( 1, ( 100 * self.TargetSetUnit:Count() ) / 4 ) <= 100 then - SCHEDULER:New( self, - function() - SmokeUnit:Smoke( self.SmokeColor, 150 ) - end, {}, math.random( 10, 60 ) - ) - end - end - ) - - end - -end--- @module Process_Destroy - ---- PROCESS_DESTROY class --- @type PROCESS_DESTROY --- @field Unit#UNIT ProcessUnit --- @field Set#SET_UNIT TargetSetUnit --- @extends Process#PROCESS -PROCESS_DESTROY = { - ClassName = "PROCESS_DESTROY", - Fsm = {}, - TargetSetUnit = nil, -} - - ---- Creates a new DESTROY process. --- @param #PROCESS_DESTROY self --- @param Task#TASK Task --- @param Unit#UNIT ProcessUnit --- @param Set#SET_UNIT TargetSetUnit --- @return #PROCESS_DESTROY self -function PROCESS_DESTROY:New( Task, ProcessName, ProcessUnit, TargetSetUnit ) - - -- Inherits from BASE - local self = BASE:Inherit( self, PROCESS:New( ProcessName, Task, ProcessUnit ) ) -- #PROCESS_DESTROY - - self.TargetSetUnit = TargetSetUnit - - self.DisplayInterval = 60 - self.DisplayCount = 30 - self.DisplayMessage = true - self.DisplayTime = 10 -- 10 seconds is the default - self.DisplayCategory = "HQ" -- Targets is the default display category - - self.Fsm = STATEMACHINE_PROCESS:New( self, { - initial = 'Assigned', - events = { - { name = 'Start', from = 'Assigned', to = 'Waiting' }, - { name = 'Start', from = 'Waiting', to = 'Waiting' }, - { name = 'HitTarget', from = 'Waiting', to = 'Destroy' }, - { name = 'MoreTargets', from = 'Destroy', to = 'Waiting' }, - { name = 'Destroyed', from = 'Destroy', to = 'Success' }, - { name = 'Fail', from = 'Assigned', to = 'Failed' }, - { name = 'Fail', from = 'Waiting', to = 'Failed' }, - { name = 'Fail', from = 'Destroy', to = 'Failed' }, - }, - callbacks = { - onStart = self.OnStart, - onWaiting = self.OnWaiting, - onHitTarget = self.OnHitTarget, - onMoreTargets = self.OnMoreTargets, - onDestroyed = self.OnDestroyed, - onKilled = self.OnKilled, - }, - endstates = { 'Success', 'Failed' } - } ) - - - _EVENTDISPATCHER:OnDead( self.EventDead, self ) - - return self -end - ---- Process Events - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_DESTROY self --- @param StateMachine#STATEMACHINE_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To -function PROCESS_DESTROY:OnStart( Fsm, Event, From, To ) - - self:NextEvent( Fsm.Start ) -end - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_DESTROY self --- @param StateMachine#STATEMACHINE_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To -function PROCESS_DESTROY:OnWaiting( Fsm, Event, From, To ) - - local TaskGroup = self.ProcessUnit:GetGroup() - if self.DisplayCount >= self.DisplayInterval then - MESSAGE:New( "Your group with assigned " .. self.Task:GetName() .. " task has " .. self.TargetSetUnit:GetUnitTypesText() .. " targets left to be destroyed.", 5, "HQ" ):ToGroup( TaskGroup ) - self.DisplayCount = 1 - else - self.DisplayCount = self.DisplayCount + 1 - end - - return true -- Process always the event. - -end - - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_DESTROY self --- @param StateMachine#STATEMACHINE_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To --- @param Event#EVENTDATA Event -function PROCESS_DESTROY:OnHitTarget( Fsm, Event, From, To, Event ) - - self.TargetSetUnit:Flush() - - if self.TargetSetUnit:FindUnit( Event.IniUnitName ) then - self.TargetSetUnit:RemoveUnitsByName( Event.IniUnitName ) - end - - local TaskGroup = self.ProcessUnit:GetGroup() - MESSAGE:New( "You hit a target. Your group with assigned " .. self.Task:GetName() .. " task has " .. self.TargetSetUnit:GetUnitTypesText() .. " targets left to be destroyed.", 15, "HQ" ):ToGroup( TaskGroup ) - - if self.TargetSetUnit:Count() > 0 then - self:NextEvent( Fsm.MoreTargets ) - else - self:NextEvent( Fsm.Destroyed ) - end -end - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_DESTROY self --- @param StateMachine#STATEMACHINE_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To -function PROCESS_DESTROY:OnMoreTargets( Fsm, Event, From, To ) - - -end - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_DESTROY self --- @param StateMachine#STATEMACHINE_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To --- @param Event#EVENTDATA DCSEvent -function PROCESS_DESTROY:OnKilled( Fsm, Event, From, To ) - - self:NextEvent( Fsm.Restart ) - -end - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_DESTROY self --- @param StateMachine#STATEMACHINE_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To -function PROCESS_DESTROY:OnRestart( Fsm, Event, From, To ) - - self:NextEvent( Fsm.Menu ) - -end - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_DESTROY self --- @param StateMachine#STATEMACHINE_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To -function PROCESS_DESTROY:OnDestroyed( Fsm, Event, From, To ) - -end - ---- DCS Events - ---- @param #PROCESS_DESTROY self --- @param Event#EVENTDATA Event -function PROCESS_DESTROY:EventDead( Event ) - - if Event.IniDCSUnit then - self.TargetSetUnit:Remove( Event.IniDCSUnitName ) - self:NextEvent( self.Fsm.HitTarget, Event ) - end -end - - ---- @module Process_JTAC - ---- PROCESS_JTAC class --- @type PROCESS_JTAC --- @field Unit#UNIT ProcessUnit --- @field Set#SET_UNIT TargetSetUnit --- @extends Process#PROCESS -PROCESS_JTAC = { - ClassName = "PROCESS_JTAC", - Fsm = {}, - TargetSetUnit = nil, -} - - ---- Creates a new DESTROY process. --- @param #PROCESS_JTAC self --- @param Task#TASK Task --- @param Unit#UNIT ProcessUnit --- @param Set#SET_UNIT TargetSetUnit --- @param Unit#UNIT FACUnit --- @return #PROCESS_JTAC self -function PROCESS_JTAC:New( Task, ProcessUnit, TargetSetUnit, FACUnit ) - - -- Inherits from BASE - local self = BASE:Inherit( self, PROCESS:New( "JTAC", Task, ProcessUnit ) ) -- #PROCESS_JTAC - - self.TargetSetUnit = TargetSetUnit - self.FACUnit = FACUnit - - self.DisplayInterval = 60 - self.DisplayCount = 30 - self.DisplayMessage = true - self.DisplayTime = 10 -- 10 seconds is the default - self.DisplayCategory = "HQ" -- Targets is the default display category - - - self.Fsm = STATEMACHINE_PROCESS:New( self, { - initial = 'Assigned', - events = { - { name = 'Start', from = 'Assigned', to = 'CreatedMenu' }, - { name = 'JTACMenuUpdate', from = 'CreatedMenu', to = 'AwaitingMenu' }, - { name = 'JTACMenuAwait', from = 'AwaitingMenu', to = 'AwaitingMenu' }, - { name = 'JTACMenuSpot', from = 'AwaitingMenu', to = 'AwaitingMenu' }, - { name = 'JTACMenuCancel', from = 'AwaitingMenu', to = 'AwaitingMenu' }, - { name = 'JTACStatus', from = 'AwaitingMenu', to = 'AwaitingMenu' }, - { name = 'Fail', from = 'AwaitingMenu', to = 'Failed' }, - { name = 'Fail', from = 'CreatedMenu', to = 'Failed' }, - }, - callbacks = { - onStart = self.OnStart, - onJTACMenuUpdate = self.OnJTACMenuUpdate, - onJTACMenuAwait = self.OnJTACMenuAwait, - onJTACMenuSpot = self.OnJTACMenuSpot, - onJTACMenuCancel = self.OnJTACMenuCancel, - }, - endstates = { 'Failed' } - } ) - - - _EVENTDISPATCHER:OnDead( self.EventDead, self ) - - return self -end - ---- Process Events - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_JTAC self --- @param StateMachine#STATEMACHINE_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To -function PROCESS_JTAC:OnStart( Fsm, Event, From, To ) - - self:NextEvent( Fsm.JTACMenuUpdate ) -end - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_JTAC self --- @param StateMachine#STATEMACHINE_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To -function PROCESS_JTAC:OnJTACMenuUpdate( Fsm, Event, From, To ) - - local function JTACMenuSpot( MenuParam ) - self:E( MenuParam.TargetUnit.UnitName ) - local self = MenuParam.self - local TargetUnit = MenuParam.TargetUnit - - self:NextEvent( self.Fsm.JTACMenuSpot, TargetUnit ) - end - - local function JTACMenuCancel( MenuParam ) - self:E( MenuParam ) - local self = MenuParam.self - local TargetUnit = MenuParam.TargetUnit - - self:NextEvent( self.Fsm.JTACMenuCancel, TargetUnit ) - end - - - -- Loop each unit in the target set, and determine the threat levels map table. - local UnitThreatLevels = self.TargetSetUnit:GetUnitThreatLevels() - - self:E( {"UnitThreadLevels", UnitThreatLevels } ) - - local JTACMenu = self.ProcessGroup:GetState( self.ProcessGroup, "JTACMenu" ) - - if not JTACMenu then - JTACMenu = MENU_GROUP:New( self.ProcessGroup, "JTAC", self.MissionMenu ) - for ThreatLevel, ThreatLevelTable in pairs( UnitThreatLevels ) do - local JTACMenuThreatLevel = MENU_GROUP:New( self.ProcessGroup, ThreatLevelTable.UnitThreatLevelText, JTACMenu ) - for ThreatUnitName, ThreatUnit in pairs( ThreatLevelTable.Units ) do - local JTACMenuUnit = MENU_GROUP:New( self.ProcessGroup, ThreatUnit:GetTypeName(), JTACMenuThreatLevel ) - MENU_GROUP_COMMAND:New( self.ProcessGroup, "Lase Target", JTACMenuUnit, JTACMenuSpot, { self = self, TargetUnit = ThreatUnit } ) - MENU_GROUP_COMMAND:New( self.ProcessGroup, "Cancel Target", JTACMenuUnit, JTACMenuCancel, { self = self, TargetUnit = ThreatUnit } ) - end - end - end - -end - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_JTAC self --- @param StateMachine#STATEMACHINE_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To -function PROCESS_JTAC:OnJTACMenuAwait( Fsm, Event, From, To ) - - if self.DisplayCount >= self.DisplayInterval then - - local TaskJTAC = self.Task -- Task#TASK_JTAC - TaskJTAC.Spots = TaskJTAC.Spots or {} - for TargetUnitName, SpotData in pairs( TaskJTAC.Spots) do - local TargetUnit = UNIT:FindByName( TargetUnitName ) - self.FACUnit:MessageToGroup( "Lasing " .. TargetUnit:GetTypeName() .. " with laser code " .. SpotData:getCode(), 15, self.ProcessGroup ) - end - self.DisplayCount = 1 - else - self.DisplayCount = self.DisplayCount + 1 - end - - self:NextEvent( Fsm.JTACMenuAwait ) -end - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_JTAC self --- @param StateMachine#STATEMACHINE_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To --- @param Unit#UNIT TargetUnit -function PROCESS_JTAC:OnJTACMenuSpot( Fsm, Event, From, To, TargetUnit ) - - local TargetUnitName = TargetUnit:GetName() - - local TaskJTAC = self.Task -- Task#TASK_JTAC - - TaskJTAC.Spots = TaskJTAC.Spots or {} - TaskJTAC.Spots[TargetUnitName] = TaskJTAC.Spots[TargetUnitName] or {} - - local DCSFACObject = self.FACUnit:GetDCSObject() - local TargetVec3 = TargetUnit:GetPointVec3() - - TaskJTAC.Spots[TargetUnitName] = Spot.createInfraRed( self.FACUnit:GetDCSObject(), { x = 0, y = 1, z = 0 }, TargetUnit:GetPointVec3(), math.random( 1000, 9999 ) ) - - local SpotData = TaskJTAC.Spots[TargetUnitName] - self.FACUnit:MessageToGroup( "Lasing " .. TargetUnit:GetTypeName() .. " with laser code " .. SpotData:getCode(), 15, self.ProcessGroup ) - - self:NextEvent( Fsm.JTACMenuAwait ) -end - ---- StateMachine callback function for a PROCESS --- @param #PROCESS_JTAC self --- @param StateMachine#STATEMACHINE_PROCESS Fsm --- @param #string Event --- @param #string From --- @param #string To --- @param Unit#UNIT TargetUnit -function PROCESS_JTAC:OnJTACMenuCancel( Fsm, Event, From, To, TargetUnit ) - - local TargetUnitName = TargetUnit:GetName() - - local TaskJTAC = self.Task -- Task#TASK_JTAC - - TaskJTAC.Spots = TaskJTAC.Spots or {} - if TaskJTAC.Spots[TargetUnitName] then - TaskJTAC.Spots[TargetUnitName]:destroy() -- destroys the spot - TaskJTAC.Spots[TargetUnitName] = nil - end - - self.FACUnit:MessageToGroup( "Stopped lasing " .. TargetUnit:GetTypeName(), 15, self.ProcessGroup ) - - self:NextEvent( Fsm.JTACMenuAwait ) -end - - ---- This module contains the TASK_BASE class. --- --- 1) @{#TASK_BASE} class, extends @{Base#BASE} --- ============================================ --- 1.1) The @{#TASK_BASE} class implements the methods for task orchestration within MOOSE. --- ---------------------------------------------------------------------------------------- --- The class provides a couple of methods to: --- --- * @{#TASK_BASE.AssignToGroup}():Assign a task to a group (of players). --- * @{#TASK_BASE.AddProcess}():Add a @{Process} to a task. --- * @{#TASK_BASE.RemoveProcesses}():Remove a running @{Process} from a running task. --- * @{#TASK_BASE.AddStateMachine}():Add a @{StateMachine} to a task. --- * @{#TASK_BASE.RemoveStateMachines}():Remove @{StateMachine}s from a task. --- * @{#TASK_BASE.HasStateMachine}():Enquire if the task has a @{StateMachine} --- * @{#TASK_BASE.AssignToUnit}(): Assign a task to a unit. (Needs to be implemented in the derived classes from @{#TASK_BASE}. --- * @{#TASK_BASE.UnAssignFromUnit}(): Unassign the task from a unit. --- --- 1.2) Set and enquire task status (beyond the task state machine processing). --- ---------------------------------------------------------------------------- --- A task needs to implement as a minimum the following task states: --- --- * **Success**: Expresses the successful execution and finalization of the task. --- * **Failed**: Expresses the failure of a task. --- * **Planned**: Expresses that the task is created, but not yet in execution and is not assigned yet. --- * **Assigned**: Expresses that the task is assigned to a Group of players, and that the task is in execution mode. --- --- A task may also implement the following task states: --- --- * **Rejected**: Expresses that the task is rejected by a player, who was requested to accept the task. --- * **Cancelled**: Expresses that the task is cancelled by HQ or through a logical situation where a cancellation of the task is required. --- --- A task can implement more statusses than the ones outlined above. Please consult the documentation of the specific tasks to understand the different status modelled. --- --- The status of tasks can be set by the methods **State** followed by the task status. An example is `StateAssigned()`. --- The status of tasks can be enquired by the methods **IsState** followed by the task status name. An example is `if IsStateAssigned() then`. --- --- 1.3) Add scoring when reaching a certain task status: --- ----------------------------------------------------- --- Upon reaching a certain task status in a task, additional scoring can be given. If the Mission has a scoring system attached, the scores will be added to the mission scoring. --- Use the method @{#TASK_BASE.AddScore}() to add scores when a status is reached. --- --- 1.4) Task briefing: --- ------------------- --- A task briefing can be given that is shown to the player when he is assigned to the task. --- --- === --- --- ### Authors: FlightControl - Design and Programming --- --- @module Task - ---- The TASK_BASE class --- @type TASK_BASE --- @field Scheduler#SCHEDULER TaskScheduler --- @field Mission#MISSION Mission --- @field StateMachine#STATEMACHINE Fsm --- @field Set#SET_GROUP SetGroup The Set of Groups assigned to the Task --- @extends Base#BASE -TASK_BASE = { - ClassName = "TASK_BASE", - TaskScheduler = nil, - Processes = {}, - Players = nil, - Scores = {}, - Menu = {}, - SetGroup = nil, -} - - ---- Instantiates a new TASK_BASE. Should never be used. Interface Class. --- @param #TASK_BASE self --- @param Mission#MISSION The mission wherein the Task is registered. --- @param Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. --- @param #string TaskName The name of the Task --- @param #string TaskType The type of the Task --- @param #string TaskCategory The category of the Task (A2G, A2A, Transport, ... ) --- @return #TASK_BASE self -function TASK_BASE:New( Mission, SetGroup, TaskName, TaskType, TaskCategory ) - - local self = BASE:Inherit( self, BASE:New() ) - self:E( "New TASK " .. TaskName ) - - self.Processes = {} - self.Fsm = {} - - self.Mission = Mission - self.SetGroup = SetGroup - - self:SetCategory( TaskCategory ) - self:SetType( TaskType ) - self:SetName( TaskName ) - self:SetID( Mission:GetNextTaskID( self ) ) -- The Mission orchestrates the task sequences .. - - self.TaskBriefing = "You are assigned to the task: " .. self.TaskName .. "." - - return self -end - ---- Cleans all references of a TASK_BASE. --- @param #TASK_BASE self --- @return #nil -function TASK_BASE:CleanUp() - - _EVENTDISPATCHER:OnPlayerLeaveRemove( self ) - _EVENTDISPATCHER:OnDeadRemove( self ) - _EVENTDISPATCHER:OnCrashRemove( self ) - _EVENTDISPATCHER:OnPilotDeadRemove( self ) - - return nil -end - - ---- Assign the @{Task}to a @{Group}. --- @param #TASK_BASE self --- @param Group#GROUP TaskGroup -function TASK_BASE:AssignToGroup( TaskGroup ) - self:F2( TaskGroup:GetName() ) - - local TaskGroupName = TaskGroup:GetName() - - TaskGroup:SetState( TaskGroup, "Assigned", self ) - - self:RemoveMenuForGroup( TaskGroup ) - self:SetAssignedMenuForGroup( TaskGroup ) - - local TaskUnits = TaskGroup:GetUnits() - for UnitID, UnitData in pairs( TaskUnits ) do - local TaskUnit = UnitData -- Unit#UNIT - local PlayerName = TaskUnit:GetPlayerName() - if PlayerName ~= nil or PlayerName ~= "" then - self:AssignToUnit( TaskUnit ) - end - end -end - ---- Send the briefng message of the @{Task} to the assigned @{Group}s. --- @param #TASK_BASE self -function TASK_BASE:SendBriefingToAssignedGroups() - self:F2() - - for TaskGroupName, TaskGroup in pairs( self.SetGroup:GetSet() ) do - - if self:IsAssignedToGroup( TaskGroup ) then - TaskGroup:Message( self.TaskBriefing, 60 ) - end - end -end - - ---- Assign the @{Task} from the @{Group}s. --- @param #TASK_BASE self -function TASK_BASE:UnAssignFromGroups() - self:F2() - - for TaskGroupName, TaskGroup in pairs( self.SetGroup:GetSet() ) do - - TaskGroup:SetState( TaskGroup, "Assigned", nil ) - local TaskUnits = TaskGroup:GetUnits() - for UnitID, UnitData in pairs( TaskUnits ) do - local TaskUnit = UnitData -- Unit#UNIT - local PlayerName = TaskUnit:GetPlayerName() - if PlayerName ~= nil or PlayerName ~= "" then - self:UnAssignFromUnit( TaskUnit ) - end - end - end -end - ---- Returns if the @{Task} is assigned to the Group. --- @param #TASK_BASE self --- @param Group#GROUP TaskGroup --- @return #boolean -function TASK_BASE:IsAssignedToGroup( TaskGroup ) - - local TaskGroupName = TaskGroup:GetName() - - if self:IsStateAssigned() then - if TaskGroup:GetState( TaskGroup, "Assigned" ) == self then - return true - end - end - - return false -end - ---- Assign the @{Task}to an alive @{Unit}. --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @return #TASK_BASE self -function TASK_BASE:AssignToUnit( TaskUnit ) - self:F( TaskUnit:GetName() ) - - return nil -end - ---- UnAssign the @{Task} from an alive @{Unit}. --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @return #TASK_BASE self -function TASK_BASE:UnAssignFromUnit( TaskUnitName ) - self:F( TaskUnitName ) - - if self:HasStateMachine( TaskUnitName ) == true then - self:RemoveStateMachines( TaskUnitName ) - self:RemoveProcesses( TaskUnitName ) - end - - return self -end - ---- Set the menu options of the @{Task} to all the groups in the SetGroup. --- @param #TASK_BASE self --- @return #TASK_BASE self -function TASK_BASE:SetPlannedMenu() - - local MenuText = self:GetPlannedMenuText() - for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - if not self:IsAssignedToGroup( TaskGroup ) then - self:SetPlannedMenuForGroup( TaskGroup, MenuText ) - end - end -end - ---- Set the menu options of the @{Task} to all the groups in the SetGroup. --- @param #TASK_BASE self --- @return #TASK_BASE self -function TASK_BASE:SetAssignedMenu() - - for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - if self:IsAssignedToGroup( TaskGroup ) then - self:SetAssignedMenuForGroup( TaskGroup ) - end - end -end - ---- Remove the menu options of the @{Task} to all the groups in the SetGroup. --- @param #TASK_BASE self --- @return #TASK_BASE self -function TASK_BASE:RemoveMenu() - - for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - self:RemoveMenuForGroup( TaskGroup ) - end -end - ---- Set the planned menu option of the @{Task}. --- @param #TASK_BASE self --- @param Group#GROUP TaskGroup --- @param #string MenuText The menu text. --- @return #TASK_BASE self -function TASK_BASE:SetPlannedMenuForGroup( TaskGroup, MenuText ) - self:E( TaskGroup:GetName() ) - - local TaskMission = self.Mission:GetName() - local TaskCategory = self:GetCategory() - local TaskType = self:GetType() - - local Mission = self.Mission - - Mission.MenuMission = Mission.MenuMission or {} - local MenuMission = Mission.MenuMission - - Mission.MenuCategory = Mission.MenuCategory or {} - local MenuCategory = Mission.MenuCategory - - Mission.MenuType = Mission.MenuType or {} - local MenuType = Mission.MenuType - - self.Menu = self.Menu or {} - local Menu = self.Menu - - local TaskGroupName = TaskGroup:GetName() - MenuMission[TaskGroupName] = MenuMission[TaskGroupName] or MENU_GROUP:New( TaskGroup, TaskMission, nil ) - - MenuCategory[TaskGroupName] = MenuCategory[TaskGroupName] or {} - MenuCategory[TaskGroupName][TaskCategory] = MenuCategory[TaskGroupName][TaskCategory] or MENU_GROUP:New( TaskGroup, TaskCategory, MenuMission[TaskGroupName] ) - - MenuType[TaskGroupName] = MenuType[TaskGroupName] or {} - MenuType[TaskGroupName][TaskType] = MenuType[TaskGroupName][TaskType] or MENU_GROUP:New( TaskGroup, TaskType, MenuCategory[TaskGroupName][TaskCategory] ) - - if Menu[TaskGroupName] then - Menu[TaskGroupName]:Remove() - end - Menu[TaskGroupName] = MENU_GROUP_COMMAND:New( TaskGroup, MenuText, MenuType[TaskGroupName][TaskType], self.MenuAssignToGroup, { self = self, TaskGroup = TaskGroup } ) - - return self -end - ---- Set the assigned menu options of the @{Task}. --- @param #TASK_BASE self --- @param Group#GROUP TaskGroup --- @return #TASK_BASE self -function TASK_BASE:SetAssignedMenuForGroup( TaskGroup ) - self:E( TaskGroup:GetName() ) - - local TaskMission = self.Mission:GetName() - - local Mission = self.Mission - - Mission.MenuMission = Mission.MenuMission or {} - local MenuMission = Mission.MenuMission - - self.MenuStatus = self.MenuStatus or {} - local MenuStatus = self.MenuStatus - - - self.MenuAbort = self.MenuAbort or {} - local MenuAbort = self.MenuAbort - - local TaskGroupName = TaskGroup:GetName() - MenuMission[TaskGroupName] = MenuMission[TaskGroupName] or MENU_GROUP:New( TaskGroup, TaskMission, nil ) - MenuStatus[TaskGroupName] = MENU_GROUP_COMMAND:New( TaskGroup, "Task Status", MenuMission[TaskGroupName], self.MenuTaskStatus, { self = self, TaskGroup = TaskGroup } ) - MenuAbort[TaskGroupName] = MENU_GROUP_COMMAND:New( TaskGroup, "Abort Task", MenuMission[TaskGroupName], self.MenuTaskAbort, { self = self, TaskGroup = TaskGroup } ) - - return self -end - ---- Remove the menu option of the @{Task} for a @{Group}. --- @param #TASK_BASE self --- @param Group#GROUP TaskGroup --- @return #TASK_BASE self -function TASK_BASE:RemoveMenuForGroup( TaskGroup ) - - local TaskGroupName = TaskGroup:GetName() - - local Mission = self.Mission - local MenuMission = Mission.MenuMission - local MenuCategory = Mission.MenuCategory - local MenuType = Mission.MenuType - local MenuStatus = self.MenuStatus - local MenuAbort = self.MenuAbort - local Menu = self.Menu - - Menu = Menu or {} - if Menu[TaskGroupName] then - Menu[TaskGroupName]:Remove() - Menu[TaskGroupName] = nil - end - - MenuType = MenuType or {} - if MenuType[TaskGroupName] then - for _, Menu in pairs( MenuType[TaskGroupName] ) do - Menu:Remove() - end - MenuType[TaskGroupName] = nil - end - - MenuCategory = MenuCategory or {} - if MenuCategory[TaskGroupName] then - for _, Menu in pairs( MenuCategory[TaskGroupName] ) do - Menu:Remove() - end - MenuCategory[TaskGroupName] = nil - end - - MenuStatus = MenuStatus or {} - if MenuStatus[TaskGroupName] then - MenuStatus[TaskGroupName]:Remove() - MenuStatus[TaskGroupName] = nil - end - - MenuAbort = MenuAbort or {} - if MenuAbort[TaskGroupName] then - MenuAbort[TaskGroupName]:Remove() - MenuAbort[TaskGroupName] = nil - end - -end - -function TASK_BASE.MenuAssignToGroup( MenuParam ) - - local self = MenuParam.self - local TaskGroup = MenuParam.TaskGroup - - self:AssignToGroup( TaskGroup ) -end - -function TASK_BASE.MenuTaskStatus( MenuParam ) - - local self = MenuParam.self - local TaskGroup = MenuParam.TaskGroup - - --self:AssignToGroup( TaskGroup ) -end - -function TASK_BASE.MenuTaskAbort( MenuParam ) - - local self = MenuParam.self - local TaskGroup = MenuParam.TaskGroup - - --self:AssignToGroup( TaskGroup ) -end - - - ---- Returns the @{Task} name. --- @param #TASK_BASE self --- @return #string TaskName -function TASK_BASE:GetTaskName() - return self.TaskName -end - - ---- Add Process to @{Task} with key @{Unit}. --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @return #TASK_BASE self -function TASK_BASE:AddProcess( TaskUnit, Process ) - local TaskUnitName = TaskUnit:GetName() - self.Processes = self.Processes or {} - self.Processes[TaskUnitName] = self.Processes[TaskUnitName] or {} - self.Processes[TaskUnitName][#self.Processes[TaskUnitName]+1] = Process - return Process -end - - ---- Remove Processes from @{Task} with key @{Unit} --- @param #TASK_BASE self --- @param #string TaskUnitName --- @return #TASK_BASE self -function TASK_BASE:RemoveProcesses( TaskUnitName ) - - for ProcessID, ProcessData in pairs( self.Processes[TaskUnitName] ) do - local Process = ProcessData -- Process#PROCESS - Process:StopEvents() - Process = nil - self.Processes[TaskUnitName][ProcessID] = nil - self:E( self.Processes[TaskUnitName][ProcessID] ) - end - self.Processes[TaskUnitName] = nil -end - ---- Fail processes from @{Task} with key @{Unit} --- @param #TASK_BASE self --- @param #string TaskUnitName --- @return #TASK_BASE self -function TASK_BASE:FailProcesses( TaskUnitName ) - - for ProcessID, ProcessData in pairs( self.Processes[TaskUnitName] ) do - local Process = ProcessData -- Process#PROCESS - self:E( { "Failing process: ", Process } ) - Process.Fsm:Fail() - end -end - ---- Add a FiniteStateMachine to @{Task} with key @{Unit} --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @return #TASK_BASE self -function TASK_BASE:AddStateMachine( TaskUnit, Fsm ) - local TaskUnitName = TaskUnit:GetName() - self.Fsm[TaskUnitName] = self.Fsm[TaskUnitName] or {} - self.Fsm[TaskUnitName][#self.Fsm[TaskUnitName]+1] = Fsm - return Fsm -end - ---- Remove FiniteStateMachines from @{Task} with key @{Unit} --- @param #TASK_BASE self --- @param #string TaskUnitName --- @return #TASK_BASE self -function TASK_BASE:RemoveStateMachines( TaskUnitName ) - - for _, Fsm in pairs( self.Fsm[TaskUnitName] ) do - Fsm = nil - self.Fsm[TaskUnitName][_] = nil - self:E( self.Fsm[TaskUnitName][_] ) - end - self.Fsm[TaskUnitName] = nil -end - ---- Checks if there is a FiniteStateMachine assigned to @{Unit} for @{Task} --- @param #TASK_BASE self --- @param #string TaskUnitName --- @return #TASK_BASE self -function TASK_BASE:HasStateMachine( TaskUnitName ) - - self:F( { TaskUnitName, self.Fsm[TaskUnitName] ~= nil } ) - return ( self.Fsm[TaskUnitName] ~= nil ) -end - - - - - ---- Register a potential new assignment for a new spawned @{Unit}. --- Tasks only get assigned if there are players in it. --- @param #TASK_BASE self --- @param Event#EVENTDATA Event --- @return #TASK_BASE self -function TASK_BASE:_EventAssignUnit( Event ) - if Event.IniUnit then - self:F( Event ) - local TaskUnit = Event.IniUnit - if TaskUnit:IsAlive() then - local TaskPlayerName = TaskUnit:GetPlayerName() - if TaskPlayerName ~= nil then - if not self:HasStateMachine( TaskUnit ) then - -- Check if the task was assigned to the group, if it was assigned to the group, assign to the unit just spawned and initiate the processes. - local TaskGroup = TaskUnit:GetGroup() - if self:IsAssignedToGroup( TaskGroup ) then - self:AssignToUnit( TaskUnit ) - end - end - end - end - end - return nil -end - ---- Catches the "player leave unit" event for a @{Unit} .... --- When a player is an air unit, and leaves the unit: --- --- * and he is not at an airbase runway on the ground, he will fail its task. --- * and he is on an airbase and on the ground, the process for him will just continue to work, he can switch airplanes, and take-off again. --- This is important to model the change from plane types for a player during mission assignment. --- @param #TASK_BASE self --- @param Event#EVENTDATA Event --- @return #TASK_BASE self -function TASK_BASE:_EventPlayerLeaveUnit( Event ) - self:F( Event ) - if Event.IniUnit then - local TaskUnit = Event.IniUnit - local TaskUnitName = Event.IniUnitName - - -- Check if for this unit in the task there is a process ongoing. - if self:HasStateMachine( TaskUnitName ) then - if TaskUnit:IsAir() then - if TaskUnit:IsAboveRunway() then - -- do nothing - else - self:E( "IsNotAboveRunway" ) - -- Player left airplane during an assigned task and was not at an airbase. - self:FailProcesses( TaskUnitName ) - self:UnAssignFromUnit( TaskUnitName ) - end - end - end - - end - return nil -end - ---- UnAssigns a @{Unit} that is left by a player, crashed, dead, .... --- There are only assignments if there are players in it. --- @param #TASK_BASE self --- @param Event#EVENTDATA Event --- @return #TASK_BASE self -function TASK_BASE:_EventDead( Event ) - self:F( Event ) - if Event.IniUnit then - local TaskUnit = Event.IniUnit - local TaskUnitName = Event.IniUnitName - - -- Check if for this unit in the task there is a process ongoing. - if self:HasStateMachine( TaskUnitName ) then - self:FailProcesses( TaskUnitName ) - self:UnAssignFromUnit( TaskUnitName ) - end - - local TaskGroup = Event.IniUnit:GetGroup() - TaskGroup:SetState( TaskGroup, "Assigned", nil ) - end - return nil -end - ---- Gets the Scoring of the task --- @param #TASK_BASE self --- @return Scoring#SCORING Scoring -function TASK_BASE:GetScoring() - return self.Mission:GetScoring() -end - - ---- Gets the Task Index, which is a combination of the Task category, the Task type, the Task name. --- @param #TASK_BASE self --- @return #string The Task ID -function TASK_BASE:GetTaskIndex() - - local TaskCategory = self:GetCategory() - local TaskType = self:GetType() - local TaskName = self:GetName() - - return TaskCategory .. "." ..TaskType .. "." .. TaskName -end - ---- Sets the Name of the Task --- @param #TASK_BASE self --- @param #string TaskName -function TASK_BASE:SetName( TaskName ) - self.TaskName = TaskName -end - ---- Gets the Name of the Task --- @param #TASK_BASE self --- @return #string The Task Name -function TASK_BASE:GetName() - return self.TaskName -end - ---- Sets the Type of the Task --- @param #TASK_BASE self --- @param #string TaskType -function TASK_BASE:SetType( TaskType ) - self.TaskType = TaskType -end - ---- Gets the Type of the Task --- @param #TASK_BASE self --- @return #string TaskType -function TASK_BASE:GetType() - return self.TaskType -end - ---- Sets the Category of the Task --- @param #TASK_BASE self --- @param #string TaskCategory -function TASK_BASE:SetCategory( TaskCategory ) - self.TaskCategory = TaskCategory -end - ---- Gets the Category of the Task --- @param #TASK_BASE self --- @return #string TaskCategory -function TASK_BASE:GetCategory() - return self.TaskCategory -end - ---- Sets the ID of the Task --- @param #TASK_BASE self --- @param #string TaskID -function TASK_BASE:SetID( TaskID ) - self.TaskID = TaskID -end - ---- Gets the ID of the Task --- @param #TASK_BASE self --- @return #string TaskID -function TASK_BASE:GetID() - return self.TaskID -end - - ---- Sets a @{Task} to status **Success**. --- @param #TASK_BASE self -function TASK_BASE:StateSuccess() - self:SetState( self, "State", "Success" ) - return self -end - ---- Is the @{Task} status **Success**. --- @param #TASK_BASE self -function TASK_BASE:IsStateSuccess() - return self:GetStateString() == "Success" -end - ---- Sets a @{Task} to status **Failed**. --- @param #TASK_BASE self -function TASK_BASE:StateFailed() - self:SetState( self, "State", "Failed" ) - return self -end - ---- Is the @{Task} status **Failed**. --- @param #TASK_BASE self -function TASK_BASE:IsStateFailed() - return self:GetStateString() == "Failed" -end - ---- Sets a @{Task} to status **Planned**. --- @param #TASK_BASE self -function TASK_BASE:StatePlanned() - self:SetState( self, "State", "Planned" ) - return self -end - ---- Is the @{Task} status **Planned**. --- @param #TASK_BASE self -function TASK_BASE:IsStatePlanned() - return self:GetStateString() == "Planned" -end - ---- Sets a @{Task} to status **Assigned**. --- @param #TASK_BASE self -function TASK_BASE:StateAssigned() - self:SetState( self, "State", "Assigned" ) - return self -end - ---- Is the @{Task} status **Assigned**. --- @param #TASK_BASE self -function TASK_BASE:IsStateAssigned() - return self:GetStateString() == "Assigned" -end - ---- Sets a @{Task} to status **Hold**. --- @param #TASK_BASE self -function TASK_BASE:StateHold() - self:SetState( self, "State", "Hold" ) - return self -end - ---- Is the @{Task} status **Hold**. --- @param #TASK_BASE self -function TASK_BASE:IsStateHold() - return self:GetStateString() == "Hold" -end - ---- Sets a @{Task} to status **Replanned**. --- @param #TASK_BASE self -function TASK_BASE:StateReplanned() - self:SetState( self, "State", "Replanned" ) - return self -end - ---- Is the @{Task} status **Replanned**. --- @param #TASK_BASE self -function TASK_BASE:IsStateReplanned() - return self:GetStateString() == "Replanned" -end - ---- Gets the @{Task} status. --- @param #TASK_BASE self -function TASK_BASE:GetStateString() - return self:GetState( self, "State" ) -end - ---- Sets a @{Task} briefing. --- @param #TASK_BASE self --- @param #string TaskBriefing --- @return #TASK_BASE self -function TASK_BASE:SetBriefing( TaskBriefing ) - self.TaskBriefing = TaskBriefing - return self -end - - - ---- Adds a score for the TASK to be achieved. --- @param #TASK_BASE self --- @param #string TaskStatus is the status of the TASK when the score needs to be given. --- @param #string ScoreText is a text describing the score that is given according the status. --- @param #number Score is a number providing the score of the status. --- @return #TASK_BASE self -function TASK_BASE:AddScore( TaskStatus, ScoreText, Score ) - self:F2( { TaskStatus, ScoreText, Score } ) - - self.Scores[TaskStatus] = self.Scores[TaskStatus] or {} - self.Scores[TaskStatus].ScoreText = ScoreText - self.Scores[TaskStatus].Score = Score - return self -end - ---- StateMachine callback function for a TASK --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @param StateMachine#STATEMACHINE_TASK Fsm --- @param #string Event --- @param #string From --- @param #string To --- @param Event#EVENTDATA Event -function TASK_BASE:OnAssigned( TaskUnit, Fsm, Event, From, To ) - - self:E("Assigned") - - local TaskGroup = TaskUnit:GetGroup() - - TaskGroup:Message( self.TaskBriefing, 20 ) - - self:RemoveMenuForGroup( TaskGroup ) - self:SetAssignedMenuForGroup( TaskGroup ) - -end - - ---- StateMachine callback function for a TASK --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @param StateMachine#STATEMACHINE_TASK Fsm --- @param #string Event --- @param #string From --- @param #string To --- @param Event#EVENTDATA Event -function TASK_BASE:OnSuccess( TaskUnit, Fsm, Event, From, To ) - - self:E("Success") - - self:UnAssignFromGroups() - - local TaskGroup = TaskUnit:GetGroup() - self.Mission:SetPlannedMenu() - - self:StateSuccess() - - -- The task has become successful, the event catchers can be cleaned. - self:CleanUp() - -end - ---- StateMachine callback function for a TASK --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @param StateMachine#STATEMACHINE_TASK Fsm --- @param #string Event --- @param #string From --- @param #string To --- @param Event#EVENTDATA Event -function TASK_BASE:OnFailed( TaskUnit, Fsm, Event, From, To ) - - self:E( { "Failed for unit ", TaskUnit:GetName(), TaskUnit:GetPlayerName() } ) - - -- A task cannot be "failed", so a task will always be there waiting for players to join. - -- When the player leaves its unit, we will need to check whether he was on the ground or not at an airbase. - -- When the player crashes, we will need to check whether in the group there are other players still active. It not, we reset the task from Assigned to Planned, otherwise, we just leave as Assigned. - - self:UnAssignFromGroups() - self:StatePlanned() - -end - ---- StateMachine callback function for a TASK --- @param #TASK_BASE self --- @param Unit#UNIT TaskUnit --- @param StateMachine#STATEMACHINE_TASK Fsm --- @param #string Event --- @param #string From --- @param #string To --- @param Event#EVENTDATA Event -function TASK_BASE:OnStateChange( TaskUnit, Fsm, Event, From, To ) - - if self:IsTrace() then - MESSAGE:New( "Task " .. self.TaskName .. " : " .. Event .. " changed to state " .. To, 15 ):ToAll() - end - - self:E( { Event, From, To } ) - self:SetState( self, "State", To ) - - if self.Scores[To] then - local Scoring = self:GetScoring() - if Scoring then - Scoring:_AddMissionScore( self.Mission, self.Scores[To].ScoreText, self.Scores[To].Score ) - end - end - -end - - ---- @param #TASK_BASE self -function TASK_BASE:_Schedule() - self:F2() - - self.TaskScheduler = SCHEDULER:New( self, _Scheduler, {}, 15, 15 ) - return self -end - - ---- @param #TASK_BASE self -function TASK_BASE._Scheduler() - self:F2() - - return true -end - - - - ---- This module contains the TASK_SEAD classes. --- --- 1) @{#TASK_SEAD} class, extends @{Task#TASK_BASE} --- ================================================= --- The @{#TASK_SEAD} class defines a new SEAD task of a @{Set} of Target Units, located at a Target Zone, based on the tasking capabilities defined in @{Task#TASK_BASE}. --- The TASK_SEAD is processed through a @{Statemachine#STATEMACHINE_TASK}, and has the following statuses: --- --- * **None**: Start of the process --- * **Planned**: The SEAD task is planned. Upon Planned, the sub-process @{Process_Assign#PROCESS_ASSIGN_ACCEPT} is started to accept the task. --- * **Assigned**: The SEAD task is assigned to a @{Group#GROUP}. Upon Assigned, the sub-process @{Process_Route#PROCESS_ROUTE} is started to route the active Units in the Group to the attack zone. --- * **Success**: The SEAD task is successfully completed. Upon Success, the sub-process @{Process_SEAD#PROCESS_SEAD} is started to follow-up successful SEADing of the targets assigned in the task. --- * **Failed**: The SEAD task has failed. This will happen if the player exists the task early, without communicating a possible cancellation to HQ. --- --- === --- --- ### Authors: FlightControl - Design and Programming --- --- @module Task_SEAD - - -do -- TASK_SEAD - - --- The TASK_SEAD class - -- @type TASK_SEAD - -- @field Set#SET_UNIT TargetSetUnit - -- @extends Task#TASK_BASE - TASK_SEAD = { - ClassName = "TASK_SEAD", - } - - --- Instantiates a new TASK_SEAD. - -- @param #TASK_SEAD self - -- @param Mission#MISSION Mission - -- @param Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. - -- @param #string TaskName The name of the Task. - -- @param Set#SET_UNIT UnitSetTargets - -- @param Zone#ZONE_BASE TargetZone - -- @return #TASK_SEAD self - function TASK_SEAD:New( Mission, SetGroup, TaskName, TargetSetUnit, TargetZone ) - local self = BASE:Inherit( self, TASK_BASE:New( Mission, SetGroup, TaskName, "SEAD", "A2G" ) ) - self:F() - - self.TargetSetUnit = TargetSetUnit - self.TargetZone = TargetZone - - _EVENTDISPATCHER:OnPlayerLeaveUnit( self._EventPlayerLeaveUnit, self ) - _EVENTDISPATCHER:OnDead( self._EventDead, self ) - _EVENTDISPATCHER:OnCrash( self._EventDead, self ) - _EVENTDISPATCHER:OnPilotDead( self._EventDead, self ) - - return self - end - - --- Removes a TASK_SEAD. - -- @param #TASK_SEAD self - -- @return #nil - function TASK_SEAD:CleanUp() - - self:GetParent(self):CleanUp() - - return nil - end - - - - --- Assign the @{Task} to a @{Unit}. - -- @param #TASK_SEAD self - -- @param Unit#UNIT TaskUnit - -- @return #TASK_SEAD self - function TASK_SEAD:AssignToUnit( TaskUnit ) - self:F( TaskUnit:GetName() ) - - local ProcessAssign = self:AddProcess( TaskUnit, PROCESS_ASSIGN_ACCEPT:New( self, TaskUnit, self.TaskBriefing ) ) - local ProcessRoute = self:AddProcess( TaskUnit, PROCESS_ROUTE:New( self, TaskUnit, self.TargetZone ) ) - local ProcessSEAD = self:AddProcess( TaskUnit, PROCESS_DESTROY:New( self, "SEAD", TaskUnit, self.TargetSetUnit ) ) - local ProcessSmoke = self:AddProcess( TaskUnit, PROCESS_SMOKE_TARGETS:New( self, TaskUnit, self.TargetSetUnit, self.TargetZone ) ) - - local Process = self:AddStateMachine( TaskUnit, STATEMACHINE_TASK:New( self, TaskUnit, { - initial = 'None', - events = { - { name = 'Next', from = 'None', to = 'Planned' }, - { name = 'Next', from = 'Planned', to = 'Assigned' }, - { name = 'Reject', from = 'Planned', to = 'Rejected' }, - { name = 'Next', from = 'Assigned', to = 'Success' }, - { name = 'Fail', from = 'Assigned', to = 'Failed' }, - { name = 'Fail', from = 'Arrived', to = 'Failed' } - }, - callbacks = { - onNext = self.OnNext, - onRemove = self.OnRemove, - }, - subs = { - Assign = { onstateparent = 'Planned', oneventparent = 'Next', fsm = ProcessAssign.Fsm, event = 'Start', returnevents = { 'Next', 'Reject' } }, - Route = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessRoute.Fsm, event = 'Start' }, - Sead = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessSEAD.Fsm, event = 'Start', returnevents = { 'Next' } }, - Smoke = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessSmoke.Fsm, event = 'Start', } - } - } ) ) - - ProcessRoute:AddScore( "Failed", "failed to destroy a radar", -100 ) - ProcessSEAD:AddScore( "Destroy", "destroyed a radar", 25 ) - ProcessSEAD:AddScore( "Failed", "failed to destroy a radar", -100 ) - self:AddScore( "Success", "Destroyed all target radars", 250 ) - - Process:Next() - - return self - end - - --- StateMachine callback function for a TASK - -- @param #TASK_SEAD self - -- @param StateMachine#STATEMACHINE_TASK Fsm - -- @param #string Event - -- @param #string From - -- @param #string To - -- @param Event#EVENTDATA Event - function TASK_SEAD:OnNext( Fsm, Event, From, To ) - - self:SetState( self, "State", To ) - - end - - - --- @param #TASK_SEAD self - function TASK_SEAD:GetPlannedMenuText() - return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.TargetSetUnit:GetUnitTypesText() .. " )" - end - - --- @param #TASK_SEAD self - function TASK_SEAD:_Schedule() - self:F2() - - self.TaskScheduler = SCHEDULER:New( self, _Scheduler, {}, 15, 15 ) - return self - end - - - --- @param #TASK_SEAD self - function TASK_SEAD._Scheduler() - self:F2() - - return true - end - -end ---- This module contains the TASK_A2G classes. --- --- 1) @{#TASK_A2G} class, extends @{Task#TASK_BASE} --- ================================================= --- The @{#TASK_A2G} class defines a new CAS task of a @{Set} of Target Units, located at a Target Zone, based on the tasking capabilities defined in @{Task#TASK_BASE}. --- The TASK_A2G is processed through a @{Statemachine#STATEMACHINE_TASK}, and has the following statuses: --- --- * **None**: Start of the process --- * **Planned**: The SEAD task is planned. Upon Planned, the sub-process @{Process_Assign#PROCESS_ASSIGN_ACCEPT} is started to accept the task. --- * **Assigned**: The SEAD task is assigned to a @{Group#GROUP}. Upon Assigned, the sub-process @{Process_Route#PROCESS_ROUTE} is started to route the active Units in the Group to the attack zone. --- * **Success**: The SEAD task is successfully completed. Upon Success, the sub-process @{Process_SEAD#PROCESS_SEAD} is started to follow-up successful SEADing of the targets assigned in the task. --- * **Failed**: The SEAD task has failed. This will happen if the player exists the task early, without communicating a possible cancellation to HQ. --- --- === --- --- ### Authors: FlightControl - Design and Programming --- --- @module Task_CAS - - -do -- TASK_A2G - - --- The TASK_A2G class - -- @type TASK_A2G - -- @extends Task#TASK_BASE - TASK_A2G = { - ClassName = "TASK_A2G", - } - - --- Instantiates a new TASK_A2G. - -- @param #TASK_A2G self - -- @param Mission#MISSION Mission - -- @param Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. - -- @param #string TaskName The name of the Task. - -- @param #string TaskType BAI or CAS - -- @param Set#SET_UNIT UnitSetTargets - -- @param Zone#ZONE_BASE TargetZone - -- @return #TASK_A2G self - function TASK_A2G:New( Mission, SetGroup, TaskName, TaskType, TargetSetUnit, TargetZone, FACUnit ) - local self = BASE:Inherit( self, TASK_BASE:New( Mission, SetGroup, TaskName, TaskType, "A2G" ) ) - self:F() - - self.TargetSetUnit = TargetSetUnit - self.TargetZone = TargetZone - self.FACUnit = FACUnit - - _EVENTDISPATCHER:OnPlayerLeaveUnit( self._EventPlayerLeaveUnit, self ) - _EVENTDISPATCHER:OnDead( self._EventDead, self ) - _EVENTDISPATCHER:OnCrash( self._EventDead, self ) - _EVENTDISPATCHER:OnPilotDead( self._EventDead, self ) - - return self - end - - --- Removes a TASK_A2G. - -- @param #TASK_A2G self - -- @return #nil - function TASK_A2G:CleanUp() - - self:GetParent( self ):CleanUp() - - return nil - end - - - --- Assign the @{Task} to a @{Unit}. - -- @param #TASK_A2G self - -- @param Unit#UNIT TaskUnit - -- @return #TASK_A2G self - function TASK_A2G:AssignToUnit( TaskUnit ) - self:F( TaskUnit:GetName() ) - - local ProcessAssign = self:AddProcess( TaskUnit, PROCESS_ASSIGN_ACCEPT:New( self, TaskUnit, self.TaskBriefing ) ) - local ProcessRoute = self:AddProcess( TaskUnit, PROCESS_ROUTE:New( self, TaskUnit, self.TargetZone ) ) - local ProcessDestroy = self:AddProcess( TaskUnit, PROCESS_DESTROY:New( self, self.TaskType, TaskUnit, self.TargetSetUnit ) ) - local ProcessSmoke = self:AddProcess( TaskUnit, PROCESS_SMOKE_TARGETS:New( self, TaskUnit, self.TargetSetUnit, self.TargetZone ) ) - local ProcessJTAC = self:AddProcess( TaskUnit, PROCESS_JTAC:New( self, TaskUnit, self.TargetSetUnit, self.FACUnit ) ) - - local Process = self:AddStateMachine( TaskUnit, STATEMACHINE_TASK:New( self, TaskUnit, { - initial = 'None', - events = { - { name = 'Next', from = 'None', to = 'Planned' }, - { name = 'Next', from = 'Planned', to = 'Assigned' }, - { name = 'Reject', from = 'Planned', to = 'Rejected' }, - { name = 'Next', from = 'Assigned', to = 'Success' }, - { name = 'Fail', from = 'Assigned', to = 'Failed' }, - { name = 'Fail', from = 'Arrived', to = 'Failed' } - }, - callbacks = { - onNext = self.OnNext, - onRemove = self.OnRemove, - }, - subs = { - Assign = { onstateparent = 'Planned', oneventparent = 'Next', fsm = ProcessAssign.Fsm, event = 'Start', returnevents = { 'Next', 'Reject' } }, - Route = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessRoute.Fsm, event = 'Start' }, - Destroy = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessDestroy.Fsm, event = 'Start', returnevents = { 'Next' } }, - Smoke = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessSmoke.Fsm, event = 'Start', }, - JTAC = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessJTAC.Fsm, event = 'Start', }, - } - } ) ) - - ProcessRoute:AddScore( "Failed", "failed to destroy a ground unit", -100 ) - ProcessDestroy:AddScore( "Destroy", "destroyed a ground unit", 25 ) - ProcessDestroy:AddScore( "Failed", "failed to destroy a ground unit", -100 ) - - Process:Next() - - return self - end - - --- StateMachine callback function for a TASK - -- @param #TASK_A2G self - -- @param StateMachine#STATEMACHINE_TASK Fsm - -- @param #string Event - -- @param #string From - -- @param #string To - -- @param Event#EVENTDATA Event - function TASK_A2G:OnNext( Fsm, Event, From, To, Event ) - - self:SetState( self, "State", To ) - - end - - --- @param #TASK_A2G self - function TASK_A2G:GetPlannedMenuText() - return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.TargetSetUnit:GetUnitTypesText() .. " )" - end - - - --- @param #TASK_A2G self - function TASK_A2G:_Schedule() - self:F2() - - self.TaskScheduler = SCHEDULER:New( self, _Scheduler, {}, 15, 15 ) - return self - end - - - --- @param #TASK_A2G self - function TASK_A2G._Scheduler() - self:F2() - - return true - end - -end - - - - -BASE:TraceOnOff( false ) -env.info( '*** MOOSE INCLUDE END *** ' ) diff --git a/Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC/l10n/DEFAULT/Moose_Test_FAC.lua b/Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC/l10n/DEFAULT/Moose_Test_FAC.lua deleted file mode 100644 index 8e51ca4db..000000000 --- a/Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC/l10n/DEFAULT/Moose_Test_FAC.lua +++ /dev/null @@ -1,8 +0,0 @@ - - -local FACGroup = GROUP:FindByName( "FAC Group" ) - -local FACDetection = DETECTION_UNITGROUPS:New( FACGroup, 1000, 250 ) -local FACClientSet = SET_CLIENT:New():FilterCoalitions( "blue" ):FilterStart() - -local FACReporting = FAC_REPORTING:New( FACClientSet, FACDetection ) \ No newline at end of file diff --git a/Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC/l10n/DEFAULT/dictionary b/Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC/l10n/DEFAULT/dictionary deleted file mode 100644 index bb3e2c10b..000000000 --- a/Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC/l10n/DEFAULT/dictionary +++ /dev/null @@ -1,245 +0,0 @@ -dictionary = -{ - ["DictKey_GroupName_63"] = "Target #011", - ["DictKey_WptName_201"] = "", - ["DictKey_UnitName_52"] = "Target #007", - ["DictKey_UnitName_67"] = "Target #012", - ["DictKey_WptName_185"] = "", - ["DictKey_WptName_86"] = "", - ["DictKey_WptName_138"] = "", - ["DictKey_GroupName_96"] = "Target #022", - ["DictKey_WptName_113"] = "", - ["DictKey_WptName_194"] = "", - ["DictKey_WptName_41"] = "", - ["DictKey_UnitName_73"] = "Target #014", - ["DictKey_GroupName_21"] = "FAC Group #004", - ["DictKey_WptName_198"] = "", - ["DictKey_WptName_31"] = "", - ["DictKey_ActionText_30"] = "BASE:TraceClass( \"DETECTION_UNITGROUPS\" )\ -BASE:TraceClass( \"SET_UNIT\" )\ -BASE:TraceClass( \"FAC_REPORT\" )\ -BASE:TraceLevel( 2 )", - ["DictKey_WptName_114"] = "", - ["DictKey_WptName_112"] = "", - ["DictKey_GroupName_102"] = "New Vehicle Group", - ["DictKey_WptName_145"] = "", - ["DictKey_UnitName_25"] = "Unit #004", - ["DictKey_UnitName_197"] = "Target #026", - ["DictKey_WptName_123"] = "", - ["DictKey_GroupName_51"] = "Target #007", - ["DictKey_WptName_215"] = "", - ["DictKey_WptName_59"] = "", - ["DictKey_WptName_13"] = "", - ["DictKey_WptName_124"] = "", - ["DictKey_WptName_212"] = "", - ["DictKey_WptName_217"] = "", - ["DictKey_UnitName_76"] = "Target #015", - ["DictKey_WptName_199"] = "", - ["DictKey_WptName_134"] = "", - ["DictKey_WptName_181"] = "", - ["DictKey_WptName_109"] = "", - ["DictKey_GroupName_45"] = "Target #005", - ["DictKey_WptName_77"] = "", - ["DictKey_GroupName_32"] = "Target #001", - ["DictKey_UnitName_190"] = "Target #025", - ["DictKey_WptName_147"] = "", - ["DictKey_WptName_101"] = "", - ["DictKey_GroupName_42"] = "Target #004", - ["DictKey_GroupName_84"] = "Target #018", - ["DictKey_GroupName_189"] = "Target #025", - ["DictKey_UnitName_155"] = "Target #017", - ["DictKey_WptName_47"] = "", - ["DictKey_GroupName_203"] = "Target #027", - ["DictKey_UnitName_43"] = "Target #004", - ["DictKey_UnitName_61"] = "Target #010", - ["DictKey_WptName_56"] = "", - ["DictKey_WptName_157"] = "", - ["DictKey_WptName_233"] = "", - ["DictKey_GroupName_78"] = "Target #016", - ["DictKey_WptName_153"] = "", - ["DictKey_UnitName_49"] = "Target #006", - ["DictKey_WptName_187"] = "", - ["DictKey_UnitName_223"] = "Target #011", - ["DictKey_WptName_23"] = "", - ["DictKey_WptName_172"] = "", - ["DictKey_WptName_214"] = "", - ["DictKey_WptName_188"] = "", - ["DictKey_WptName_151"] = "", - ["DictKey_descriptionRedTask_2"] = "", - ["DictKey_WptName_38"] = "", - ["DictKey_WptName_71"] = "", - ["DictKey_UnitName_183"] = "Target #024", - ["DictKey_WptName_144"] = "", - ["DictKey_WptName_178"] = "", - ["DictKey_UnitName_12"] = "Unit #1", - ["DictKey_WptName_139"] = "", - ["DictKey_WptName_205"] = "", - ["DictKey_WptName_17"] = "", - ["DictKey_UnitName_211"] = "Target #028", - ["DictKey_WptName_104"] = "", - ["DictKey_WptName_14"] = "", - ["DictKey_UnitName_103"] = "Unit #1", - ["DictKey_WptName_158"] = "", - ["DictKey_UnitName_227"] = "Target #013", - ["DictKey_WptName_216"] = "", - ["DictKey_WptName_120"] = "", - ["DictKey_WptName_225"] = "", - ["DictKey_UnitName_37"] = "Target #002", - ["DictKey_WptName_200"] = "", - ["DictKey_GroupName_18"] = "FAC Group #003", - ["DictKey_GroupName_222"] = "Target #011", - ["DictKey_WptName_179"] = "", - ["DictKey_UnitName_176"] = "Target #020", - ["DictKey_WptName_236"] = "", - ["DictKey_GroupName_81"] = "Target #017", - ["DictKey_WptName_130"] = "", - ["DictKey_UnitName_46"] = "Target #005", - ["DictKey_WptName_92"] = "", - ["DictKey_GroupName_36"] = "Target #002", - ["DictKey_WptName_140"] = "", - ["DictKey_UnitName_40"] = "Target #003", - ["DictKey_WptName_121"] = "", - ["DictKey_WptName_119"] = "", - ["DictKey_WptName_174"] = "", - ["DictKey_GroupName_72"] = "Target #014", - ["DictKey_GroupName_75"] = "Target #015", - ["DictKey_GroupName_66"] = "Target #012", - ["DictKey_WptName_167"] = "", - ["DictKey_WptName_9"] = "", - ["DictKey_WptName_116"] = "", - ["DictKey_WptName_220"] = "", - ["DictKey_UnitName_79"] = "Target #016", - ["DictKey_WptName_137"] = "", - ["DictKey_GroupName_93"] = "Target #021", - ["DictKey_WptName_132"] = "", - ["DictKey_GroupName_27"] = "FAC Group #006", - ["DictKey_WptName_65"] = "", - ["DictKey_UnitName_22"] = "Unit #003", - ["DictKey_WptName_152"] = "", - ["DictKey_WptName_95"] = "", - ["DictKey_WptName_206"] = "", - ["DictKey_UnitName_169"] = "Target #019", - ["DictKey_WptName_122"] = "", - ["DictKey_UnitName_19"] = "Unit #002", - ["DictKey_sortie_4"] = "", - ["DictKey_GroupName_161"] = "Target #010", - ["DictKey_WptName_240"] = "", - ["DictKey_GroupName_175"] = "Target #020", - ["DictKey_GroupName_99"] = "Target #023", - ["DictKey_UnitName_85"] = "Target #018", - ["DictKey_WptName_80"] = "", - ["DictKey_WptName_133"] = "", - ["DictKey_WptName_235"] = "", - ["DictKey_WptName_202"] = "", - ["DictKey_UnitName_91"] = "Target #020", - ["DictKey_GroupName_57"] = "Target #009", - ["DictKey_UnitName_94"] = "Target #021", - ["DictKey_UnitName_162"] = "Target #010", - ["DictKey_WptName_177"] = "", - ["DictKey_GroupName_60"] = "Target #010", - ["DictKey_WptName_110"] = "", - ["DictKey_WptName_207"] = "", - ["DictKey_WptName_239"] = "", - ["DictKey_WptName_50"] = "", - ["DictKey_UnitName_106"] = "FAC Client", - ["DictKey_GroupName_105"] = "FAC Client Group", - ["DictKey_UnitName_58"] = "Target #009", - ["DictKey_GroupName_48"] = "Target #006", - ["DictKey_WptName_26"] = "", - ["DictKey_WptName_171"] = "", - ["DictKey_GroupName_196"] = "Target #026", - ["DictKey_GroupName_54"] = "Target #008", - ["DictKey_GroupName_39"] = "Target #003", - ["DictKey_WptName_89"] = "", - ["DictKey_WptName_237"] = "", - ["DictKey_GroupName_87"] = "Target #019", - ["DictKey_UnitName_97"] = "Target #022", - ["DictKey_WptName_128"] = "", - ["DictKey_UnitName_82"] = "Target #017", - ["DictKey_UnitName_28"] = "Unit #005", - ["DictKey_WptName_98"] = "", - ["DictKey_WptName_180"] = "", - ["DictKey_WptName_159"] = "", - ["DictKey_WptName_166"] = "", - ["DictKey_WptName_208"] = "", - ["DictKey_WptName_193"] = "", - ["DictKey_WptName_238"] = "", - ["DictKey_WptName_191"] = "", - ["DictKey_descriptionText_1"] = "", - ["DictKey_UnitName_55"] = "Target #008", - ["DictKey_WptName_232"] = "", - ["DictKey_WptName_107"] = "", - ["DictKey_WptName_115"] = "", - ["DictKey_WptName_229"] = "", - ["DictKey_WptName_108"] = "", - ["DictKey_WptName_149"] = "", - ["DictKey_WptName_142"] = "", - ["DictKey_UnitName_100"] = "Target #023", - ["DictKey_WptName_170"] = "", - ["DictKey_WptName_164"] = "", - ["DictKey_WptName_74"] = "", - ["DictKey_WptName_141"] = "", - ["DictKey_WptName_143"] = "", - ["DictKey_UnitName_33"] = "Target #001", - ["DictKey_GroupName_69"] = "Target #013", - ["DictKey_WptName_127"] = "", - ["DictKey_WptName_129"] = "", - ["DictKey_WptName_213"] = "", - ["DictKey_GroupName_24"] = "FAC Group #005", - ["DictKey_WptName_186"] = "", - ["DictKey_GroupName_7"] = "FAC Group", - ["DictKey_WptName_148"] = "", - ["DictKey_WptName_146"] = "", - ["DictKey_WptName_230"] = "", - ["DictKey_WptName_156"] = "", - ["DictKey_GroupName_154"] = "Target #017", - ["DictKey_UnitName_64"] = "Target #011", - ["DictKey_GroupName_218"] = "Target #010", - ["DictKey_UnitName_219"] = "Target #010", - ["DictKey_WptName_68"] = "", - ["DictKey_UnitName_88"] = "Target #019", - ["DictKey_WptName_160"] = "", - ["DictKey_WptName_10"] = "", - ["DictKey_GroupName_210"] = "Target #028", - ["DictKey_WptName_163"] = "", - ["DictKey_WptName_234"] = "", - ["DictKey_WptName_195"] = "", - ["DictKey_WptName_221"] = "", - ["DictKey_UnitName_204"] = "Target #027", - ["DictKey_UnitName_16"] = "Unit #001", - ["DictKey_WptName_135"] = "", - ["DictKey_WptName_131"] = "", - ["DictKey_WptName_228"] = "", - ["DictKey_WptName_231"] = "", - ["DictKey_WptName_165"] = "", - ["DictKey_WptName_224"] = "", - ["DictKey_GroupName_11"] = "FAC Group #001", - ["DictKey_UnitName_70"] = "Target #013", - ["DictKey_GroupName_226"] = "Target #013", - ["DictKey_WptName_20"] = "", - ["DictKey_GroupName_15"] = "FAC Group #002", - ["DictKey_WptName_150"] = "", - ["DictKey_WptName_126"] = "", - ["DictKey_WptName_29"] = "", - ["DictKey_WptName_173"] = "", - ["DictKey_WptName_83"] = "", - ["DictKey_WptName_241"] = "", - ["DictKey_WptName_62"] = "", - ["DictKey_GroupName_168"] = "Target #019", - ["DictKey_WptName_118"] = "", - ["DictKey_WptName_53"] = "", - ["DictKey_WptName_136"] = "", - ["DictKey_GroupName_182"] = "Target #024", - ["DictKey_descriptionBlueTask_3"] = "", - ["DictKey_WptName_192"] = "", - ["DictKey_WptName_117"] = "", - ["DictKey_WptName_125"] = "", - ["DictKey_WptName_44"] = "", - ["DictKey_GroupName_90"] = "Target #020", - ["DictKey_WptName_111"] = "", - ["DictKey_WptName_35"] = "", - ["DictKey_WptName_184"] = "", - ["DictKey_WptName_209"] = "", - ["DictKey_WptName_34"] = "", - ["DictKey_UnitName_8"] = "FAC Unit", -} -- end of dictionary diff --git a/Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC/l10n/DEFAULT/mapResource b/Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC/l10n/DEFAULT/mapResource deleted file mode 100644 index a7d1ef0da..000000000 --- a/Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC/l10n/DEFAULT/mapResource +++ /dev/null @@ -1,5 +0,0 @@ -mapResource = -{ - ["ResKey_Action_5"] = "Moose.lua", - ["ResKey_Action_6"] = "Moose_Test_FAC.lua", -} -- end of mapResource diff --git a/Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC/mission b/Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC/mission deleted file mode 100644 index 505787787..000000000 --- a/Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC/mission +++ /dev/null @@ -1,6238 +0,0 @@ -mission = -{ - ["trig"] = - { - ["actions"] = - { - [1] = "a_do_script_file(getValueResourceByKey(\"ResKey_Action_5\"));", - [2] = "a_do_script(getValueDictByKey(\"DictKey_ActionText_30\"));", - [3] = "a_do_script_file(getValueResourceByKey(\"ResKey_Action_6\"));", - }, -- end of ["actions"] - ["events"] = - { - }, -- end of ["events"] - ["custom"] = - { - }, -- end of ["custom"] - ["func"] = - { - }, -- end of ["func"] - ["flag"] = - { - [1] = true, - [2] = true, - [3] = true, - }, -- end of ["flag"] - ["conditions"] = - { - [1] = "return(true)", - [2] = "return(true)", - [3] = "return(true)", - }, -- end of ["conditions"] - ["customStartup"] = - { - }, -- end of ["customStartup"] - ["funcStartup"] = - { - [1] = "if mission.trig.conditions[1]() then mission.trig.actions[1]() end", - [2] = "if mission.trig.conditions[2]() then mission.trig.actions[2]() end", - [3] = "if mission.trig.conditions[3]() then mission.trig.actions[3]() end", - }, -- end of ["funcStartup"] - }, -- end of ["trig"] - ["result"] = - { - ["offline"] = - { - ["conditions"] = - { - }, -- end of ["conditions"] - ["actions"] = - { - }, -- end of ["actions"] - ["func"] = - { - }, -- end of ["func"] - }, -- end of ["offline"] - ["total"] = 0, - ["blue"] = - { - ["conditions"] = - { - }, -- end of ["conditions"] - ["actions"] = - { - }, -- end of ["actions"] - ["func"] = - { - }, -- end of ["func"] - }, -- end of ["blue"] - ["red"] = - { - ["conditions"] = - { - }, -- end of ["conditions"] - ["actions"] = - { - }, -- end of ["actions"] - ["func"] = - { - }, -- end of ["func"] - }, -- end of ["red"] - }, -- end of ["result"] - ["maxDictId"] = 241, - ["groundControl"] = - { - ["isPilotControlVehicles"] = false, - ["roles"] = - { - ["artillery_commander"] = - { - ["blue"] = 0, - ["red"] = 1, - }, -- end of ["artillery_commander"] - ["instructor"] = - { - ["blue"] = 0, - ["red"] = 1, - }, -- end of ["instructor"] - ["observer"] = - { - ["blue"] = 0, - ["red"] = 0, - }, -- end of ["observer"] - ["forward_observer"] = - { - ["blue"] = 0, - ["red"] = 1, - }, -- end of ["forward_observer"] - }, -- end of ["roles"] - }, -- end of ["groundControl"] - ["usedModules"] = - { - ["F-5E/E-3 by Belsimtek"] = true, - ["M-2000C by RAZBAM Sims"] = true, - ["SA342 AI by Polychop-Simulations"] = true, - ["Hawk T.1A by VEAO Simulations"] = true, - ["MiG-15bis AI by Eagle Dynamics"] = true, - ["C-101 Aviojet by AvioDev"] = true, - ["F-86F Sabre by Belsimtek"] = true, - ["F-15C"] = false, - ["M-2000C AI by RAZBAM Sims"] = true, - ["SA342 Gazelle by Polychop-Simulations"] = true, - ["Ka-50 Black Shark by Eagle Dynamics"] = true, - ["World War II AI Units by Eagle Dynamics"] = true, - ["MiG-21Bis by Leatherneck Simulations"] = false, - ["F-86F Sabre AI by Eagle Dynamics"] = true, - ["TF-51D Mustang by Eagle Dynamics"] = true, - ["Su-25T by Eagle Dynamics"] = true, - ["Mi-8MTV2 Hip by Belsimtek"] = true, - ["FW-190D9 Dora by Eagle Dynamics"] = true, - ["Su-27 Flanker by Eagle Dynamics"] = false, - ["L-39C"] = true, - ["UH-1H Huey by Belsimtek"] = true, - ["C-101 Aviojet"] = true, - ["./CoreMods/aircraft/MQ-9 Reaper"] = true, - ["Combined Arms by Eagle Dynamics"] = true, - ["Su-25A by Eagle Dynamics"] = false, - ["MiG-21Bis AI by Leatherneck Simulations"] = true, - ["L-39C/ZA by Eagle Dynamics"] = true, - ["Bf 109 K-4 by Eagle Dynamics"] = true, - ["Caucasus"] = true, - ["A-10A by Eagle Dynamics"] = false, - ["Hawk T.1A AI by VEAO Simulations"] = true, - ["P-51D Mustang by Eagle Dynamics"] = true, - ["MiG-15bis by Belsimtek"] = true, - ["A-10C Warthog by Eagle Dynamics"] = true, - ["Flaming Cliffs by Eagle Dynamics"] = true, - }, -- end of ["usedModules"] - ["triggers"] = - { - ["zones"] = - { - }, -- end of ["zones"] - }, -- end of ["triggers"] - ["weather"] = - { - ["atmosphere_type"] = 0, - ["wind"] = - { - ["at8000"] = - { - ["speed"] = 0, - ["dir"] = 0, - }, -- end of ["at8000"] - ["atGround"] = - { - ["speed"] = 0, - ["dir"] = 0, - }, -- end of ["atGround"] - ["at2000"] = - { - ["speed"] = 0, - ["dir"] = 0, - }, -- end of ["at2000"] - }, -- end of ["wind"] - ["enable_fog"] = false, - ["turbulence"] = - { - ["at8000"] = 0, - ["atGround"] = 0, - ["at2000"] = 0, - }, -- end of ["turbulence"] - ["season"] = - { - ["iseason"] = 1, - ["temperature"] = 20, - }, -- end of ["season"] - ["type_weather"] = 0, - ["qnh"] = 760, - ["cyclones"] = - { - }, -- end of ["cyclones"] - ["name"] = "Winter, clean sky", - ["fog"] = - { - ["thickness"] = 0, - ["visibility"] = 25, - ["density"] = 7, - }, -- end of ["fog"] - ["visibility"] = - { - ["distance"] = 80000, - }, -- end of ["visibility"] - ["clouds"] = - { - ["thickness"] = 200, - ["density"] = 0, - ["base"] = 300, - ["iprecptns"] = 0, - }, -- end of ["clouds"] - }, -- end of ["weather"] - ["theatre"] = "Caucasus", - ["needModules"] = - { - }, -- end of ["needModules"] - ["map"] = - { - ["centerY"] = 384776.85714286, - ["zoom"] = 25000, - ["centerX"] = 73064.285714284, - }, -- end of ["map"] - ["coalitions"] = - { - ["blue"] = - { - [1] = 11, - [2] = 4, - [3] = 6, - [4] = 16, - [5] = 13, - [6] = 15, - [7] = 9, - [8] = 8, - [9] = 12, - [10] = 2, - [11] = 3, - [12] = 5, - [13] = 10, - [14] = 20, - [15] = 21, - [16] = 40, - [17] = 26, - [18] = 45, - [19] = 28, - }, -- end of ["blue"] - ["neutrals"] = - { - [1] = 7, - [2] = 17, - [3] = 22, - [4] = 23, - [5] = 25, - [6] = 29, - [7] = 30, - [8] = 31, - [9] = 32, - [10] = 33, - [11] = 35, - [12] = 36, - [13] = 39, - [14] = 41, - [15] = 42, - [16] = 44, - [17] = 46, - }, -- end of ["neutrals"] - ["red"] = - { - [1] = 0, - [2] = 1, - [3] = 18, - [4] = 19, - [5] = 37, - [6] = 24, - [7] = 27, - [8] = 43, - [9] = 47, - [10] = 34, - [11] = 38, - }, -- end of ["red"] - }, -- end of ["coalitions"] - ["descriptionText"] = "DictKey_descriptionText_1", - ["pictureFileNameR"] = - { - }, -- end of ["pictureFileNameR"] - ["descriptionBlueTask"] = "DictKey_descriptionBlueTask_3", - ["descriptionRedTask"] = "DictKey_descriptionRedTask_2", - ["pictureFileNameB"] = - { - }, -- end of ["pictureFileNameB"] - ["trigrules"] = - { - [1] = - { - ["rules"] = - { - }, -- end of ["rules"] - ["eventlist"] = "", - ["actions"] = - { - [1] = - { - ["file"] = "ResKey_Action_5", - ["predicate"] = "a_do_script_file", - ["ai_task"] = - { - [1] = "", - [2] = "", - }, -- end of ["ai_task"] - }, -- end of [1] - }, -- end of ["actions"] - ["predicate"] = "triggerStart", - ["comment"] = "Load Moose", - }, -- end of [1] - [2] = - { - ["rules"] = - { - }, -- end of ["rules"] - ["comment"] = "Trace", - ["eventlist"] = "", - ["predicate"] = "triggerStart", - ["actions"] = - { - [1] = - { - ["predicate"] = "a_do_script", - ["text"] = "DictKey_ActionText_30", - ["KeyDict_text"] = "DictKey_ActionText_30", - ["ai_task"] = - { - [1] = "", - [2] = "", - }, -- end of ["ai_task"] - }, -- end of [1] - }, -- end of ["actions"] - }, -- end of [2] - [3] = - { - ["rules"] = - { - }, -- end of ["rules"] - ["eventlist"] = "", - ["actions"] = - { - [1] = - { - ["file"] = "ResKey_Action_6", - ["predicate"] = "a_do_script_file", - ["ai_task"] = - { - [1] = "", - [2] = "", - }, -- end of ["ai_task"] - }, -- end of [1] - }, -- end of ["actions"] - ["predicate"] = "triggerStart", - ["comment"] = "Load Mission", - }, -- end of [3] - }, -- end of ["trigrules"] - ["coalition"] = - { - ["blue"] = - { - ["bullseye"] = - { - ["y"] = 617414, - ["x"] = -291014, - }, -- end of ["bullseye"] - ["nav_points"] = - { - }, -- end of ["nav_points"] - ["name"] = "blue", - ["country"] = - { - [1] = - { - ["id"] = 11, - ["name"] = "Belgium", - }, -- end of [1] - [2] = - { - ["id"] = 4, - ["name"] = "UK", - }, -- end of [2] - [3] = - { - ["id"] = 6, - ["name"] = "Germany", - }, -- end of [3] - [4] = - { - ["id"] = 16, - ["name"] = "Georgia", - }, -- end of [4] - [5] = - { - ["id"] = 13, - ["name"] = "Denmark", - }, -- end of [5] - [6] = - { - ["id"] = 15, - ["name"] = "Israel", - }, -- end of [6] - [7] = - { - ["id"] = 9, - ["name"] = "Spain", - }, -- end of [7] - [8] = - { - ["id"] = 8, - ["name"] = "Canada", - }, -- end of [8] - [9] = - { - ["id"] = 12, - ["name"] = "Norway", - }, -- end of [9] - [10] = - { - ["helicopter"] = - { - ["group"] = - { - [1] = - { - ["modulation"] = 0, - ["tasks"] = - { - }, -- end of ["tasks"] - ["radioSet"] = false, - ["task"] = "CAS", - ["uncontrolled"] = false, - ["route"] = - { - ["points"] = - { - [1] = - { - ["alt"] = 500, - ["type"] = "Turning Point", - ["action"] = "Turning Point", - ["alt_type"] = "BARO", - ["formation_template"] = "", - ["properties"] = - { - ["vnav"] = 1, - ["scale"] = 0, - ["angle"] = 0, - ["vangle"] = 0, - ["steer"] = 2, - }, -- end of ["properties"] - ["ETA"] = 0, - ["y"] = 384602.57142857, - ["x"] = 55625.714285714, - ["name"] = "DictKey_WptName_107", - ["speed"] = 55.555555555556, - ["ETA_locked"] = true, - ["task"] = - { - ["id"] = "ComboTask", - ["params"] = - { - ["tasks"] = - { - [1] = - { - ["number"] = 1, - ["key"] = "CAS", - ["id"] = "EngageTargets", - ["enabled"] = true, - ["auto"] = true, - ["params"] = - { - ["targetTypes"] = - { - [1] = "Helicopters", - [2] = "Ground Units", - [3] = "Light armed ships", - }, -- end of ["targetTypes"] - ["priority"] = 0, - }, -- end of ["params"] - }, -- end of [1] - }, -- end of ["tasks"] - }, -- end of ["params"] - }, -- end of ["task"] - ["speed_locked"] = true, - }, -- end of [1] - [2] = - { - ["alt"] = 500, - ["type"] = "Turning Point", - ["action"] = "Turning Point", - ["alt_type"] = "BARO", - ["formation_template"] = "", - ["properties"] = - { - ["vnav"] = 1, - ["scale"] = 0, - ["angle"] = 0, - ["vangle"] = 0, - ["steer"] = 2, - }, -- end of ["properties"] - ["ETA"] = 134.22857142856, - ["y"] = 384602.57142857, - ["x"] = 63082.857142856, - ["name"] = "DictKey_WptName_109", - ["speed"] = 55.555555555556, - ["ETA_locked"] = false, - ["task"] = - { - ["id"] = "ComboTask", - ["params"] = - { - ["tasks"] = - { - }, -- end of ["tasks"] - }, -- end of ["params"] - }, -- end of ["task"] - ["speed_locked"] = true, - }, -- end of [2] - }, -- end of ["points"] - }, -- end of ["route"] - ["groupId"] = 31, - ["hidden"] = false, - ["units"] = - { - [1] = - { - ["alt"] = 500, - ["alt_type"] = "BARO", - ["livery_id"] = "us army", - ["skill"] = "Client", - ["ropeLength"] = 15, - ["speed"] = 55.555555555556, - ["type"] = "Ka-50", - ["Radio"] = - { - [1] = - { - ["channels"] = - { - [7] = 40, - [1] = 21.5, - [2] = 25.7, - [4] = 28, - [8] = 50, - [9] = 55.5, - [5] = 30, - [10] = 59.9, - [3] = 27, - [6] = 32, - }, -- end of ["channels"] - }, -- end of [1] - [2] = - { - ["channels"] = - { - [15] = 0.995, - [13] = 0.583, - [7] = 0.443, - [14] = 0.283, - [2] = 0.303, - [4] = 0.591, - [8] = 0.215, - [16] = 1.21, - [9] = 0.525, - [5] = 0.408, - [10] = 1.065, - [3] = 0.289, - [11] = 0.718, - [6] = 0.803, - [12] = 0.35, - [1] = 0.625, - }, -- end of ["channels"] - }, -- end of [2] - }, -- end of ["Radio"] - ["unitId"] = 31, - ["psi"] = -0, - ["y"] = 384602.57142857, - ["x"] = 55625.714285714, - ["name"] = "DictKey_UnitName_106", - ["payload"] = - { - ["pylons"] = - { - }, -- end of ["pylons"] - ["fuel"] = "1450", - ["flare"] = 128, - ["chaff"] = 0, - ["gun"] = 100, - }, -- end of ["payload"] - ["heading"] = 0, - ["callsign"] = - { - [1] = 1, - [2] = 1, - [3] = 1, - ["name"] = "Enfield11", - }, -- end of ["callsign"] - ["onboard_num"] = "050", - }, -- end of [1] - }, -- end of ["units"] - ["y"] = 384602.57142857, - ["x"] = 55625.714285714, - ["name"] = "DictKey_GroupName_105", - ["communication"] = true, - ["start_time"] = 0, - ["frequency"] = 124, - }, -- end of [1] - }, -- end of ["group"] - }, -- end of ["helicopter"] - ["name"] = "USA", - ["id"] = 2, - ["vehicle"] = - { - ["group"] = - { - [1] = - { - ["visible"] = false, - ["taskSelected"] = true, - ["route"] = - { - ["spans"] = - { - [1] = - { - [1] = - { - ["y"] = 384574, - ["x"] = 71997.142857142, - }, -- end of [1] - [2] = - { - ["y"] = 384056.85714285, - ["x"] = 73811.714285714, - }, -- end of [2] - }, -- end of [1] - [2] = - { - [1] = - { - ["y"] = 384056.85714285, - ["x"] = 73811.714285714, - }, -- end of [1] - [2] = - { - ["y"] = 384056.85714285, - ["x"] = 73811.714285714, - }, -- end of [2] - }, -- end of [2] - }, -- end of ["spans"] - ["points"] = - { - [1] = - { - ["alt"] = 0, - ["type"] = "Turning Point", - ["ETA"] = 0, - ["alt_type"] = "BARO", - ["formation_template"] = "", - ["y"] = 384574, - ["x"] = 71997.142857142, - ["name"] = "DictKey_WptName_9", - ["ETA_locked"] = true, - ["speed"] = 5.5555555555556, - ["action"] = "Off Road", - ["task"] = - { - ["id"] = "ComboTask", - ["params"] = - { - ["tasks"] = - { - [1] = - { - ["number"] = 1, - ["auto"] = true, - ["id"] = "WrappedAction", - ["enabled"] = true, - ["params"] = - { - ["action"] = - { - ["id"] = "EPLRS", - ["params"] = - { - ["value"] = true, - ["groupId"] = 1, - }, -- end of ["params"] - }, -- end of ["action"] - }, -- end of ["params"] - }, -- end of [1] - }, -- end of ["tasks"] - }, -- end of ["params"] - }, -- end of ["task"] - ["speed_locked"] = true, - }, -- end of [1] - [2] = - { - ["alt"] = 0, - ["type"] = "Turning Point", - ["ETA"] = 194.2557287786, - ["alt_type"] = "BARO", - ["formation_template"] = "", - ["y"] = 384056.85714285, - ["x"] = 73811.714285714, - ["name"] = "DictKey_WptName_31", - ["ETA_locked"] = false, - ["speed"] = 2.7777777777778, - ["action"] = "Off Road", - ["task"] = - { - ["id"] = "ComboTask", - ["params"] = - { - ["tasks"] = - { - }, -- end of ["tasks"] - }, -- end of ["params"] - }, -- end of ["task"] - ["speed_locked"] = true, - }, -- end of [2] - }, -- end of ["points"] - }, -- end of ["route"] - ["groupId"] = 1, - ["tasks"] = - { - }, -- end of ["tasks"] - ["hidden"] = false, - ["units"] = - { - [1] = - { - ["type"] = "M1134 Stryker ATGM", - ["transportable"] = - { - ["randomTransportable"] = false, - }, -- end of ["transportable"] - ["unitId"] = 1, - ["skill"] = "Average", - ["y"] = 384574, - ["x"] = 71997.142857142, - ["name"] = "DictKey_UnitName_8", - ["playerCanDrive"] = true, - ["heading"] = -0.27763406017192, - }, -- end of [1] - }, -- end of ["units"] - ["y"] = 384574, - ["x"] = 71997.142857142, - ["name"] = "DictKey_GroupName_7", - ["start_time"] = 0, - ["task"] = "Ground Nothing", - }, -- end of [1] - }, -- end of ["group"] - }, -- end of ["vehicle"] - }, -- end of [10] - [11] = - { - ["id"] = 3, - ["name"] = "Turkey", - }, -- end of [11] - [12] = - { - ["id"] = 5, - ["name"] = "France", - }, -- end of [12] - [13] = - { - ["id"] = 10, - ["name"] = "The Netherlands", - }, -- end of [13] - [14] = - { - ["id"] = 20, - ["name"] = "Italy", - }, -- end of [14] - [15] = - { - ["id"] = 21, - ["name"] = "Australia", - }, -- end of [15] - [16] = - { - ["id"] = 40, - ["name"] = "Poland", - }, -- end of [16] - [17] = - { - ["id"] = 26, - ["name"] = "Czech Republic", - }, -- end of [17] - [18] = - { - ["id"] = 45, - ["name"] = "South Korea", - }, -- end of [18] - [19] = - { - ["id"] = 28, - ["name"] = "Croatia", - }, -- end of [19] - }, -- end of ["country"] - }, -- end of ["blue"] - ["red"] = - { - ["bullseye"] = - { - ["y"] = 371700, - ["x"] = 11557, - }, -- end of ["bullseye"] - ["nav_points"] = - { - }, -- end of ["nav_points"] - ["name"] = "red", - ["country"] = - { - [1] = - { - ["id"] = 0, - ["name"] = "Russia", - }, -- end of [1] - [2] = - { - ["id"] = 1, - ["vehicle"] = - { - ["group"] = - { - [1] = - { - ["visible"] = false, - ["taskSelected"] = true, - ["route"] = - { - ["spans"] = - { - [1] = - { - [1] = - { - ["y"] = 383571.14285714, - ["x"] = 73948.857142857, - }, -- end of [1] - [2] = - { - ["y"] = 383579.71428571, - ["x"] = 73983.142857143, - }, -- end of [2] - }, -- end of [1] - }, -- end of ["spans"] - ["points"] = - { - [1] = - { - ["alt"] = 0, - ["type"] = "Turning Point", - ["ETA"] = 0, - ["alt_type"] = "BARO", - ["formation_template"] = "", - ["y"] = 383571.14285714, - ["x"] = 73948.857142857, - ["name"] = "DictKey_WptName_34", - ["ETA_locked"] = true, - ["speed"] = 5.5555555555556, - ["action"] = "Off Road", - ["task"] = - { - ["id"] = "ComboTask", - ["params"] = - { - ["tasks"] = - { - }, -- end of ["tasks"] - }, -- end of ["params"] - }, -- end of ["task"] - ["speed_locked"] = true, - }, -- end of [1] - }, -- end of ["points"] - }, -- end of ["route"] - ["groupId"] = 8, - ["tasks"] = - { - }, -- end of ["tasks"] - ["hidden"] = false, - ["units"] = - { - [1] = - { - ["type"] = "Ural-4320T", - ["transportable"] = - { - ["randomTransportable"] = false, - }, -- end of ["transportable"] - ["unitId"] = 8, - ["skill"] = "Average", - ["y"] = 383571.14285714, - ["x"] = 73948.857142857, - ["name"] = "DictKey_UnitName_33", - ["playerCanDrive"] = false, - ["heading"] = 0.24497866312666, - }, -- end of [1] - }, -- end of ["units"] - ["y"] = 383571.14285714, - ["x"] = 73948.857142857, - ["name"] = "DictKey_GroupName_32", - ["start_time"] = 0, - ["task"] = "Ground Nothing", - }, -- end of [1] - [2] = - { - ["visible"] = false, - ["taskSelected"] = true, - ["route"] = - { - ["spans"] = - { - [1] = - { - [1] = - { - ["y"] = 383571.14285714, - ["x"] = 73948.857142857, - }, -- end of [1] - [2] = - { - ["y"] = 383579.71428571, - ["x"] = 73983.142857143, - }, -- end of [2] - }, -- end of [1] - }, -- end of ["spans"] - ["points"] = - { - [1] = - { - ["alt"] = 0, - ["type"] = "Turning Point", - ["ETA"] = 0, - ["alt_type"] = "BARO", - ["formation_template"] = "", - ["y"] = 383602.57142857, - ["x"] = 74002.857142856, - ["name"] = "DictKey_WptName_38", - ["ETA_locked"] = true, - ["speed"] = 5.5555555555556, - ["action"] = "Off Road", - ["task"] = - { - ["id"] = "ComboTask", - ["params"] = - { - ["tasks"] = - { - }, -- end of ["tasks"] - }, -- end of ["params"] - }, -- end of ["task"] - ["speed_locked"] = true, - }, -- end of [1] - }, -- end of ["points"] - }, -- end of ["route"] - ["groupId"] = 9, - ["tasks"] = - { - }, -- end of ["tasks"] - ["hidden"] = false, - ["units"] = - { - [1] = - { - ["type"] = "ZIL-131 KUNG", - ["transportable"] = - { - ["randomTransportable"] = false, - }, -- end of ["transportable"] - ["unitId"] = 9, - ["skill"] = "Average", - ["y"] = 383602.57142857, - ["x"] = 74002.857142856, - ["name"] = "DictKey_UnitName_37", - ["playerCanDrive"] = false, - ["heading"] = 0.24497866312666, - }, -- end of [1] - }, -- end of ["units"] - ["y"] = 383602.57142857, - ["x"] = 74002.857142856, - ["name"] = "DictKey_GroupName_36", - ["start_time"] = 0, - ["task"] = "Ground Nothing", - }, -- end of [2] - [3] = - { - ["visible"] = false, - ["taskSelected"] = true, - ["route"] = - { - ["spans"] = - { - [1] = - { - [1] = - { - ["y"] = 383571.14285714, - ["x"] = 73948.857142857, - }, -- end of [1] - [2] = - { - ["y"] = 383579.71428571, - ["x"] = 73983.142857143, - }, -- end of [2] - }, -- end of [1] - }, -- end of ["spans"] - ["points"] = - { - [1] = - { - ["alt"] = 0, - ["type"] = "Turning Point", - ["ETA"] = 0, - ["alt_type"] = "BARO", - ["formation_template"] = "", - ["y"] = 383742.57142857, - ["x"] = 73702.857142857, - ["name"] = "DictKey_WptName_41", - ["ETA_locked"] = true, - ["speed"] = 5.5555555555556, - ["action"] = "Off Road", - ["task"] = - { - ["id"] = "ComboTask", - ["params"] = - { - ["tasks"] = - { - }, -- end of ["tasks"] - }, -- end of ["params"] - }, -- end of ["task"] - ["speed_locked"] = true, - }, -- end of [1] - }, -- end of ["points"] - }, -- end of ["route"] - ["groupId"] = 10, - ["tasks"] = - { - }, -- end of ["tasks"] - ["hidden"] = false, - ["units"] = - { - [1] = - { - ["type"] = "UAZ-469", - ["transportable"] = - { - ["randomTransportable"] = false, - }, -- end of ["transportable"] - ["unitId"] = 10, - ["skill"] = "Average", - ["y"] = 383742.57142857, - ["x"] = 73702.857142857, - ["name"] = "DictKey_UnitName_40", - ["playerCanDrive"] = false, - ["heading"] = 0.24497866312666, - }, -- end of [1] - }, -- end of ["units"] - ["y"] = 383742.57142857, - ["x"] = 73702.857142857, - ["name"] = "DictKey_GroupName_39", - ["start_time"] = 0, - ["task"] = "Ground Nothing", - }, -- end of [3] - [4] = - { - ["visible"] = false, - ["taskSelected"] = true, - ["route"] = - { - ["spans"] = - { - [1] = - { - [1] = - { - ["y"] = 383571.14285714, - ["x"] = 73948.857142857, - }, -- end of [1] - [2] = - { - ["y"] = 383579.71428571, - ["x"] = 73983.142857143, - }, -- end of [2] - }, -- end of [1] - }, -- end of ["spans"] - ["points"] = - { - [1] = - { - ["alt"] = 0, - ["type"] = "Turning Point", - ["ETA"] = 0, - ["alt_type"] = "BARO", - ["formation_template"] = "", - ["y"] = 383642.57142857, - ["x"] = 73965.714285714, - ["name"] = "DictKey_WptName_44", - ["ETA_locked"] = true, - ["speed"] = 5.5555555555556, - ["action"] = "Off Road", - ["task"] = - { - ["id"] = "ComboTask", - ["params"] = - { - ["tasks"] = - { - }, -- end of ["tasks"] - }, -- end of ["params"] - }, -- end of ["task"] - ["speed_locked"] = true, - }, -- end of [1] - }, -- end of ["points"] - }, -- end of ["route"] - ["groupId"] = 11, - ["tasks"] = - { - }, -- end of ["tasks"] - ["hidden"] = false, - ["units"] = - { - [1] = - { - ["type"] = "Ural-4320 APA-5D", - ["transportable"] = - { - ["randomTransportable"] = false, - }, -- end of ["transportable"] - ["unitId"] = 11, - ["skill"] = "Average", - ["y"] = 383642.57142857, - ["x"] = 73965.714285714, - ["name"] = "DictKey_UnitName_43", - ["playerCanDrive"] = false, - ["heading"] = 0.24497866312666, - }, -- end of [1] - }, -- end of ["units"] - ["y"] = 383642.57142857, - ["x"] = 73965.714285714, - ["name"] = "DictKey_GroupName_42", - ["start_time"] = 0, - ["task"] = "Ground Nothing", - }, -- end of [4] - [5] = - { - ["visible"] = false, - ["taskSelected"] = true, - ["route"] = - { - ["spans"] = - { - [1] = - { - [1] = - { - ["y"] = 383571.14285714, - ["x"] = 73948.857142857, - }, -- end of [1] - [2] = - { - ["y"] = 383579.71428571, - ["x"] = 73983.142857143, - }, -- end of [2] - }, -- end of [1] - }, -- end of ["spans"] - ["points"] = - { - [1] = - { - ["alt"] = 0, - ["type"] = "Turning Point", - ["ETA"] = 0, - ["alt_type"] = "BARO", - ["formation_template"] = "", - ["y"] = 383671.14285714, - ["x"] = 73694.285714284, - ["name"] = "DictKey_WptName_47", - ["ETA_locked"] = true, - ["speed"] = 5.5555555555556, - ["action"] = "Off Road", - ["task"] = - { - ["id"] = "ComboTask", - ["params"] = - { - ["tasks"] = - { - }, -- end of ["tasks"] - }, -- end of ["params"] - }, -- end of ["task"] - ["speed_locked"] = true, - }, -- end of [1] - }, -- end of ["points"] - }, -- end of ["route"] - ["groupId"] = 12, - ["tasks"] = - { - }, -- end of ["tasks"] - ["hidden"] = false, - ["units"] = - { - [1] = - { - ["type"] = "Ural-375", - ["transportable"] = - { - ["randomTransportable"] = false, - }, -- end of ["transportable"] - ["unitId"] = 12, - ["skill"] = "Average", - ["y"] = 383671.14285714, - ["x"] = 73694.285714284, - ["name"] = "DictKey_UnitName_46", - ["playerCanDrive"] = false, - ["heading"] = 0.24497866312666, - }, -- end of [1] - }, -- end of ["units"] - ["y"] = 383671.14285714, - ["x"] = 73694.285714284, - ["name"] = "DictKey_GroupName_45", - ["start_time"] = 0, - ["task"] = "Ground Nothing", - }, -- end of [5] - [6] = - { - ["visible"] = false, - ["taskSelected"] = true, - ["route"] = - { - ["spans"] = - { - [1] = - { - [1] = - { - ["y"] = 383571.14285714, - ["x"] = 73948.857142857, - }, -- end of [1] - [2] = - { - ["y"] = 383579.71428571, - ["x"] = 73983.142857143, - }, -- end of [2] - }, -- end of [1] - }, -- end of ["spans"] - ["points"] = - { - [1] = - { - ["alt"] = 0, - ["type"] = "Turning Point", - ["ETA"] = 0, - ["alt_type"] = "BARO", - ["formation_template"] = "", - ["y"] = 383719.71428571, - ["x"] = 73657.142857143, - ["name"] = "DictKey_WptName_50", - ["ETA_locked"] = true, - ["speed"] = 5.5555555555556, - ["action"] = "Off Road", - ["task"] = - { - ["id"] = "ComboTask", - ["params"] = - { - ["tasks"] = - { - }, -- end of ["tasks"] - }, -- end of ["params"] - }, -- end of ["task"] - ["speed_locked"] = true, - }, -- end of [1] - }, -- end of ["points"] - }, -- end of ["route"] - ["groupId"] = 13, - ["tasks"] = - { - }, -- end of ["tasks"] - ["hidden"] = false, - ["units"] = - { - [1] = - { - ["type"] = "ZIL-131 KUNG", - ["transportable"] = - { - ["randomTransportable"] = false, - }, -- end of ["transportable"] - ["unitId"] = 13, - ["skill"] = "Average", - ["y"] = 383719.71428571, - ["x"] = 73657.142857143, - ["name"] = "DictKey_UnitName_49", - ["playerCanDrive"] = false, - ["heading"] = 0.24497866312666, - }, -- end of [1] - }, -- end of ["units"] - ["y"] = 383719.71428571, - ["x"] = 73657.142857143, - ["name"] = "DictKey_GroupName_48", - ["start_time"] = 0, - ["task"] = "Ground Nothing", - }, -- end of [6] - [7] = - { - ["visible"] = false, - ["taskSelected"] = true, - ["route"] = - { - ["spans"] = - { - [1] = - { - [1] = - { - ["y"] = 383571.14285714, - ["x"] = 73948.857142857, - }, -- end of [1] - [2] = - { - ["y"] = 383579.71428571, - ["x"] = 73983.142857143, - }, -- end of [2] - }, -- end of [1] - }, -- end of ["spans"] - ["points"] = - { - [1] = - { - ["alt"] = 0, - ["type"] = "Turning Point", - ["ETA"] = 0, - ["alt_type"] = "BARO", - ["formation_template"] = "", - ["y"] = 384759.71428571, - ["x"] = 73765.714285714, - ["name"] = "DictKey_WptName_53", - ["ETA_locked"] = true, - ["speed"] = 5.5555555555556, - ["action"] = "Off Road", - ["task"] = - { - ["id"] = "ComboTask", - ["params"] = - { - ["tasks"] = - { - }, -- end of ["tasks"] - }, -- end of ["params"] - }, -- end of ["task"] - ["speed_locked"] = true, - }, -- end of [1] - }, -- end of ["points"] - }, -- end of ["route"] - ["groupId"] = 14, - ["tasks"] = - { - }, -- end of ["tasks"] - ["hidden"] = false, - ["units"] = - { - [1] = - { - ["type"] = "ATMZ-5", - ["transportable"] = - { - ["randomTransportable"] = false, - }, -- end of ["transportable"] - ["unitId"] = 14, - ["skill"] = "Average", - ["y"] = 384759.71428571, - ["x"] = 73765.714285714, - ["name"] = "DictKey_UnitName_52", - ["playerCanDrive"] = false, - ["heading"] = 0.24497866312666, - }, -- end of [1] - }, -- end of ["units"] - ["y"] = 384759.71428571, - ["x"] = 73765.714285714, - ["name"] = "DictKey_GroupName_51", - ["start_time"] = 0, - ["task"] = "Ground Nothing", - }, -- end of [7] - [8] = - { - ["visible"] = false, - ["taskSelected"] = true, - ["route"] = - { - ["spans"] = - { - [1] = - { - [1] = - { - ["y"] = 383571.14285714, - ["x"] = 73948.857142857, - }, -- end of [1] - [2] = - { - ["y"] = 383579.71428571, - ["x"] = 73983.142857143, - }, -- end of [2] - }, -- end of [1] - }, -- end of ["spans"] - ["points"] = - { - [1] = - { - ["alt"] = 0, - ["type"] = "Turning Point", - ["ETA"] = 0, - ["alt_type"] = "BARO", - ["formation_template"] = "", - ["y"] = 384759.71428571, - ["x"] = 73688.571428571, - ["name"] = "DictKey_WptName_56", - ["ETA_locked"] = true, - ["speed"] = 5.5555555555556, - ["action"] = "Off Road", - ["task"] = - { - ["id"] = "ComboTask", - ["params"] = - { - ["tasks"] = - { - }, -- end of ["tasks"] - }, -- end of ["params"] - }, -- end of ["task"] - ["speed_locked"] = true, - }, -- end of [1] - }, -- end of ["points"] - }, -- end of ["route"] - ["groupId"] = 15, - ["tasks"] = - { - }, -- end of ["tasks"] - ["hidden"] = false, - ["units"] = - { - [1] = - { - ["type"] = "ATMZ-5", - ["transportable"] = - { - ["randomTransportable"] = false, - }, -- end of ["transportable"] - ["unitId"] = 15, - ["skill"] = "Average", - ["y"] = 384759.71428571, - ["x"] = 73688.571428571, - ["name"] = "DictKey_UnitName_55", - ["playerCanDrive"] = false, - ["heading"] = 0.24497866312666, - }, -- end of [1] - }, -- end of ["units"] - ["y"] = 384759.71428571, - ["x"] = 73688.571428571, - ["name"] = "DictKey_GroupName_54", - ["start_time"] = 0, - ["task"] = "Ground Nothing", - }, -- end of [8] - [9] = - { - ["visible"] = false, - ["taskSelected"] = true, - ["route"] = - { - ["spans"] = - { - [1] = - { - [1] = - { - ["y"] = 383571.14285714, - ["x"] = 73948.857142857, - }, -- end of [1] - [2] = - { - ["y"] = 383579.71428571, - ["x"] = 73983.142857143, - }, -- end of [2] - }, -- end of [1] - }, -- end of ["spans"] - ["points"] = - { - [1] = - { - ["alt"] = 0, - ["type"] = "Turning Point", - ["ETA"] = 0, - ["alt_type"] = "BARO", - ["formation_template"] = "", - ["y"] = 384708.28571428, - ["x"] = 73740, - ["name"] = "DictKey_WptName_59", - ["ETA_locked"] = true, - ["speed"] = 5.5555555555556, - ["action"] = "Off Road", - ["task"] = - { - ["id"] = "ComboTask", - ["params"] = - { - ["tasks"] = - { - }, -- end of ["tasks"] - }, -- end of ["params"] - }, -- end of ["task"] - ["speed_locked"] = true, - }, -- end of [1] - }, -- end of ["points"] - }, -- end of ["route"] - ["groupId"] = 16, - ["tasks"] = - { - }, -- end of ["tasks"] - ["hidden"] = false, - ["units"] = - { - [1] = - { - ["type"] = "ATMZ-5", - ["transportable"] = - { - ["randomTransportable"] = false, - }, -- end of ["transportable"] - ["unitId"] = 16, - ["skill"] = "Average", - ["y"] = 384708.28571428, - ["x"] = 73740, - ["name"] = "DictKey_UnitName_58", - ["playerCanDrive"] = false, - ["heading"] = 0.24497866312666, - }, -- end of [1] - }, -- end of ["units"] - ["y"] = 384708.28571428, - ["x"] = 73740, - ["name"] = "DictKey_GroupName_57", - ["start_time"] = 0, - ["task"] = "Ground Nothing", - }, -- end of [9] - [10] = - { - ["visible"] = false, - ["taskSelected"] = true, - ["route"] = - { - ["spans"] = - { - [13] = - { - [1] = - { - ["y"] = 385185.42857142, - ["x"] = 74371.428571428, - }, -- end of [1] - [2] = - { - ["y"] = 385185.42857142, - ["x"] = 74371.428571428, - }, -- end of [2] - }, -- end of [13] - [7] = - { - [1] = - { - ["y"] = 383054, - ["x"] = 73794.285714285, - }, -- end of [1] - [2] = - { - ["y"] = 382896.85714285, - ["x"] = 74357.142857142, - }, -- end of [2] - }, -- end of [7] - [1] = - { - [1] = - { - ["y"] = 384445.42857142, - ["x"] = 74568.571428571, - }, -- end of [1] - [2] = - { - ["y"] = 384811.14285714, - ["x"] = 74457.142857142, - }, -- end of [2] - }, -- end of [1] - [2] = - { - [1] = - { - ["y"] = 384811.14285714, - ["x"] = 74457.142857142, - }, -- end of [1] - [2] = - { - ["y"] = 384551.14285714, - ["x"] = 73874.285714285, - }, -- end of [2] - }, -- end of [2] - [4] = - { - [1] = - { - ["y"] = 384208.28571428, - ["x"] = 73491.428571428, - }, -- end of [1] - [2] = - { - ["y"] = 383714, - ["x"] = 73379.999999999, - }, -- end of [2] - }, -- end of [4] - [8] = - { - [1] = - { - ["y"] = 382896.85714285, - ["x"] = 74357.142857142, - }, -- end of [1] - [2] = - { - ["y"] = 383034, - ["x"] = 74539.999999999, - }, -- end of [2] - }, -- end of [8] - [9] = - { - [1] = - { - ["y"] = 383034, - ["x"] = 74539.999999999, - }, -- end of [1] - [2] = - { - ["y"] = 384039.71428571, - ["x"] = 74639.999999999, - }, -- end of [2] - }, -- end of [9] - [5] = - { - [1] = - { - ["y"] = 383714, - ["x"] = 73379.999999999, - }, -- end of [1] - [2] = - { - ["y"] = 383388.28571428, - ["x"] = 73439.999999999, - }, -- end of [2] - }, -- end of [5] - [10] = - { - [1] = - { - ["y"] = 384039.71428571, - ["x"] = 74639.999999999, - }, -- end of [1] - [2] = - { - ["y"] = 384751.14285714, - ["x"] = 74659.999999999, - }, -- end of [2] - }, -- end of [10] - [3] = - { - [1] = - { - ["y"] = 384551.14285714, - ["x"] = 73874.285714285, - }, -- end of [1] - [2] = - { - ["y"] = 384208.28571428, - ["x"] = 73491.428571428, - }, -- end of [2] - }, -- end of [3] - [6] = - { - [1] = - { - ["y"] = 383388.28571428, - ["x"] = 73439.999999999, - }, -- end of [1] - [2] = - { - ["y"] = 383054, - ["x"] = 73794.285714285, - }, -- end of [2] - }, -- end of [6] - [12] = - { - [1] = - { - ["y"] = 385134, - ["x"] = 74485.714285713, - }, -- end of [1] - [2] = - { - ["y"] = 386319.71428572, - ["x"] = 74842.857142855, - }, -- end of [2] - }, -- end of [12] - [11] = - { - [1] = - { - ["y"] = 384751.14285714, - ["x"] = 74659.999999999, - }, -- end of [1] - [2] = - { - ["y"] = 385134, - ["x"] = 74485.714285713, - }, -- end of [2] - }, -- end of [11] - }, -- end of ["spans"] - ["points"] = - { - [1] = - { - ["alt"] = 0, - ["type"] = "Turning Point", - ["ETA"] = 0, - ["alt_type"] = "BARO", - ["formation_template"] = "", - ["y"] = 384445.42857142, - ["x"] = 74568.571428571, - ["name"] = "DictKey_WptName_68", - ["ETA_locked"] = true, - ["speed"] = 5.5555555555556, - ["action"] = "Off Road", - ["task"] = - { - ["id"] = "ComboTask", - ["params"] = - { - ["tasks"] = - { - }, -- end of ["tasks"] - }, -- end of ["params"] - }, -- end of ["task"] - ["speed_locked"] = true, - }, -- end of [1] - [2] = - { - ["alt"] = 0, - ["type"] = "Turning Point", - ["ETA"] = 6.3613629652274, - ["alt_type"] = "BARO", - ["formation_template"] = "", - ["y"] = 384811.14285714, - ["x"] = 74457.142857142, - ["name"] = "DictKey_WptName_121", - ["ETA_locked"] = false, - ["speed"] = 5.5555555555556, - ["action"] = "Off Road", - ["task"] = - { - ["id"] = "ComboTask", - ["params"] = - { - ["tasks"] = - { - }, -- end of ["tasks"] - }, -- end of ["params"] - }, -- end of ["task"] - ["speed_locked"] = true, - }, -- end of [2] - [3] = - { - ["alt"] = 0, - ["type"] = "Turning Point", - ["ETA"] = 68.816348319544, - ["alt_type"] = "BARO", - ["formation_template"] = "", - ["y"] = 384551.14285714, - ["x"] = 73874.285714285, - ["name"] = "DictKey_WptName_122", - ["ETA_locked"] = false, - ["speed"] = 5.5555555555556, - ["action"] = "Off Road", - ["task"] = - { - ["id"] = "ComboTask", - ["params"] = - { - ["tasks"] = - { - }, -- end of ["tasks"] - }, -- end of ["params"] - }, -- end of ["task"] - ["speed_locked"] = true, - }, -- end of [3] - [4] = - { - ["alt"] = 0, - ["type"] = "Turning Point", - ["ETA"] = 183.6956212409, - ["alt_type"] = "BARO", - ["formation_template"] = "", - ["y"] = 384208.28571428, - ["x"] = 73491.428571428, - ["name"] = "DictKey_WptName_123", - ["ETA_locked"] = false, - ["speed"] = 5.5555555555556, - ["action"] = "Off Road", - ["task"] = - { - ["id"] = "ComboTask", - ["params"] = - { - ["tasks"] = - { - }, -- end of ["tasks"] - }, -- end of ["params"] - }, -- end of ["task"] - ["speed_locked"] = true, - }, -- end of [4] - [5] = - { - ["alt"] = 0, - ["type"] = "Turning Point", - ["ETA"] = 276.20417131458, - ["alt_type"] = "BARO", - ["formation_template"] = "", - ["y"] = 383714, - ["x"] = 73379.999999999, - ["name"] = "DictKey_WptName_124", - ["ETA_locked"] = false, - ["speed"] = 5.5555555555556, - ["action"] = "Off Road", - ["task"] = - { - ["id"] = "ComboTask", - ["params"] = - { - ["tasks"] = - { - }, -- end of ["tasks"] - }, -- end of ["params"] - }, -- end of ["task"] - ["speed_locked"] = true, - }, -- end of [5] - [6] = - { - ["alt"] = 0, - ["type"] = "Turning Point", - ["ETA"] = 367.40836026242, - ["alt_type"] = "BARO", - ["formation_template"] = "", - ["y"] = 383388.28571428, - ["x"] = 73439.999999999, - ["name"] = "DictKey_WptName_125", - ["ETA_locked"] = false, - ["speed"] = 5.5555555555556, - ["action"] = "Off Road", - ["task"] = - { - ["id"] = "ComboTask", - ["params"] = - { - ["tasks"] = - { - }, -- end of ["tasks"] - }, -- end of ["params"] - }, -- end of ["task"] - ["speed_locked"] = true, - }, -- end of [6] - [7] = - { - ["alt"] = 0, - ["type"] = "Turning Point", - ["ETA"] = 427.02337001462, - ["alt_type"] = "BARO", - ["formation_template"] = "", - ["y"] = 383054, - ["x"] = 73794.285714285, - ["name"] = "DictKey_WptName_126", - ["ETA_locked"] = false, - ["speed"] = 5.5555555555556, - ["action"] = "Off Road", - ["task"] = - { - ["id"] = "ComboTask", - ["params"] = - { - ["tasks"] = - { - }, -- end of ["tasks"] - }, -- end of ["params"] - }, -- end of ["task"] - ["speed_locked"] = true, - }, -- end of [7] - [8] = - { - ["alt"] = 0, - ["type"] = "Turning Point", - ["ETA"] = 514.70116605425, - ["alt_type"] = "BARO", - ["formation_template"] = "", - ["y"] = 382896.85714285, - ["x"] = 74357.142857142, - ["name"] = "DictKey_WptName_127", - ["ETA_locked"] = false, - ["speed"] = 5.5555555555556, - ["action"] = "Off Road", - ["task"] = - { - ["id"] = "ComboTask", - ["params"] = - { - ["tasks"] = - { - }, -- end of ["tasks"] - }, -- end of ["params"] - }, -- end of ["task"] - ["speed_locked"] = true, - }, -- end of [8] - [9] = - { - ["alt"] = 0, - ["type"] = "Turning Point", - ["ETA"] = 619.88988276117, - ["alt_type"] = "BARO", - ["formation_template"] = "", - ["y"] = 383034, - ["x"] = 74539.999999999, - ["name"] = "DictKey_WptName_128", - ["ETA_locked"] = false, - ["speed"] = 5.5555555555556, - ["action"] = "Off Road", - ["task"] = - { - ["id"] = "ComboTask", - ["params"] = - { - ["tasks"] = - { - }, -- end of ["tasks"] - }, -- end of ["params"] - }, -- end of ["task"] - ["speed_locked"] = true, - }, -- end of [9] - [10] = - { - ["alt"] = 0, - ["type"] = "Turning Point", - ["ETA"] = 661.03273990402, - ["alt_type"] = "BARO", - ["formation_template"] = "", - ["y"] = 384039.71428571, - ["x"] = 74639.999999999, - ["name"] = "DictKey_WptName_129", - ["ETA_locked"] = false, - ["speed"] = 5.5555555555556, - ["action"] = "Off Road", - ["task"] = - { - ["id"] = "ComboTask", - ["params"] = - { - ["tasks"] = - { - }, -- end of ["tasks"] - }, -- end of ["params"] - }, -- end of ["task"] - ["speed_locked"] = true, - }, -- end of [10] - [11] = - { - ["alt"] = 0, - ["type"] = "Turning Point", - ["ETA"] = 842.95399669792, - ["alt_type"] = "BARO", - ["formation_template"] = "", - ["y"] = 384751.14285714, - ["x"] = 74659.999999999, - ["name"] = "DictKey_WptName_130", - ["ETA_locked"] = false, - ["speed"] = 5.5555555555556, - ["action"] = "Off Road", - ["task"] = - { - ["id"] = "ComboTask", - ["params"] = - { - ["tasks"] = - { - }, -- end of ["tasks"] - }, -- end of ["params"] - }, -- end of ["task"] - ["speed_locked"] = true, - }, -- end of [11] - [12] = - { - ["alt"] = 0, - ["type"] = "Turning Point", - ["ETA"] = 971.06173197076, - ["alt_type"] = "BARO", - ["formation_template"] = "", - ["y"] = 385134, - ["x"] = 74485.714285713, - ["name"] = "DictKey_WptName_131", - ["ETA_locked"] = false, - ["speed"] = 5.5555555555556, - ["action"] = "Off Road", - ["task"] = - { - ["id"] = "ComboTask", - ["params"] = - { - ["tasks"] = - { - }, -- end of ["tasks"] - }, -- end of ["params"] - }, -- end of ["task"] - ["speed_locked"] = true, - }, -- end of [12] - [13] = - { - ["alt"] = 0, - ["type"] = "Turning Point", - ["ETA"] = 1269.6805848567, - ["alt_type"] = "BARO", - ["formation_template"] = "", - ["y"] = 386319.71428572, - ["x"] = 74842.857142855, - ["name"] = "DictKey_WptName_132", - ["ETA_locked"] = false, - ["speed"] = 5.5555555555556, - ["action"] = "Off Road", - ["task"] = - { - ["id"] = "ComboTask", - ["params"] = - { - ["tasks"] = - { - }, -- end of ["tasks"] - }, -- end of ["params"] - }, -- end of ["task"] - ["speed_locked"] = true, - }, -- end of [13] - }, -- end of ["points"] - }, -- end of ["route"] - ["groupId"] = 19, - ["tasks"] = - { - }, -- end of ["tasks"] - ["hidden"] = false, - ["units"] = - { - [1] = - { - ["type"] = "GAZ-3308", - ["transportable"] = - { - ["randomTransportable"] = false, - }, -- end of ["transportable"] - ["unitId"] = 19, - ["skill"] = "Average", - ["y"] = 384445.42857142, - ["x"] = 74568.571428571, - ["name"] = "DictKey_UnitName_67", - ["playerCanDrive"] = false, - ["heading"] = 1.8665480125421, - }, -- end of [1] - }, -- end of ["units"] - ["y"] = 384445.42857142, - ["x"] = 74568.571428571, - ["name"] = "DictKey_GroupName_66", - ["start_time"] = 0, - ["task"] = "Ground Nothing", - }, -- end of [10] - [11] = - { - ["visible"] = false, - ["taskSelected"] = true, - ["route"] = - { - ["spans"] = - { - [1] = - { - [1] = - { - ["y"] = 383571.14285714, - ["x"] = 73948.857142857, - }, -- end of [1] - [2] = - { - ["y"] = 383579.71428571, - ["x"] = 73983.142857143, - }, -- end of [2] - }, -- end of [1] - }, -- end of ["spans"] - ["points"] = - { - [1] = - { - ["alt"] = 0, - ["type"] = "Turning Point", - ["ETA"] = 0, - ["alt_type"] = "BARO", - ["formation_template"] = "", - ["y"] = 384868.28571428, - ["x"] = 74394.285714285, - ["name"] = "DictKey_WptName_74", - ["ETA_locked"] = true, - ["speed"] = 5.5555555555556, - ["action"] = "Off Road", - ["task"] = - { - ["id"] = "ComboTask", - ["params"] = - { - ["tasks"] = - { - }, -- end of ["tasks"] - }, -- end of ["params"] - }, -- end of ["task"] - ["speed_locked"] = true, - }, -- end of [1] - }, -- end of ["points"] - }, -- end of ["route"] - ["groupId"] = 21, - ["tasks"] = - { - }, -- end of ["tasks"] - ["hidden"] = false, - ["units"] = - { - [1] = - { - ["type"] = "ATMZ-5", - ["transportable"] = - { - ["randomTransportable"] = false, - }, -- end of ["transportable"] - ["unitId"] = 21, - ["skill"] = "Average", - ["y"] = 384868.28571428, - ["x"] = 74394.285714285, - ["name"] = "DictKey_UnitName_73", - ["playerCanDrive"] = false, - ["heading"] = 0.24497866312666, - }, -- end of [1] - }, -- end of ["units"] - ["y"] = 384868.28571428, - ["x"] = 74394.285714285, - ["name"] = "DictKey_GroupName_72", - ["start_time"] = 0, - ["task"] = "Ground Nothing", - }, -- end of [11] - [12] = - { - ["visible"] = false, - ["taskSelected"] = true, - ["route"] = - { - ["spans"] = - { - [1] = - { - [1] = - { - ["y"] = 381868.28571428, - ["x"] = 74005.714285713, - }, -- end of [1] - [2] = - { - ["y"] = 386716.85714286, - ["x"] = 73777.142857141, - }, -- end of [2] - }, -- end of [1] - [2] = - { - [1] = - { - ["y"] = 384242.57142857, - ["x"] = 74808.57142857, - }, -- end of [1] - [2] = - { - ["y"] = 384242.57142857, - ["x"] = 74808.57142857, - }, -- end of [2] - }, -- end of [2] - }, -- end of ["spans"] - ["points"] = - { - [1] = - { - ["alt"] = 0, - ["type"] = "Turning Point", - ["ETA"] = 0, - ["alt_type"] = "BARO", - ["formation_template"] = "", - ["y"] = 381868.28571428, - ["x"] = 74005.714285713, - ["name"] = "DictKey_WptName_80", - ["ETA_locked"] = true, - ["speed"] = 5.5555555555556, - ["action"] = "Off Road", - ["task"] = - { - ["id"] = "ComboTask", - ["params"] = - { - ["tasks"] = - { - }, -- end of ["tasks"] - }, -- end of ["params"] - }, -- end of ["task"] - ["speed_locked"] = true, - }, -- end of [1] - [2] = - { - ["alt"] = 0, - ["type"] = "Turning Point", - ["ETA"] = 873.71209754012, - ["alt_type"] = "BARO", - ["formation_template"] = "", - ["y"] = 386716.85714286, - ["x"] = 73777.142857141, - ["name"] = "DictKey_WptName_139", - ["ETA_locked"] = false, - ["speed"] = 5.5555555555556, - ["action"] = "Off Road", - ["task"] = - { - ["id"] = "ComboTask", - ["params"] = - { - ["tasks"] = - { - }, -- end of ["tasks"] - }, -- end of ["params"] - }, -- end of ["task"] - ["speed_locked"] = true, - }, -- end of [2] - }, -- end of ["points"] - }, -- end of ["route"] - ["groupId"] = 23, - ["tasks"] = - { - }, -- end of ["tasks"] - ["hidden"] = false, - ["units"] = - { - [1] = - { - ["type"] = "GAZ-3307", - ["transportable"] = - { - ["randomTransportable"] = false, - }, -- end of ["transportable"] - ["unitId"] = 23, - ["skill"] = "Average", - ["y"] = 381868.28571428, - ["x"] = 74005.714285713, - ["name"] = "DictKey_UnitName_79", - ["playerCanDrive"] = false, - ["heading"] = 1.6179034662811, - }, -- end of [1] - }, -- end of ["units"] - ["y"] = 381868.28571428, - ["x"] = 74005.714285713, - ["name"] = "DictKey_GroupName_78", - ["start_time"] = 0, - ["task"] = "Ground Nothing", - }, -- end of [12] - [13] = - { - ["visible"] = false, - ["taskSelected"] = true, - ["route"] = - { - ["spans"] = - { - [7] = - { - [1] = - { - ["y"] = 383065.42857142, - ["x"] = 74622.857142856, - }, -- end of [1] - [2] = - { - ["y"] = 383854, - ["x"] = 74699.999999999, - }, -- end of [2] - }, -- end of [7] - [1] = - { - [1] = - { - ["y"] = 383708.28571428, - ["x"] = 74474.285714285, - }, -- end of [1] - [2] = - { - ["y"] = 385371.14285714, - ["x"] = 73797.142857142, - }, -- end of [2] - }, -- end of [1] - [2] = - { - [1] = - { - ["y"] = 385371.14285714, - ["x"] = 73797.142857142, - }, -- end of [1] - [2] = - { - ["y"] = 384982.57142857, - ["x"] = 74179.999999999, - }, -- end of [2] - }, -- end of [2] - [4] = - { - [1] = - { - ["y"] = 384314, - ["x"] = 74074.285714285, - }, -- end of [1] - [2] = - { - ["y"] = 383714, - ["x"] = 73871.428571428, - }, -- end of [2] - }, -- end of [4] - [8] = - { - [1] = - { - ["y"] = 383854, - ["x"] = 74699.999999999, - }, -- end of [1] - [2] = - { - ["y"] = 384016.85714285, - ["x"] = 74125.714285713, - }, -- end of [2] - }, -- end of [8] - [9] = - { - [1] = - { - ["y"] = 384016.85714285, - ["x"] = 74125.714285713, - }, -- end of [1] - [2] = - { - ["y"] = 384139.71428571, - ["x"] = 73745.714285713, - }, -- end of [2] - }, -- end of [9] - [5] = - { - [1] = - { - ["y"] = 383714, - ["x"] = 73871.428571428, - }, -- end of [1] - [2] = - { - ["y"] = 383102.57142857, - ["x"] = 74011.428571428, - }, -- end of [2] - }, -- end of [5] - [10] = - { - [1] = - { - ["y"] = 384139.71428571, - ["x"] = 73745.714285713, - }, -- end of [1] - [2] = - { - ["y"] = 384594, - ["x"] = 73497.142857142, - }, -- end of [2] - }, -- end of [10] - [3] = - { - [1] = - { - ["y"] = 384982.57142857, - ["x"] = 74179.999999999, - }, -- end of [1] - [2] = - { - ["y"] = 384314, - ["x"] = 74074.285714285, - }, -- end of [2] - }, -- end of [3] - [6] = - { - [1] = - { - ["y"] = 383102.57142857, - ["x"] = 74011.428571428, - }, -- end of [1] - [2] = - { - ["y"] = 383065.42857142, - ["x"] = 74622.857142856, - }, -- end of [2] - }, -- end of [6] - [12] = - { - [1] = - { - ["y"] = 385005.42857142, - ["x"] = 73502.857142856, - }, -- end of [1] - [2] = - { - ["y"] = 385005.42857142, - ["x"] = 73502.857142856, - }, -- end of [2] - }, -- end of [12] - [11] = - { - [1] = - { - ["y"] = 384594, - ["x"] = 73497.142857142, - }, -- end of [1] - [2] = - { - ["y"] = 385005.42857142, - ["x"] = 73502.857142856, - }, -- end of [2] - }, -- end of [11] - }, -- end of ["spans"] - ["points"] = - { - [1] = - { - ["alt"] = 0, - ["type"] = "Turning Point", - ["ETA"] = 0, - ["alt_type"] = "BARO", - ["formation_template"] = "", - ["y"] = 383708.28571428, - ["x"] = 74474.285714285, - ["name"] = "DictKey_WptName_95", - ["ETA_locked"] = true, - ["speed"] = 5.5555555555556, - ["action"] = "Off Road", - ["task"] = - { - ["id"] = "ComboTask", - ["params"] = - { - ["tasks"] = - { - }, -- end of ["tasks"] - }, -- end of ["params"] - }, -- end of ["task"] - ["speed_locked"] = true, - }, -- end of [1] - [2] = - { - ["alt"] = 0, - ["type"] = "Turning Point", - ["ETA"] = 6.3613629652274, - ["alt_type"] = "BARO", - ["formation_template"] = "", - ["y"] = 385371.14285714, - ["x"] = 73797.142857142, - ["name"] = "DictKey_WptName_110", - ["ETA_locked"] = false, - ["speed"] = 5.5555555555556, - ["action"] = "Off Road", - ["task"] = - { - ["id"] = "ComboTask", - ["params"] = - { - ["tasks"] = - { - }, -- end of ["tasks"] - }, -- end of ["params"] - }, -- end of ["task"] - ["speed_locked"] = true, - }, -- end of [2] - [3] = - { - ["alt"] = 0, - ["type"] = "Turning Point", - ["ETA"] = 323.17977811065, - ["alt_type"] = "BARO", - ["formation_template"] = "", - ["y"] = 384982.57142857, - ["x"] = 74179.999999999, - ["name"] = "DictKey_WptName_111", - ["ETA_locked"] = false, - ["speed"] = 5.5555555555556, - ["action"] = "Off Road", - ["task"] = - { - ["id"] = "ComboTask", - ["params"] = - { - ["tasks"] = - { - }, -- end of ["tasks"] - }, -- end of ["params"] - }, -- end of ["task"] - ["speed_locked"] = true, - }, -- end of [3] - [4] = - { - ["alt"] = 0, - ["type"] = "Turning Point", - ["ETA"] = 421.36929914429, - ["alt_type"] = "BARO", - ["formation_template"] = "", - ["y"] = 384314, - ["x"] = 74074.285714285, - ["name"] = "DictKey_WptName_112", - ["ETA_locked"] = false, - ["speed"] = 5.5555555555556, - ["action"] = "Off Road", - ["task"] = - { - ["id"] = "ComboTask", - ["params"] = - { - ["tasks"] = - { - }, -- end of ["tasks"] - }, -- end of ["params"] - }, -- end of ["task"] - ["speed_locked"] = true, - }, -- end of [4] - [5] = - { - ["alt"] = 0, - ["type"] = "Turning Point", - ["ETA"] = 543.20726447497, - ["alt_type"] = "BARO", - ["formation_template"] = "", - ["y"] = 383714, - ["x"] = 73871.428571428, - ["name"] = "DictKey_WptName_113", - ["ETA_locked"] = false, - ["speed"] = 5.5555555555556, - ["action"] = "Off Road", - ["task"] = - { - ["id"] = "ComboTask", - ["params"] = - { - ["tasks"] = - { - }, -- end of ["tasks"] - }, -- end of ["params"] - }, -- end of ["task"] - ["speed_locked"] = true, - }, -- end of [5] - [6] = - { - ["alt"] = 0, - ["type"] = "Turning Point", - ["ETA"] = 657.21293565506, - ["alt_type"] = "BARO", - ["formation_template"] = "", - ["y"] = 383102.57142857, - ["x"] = 74011.428571428, - ["name"] = "DictKey_WptName_114", - ["ETA_locked"] = false, - ["speed"] = 5.5555555555556, - ["action"] = "Off Road", - ["task"] = - { - ["id"] = "ComboTask", - ["params"] = - { - ["tasks"] = - { - }, -- end of ["tasks"] - }, -- end of ["params"] - }, -- end of ["task"] - ["speed_locked"] = true, - }, -- end of [6] - [7] = - { - ["alt"] = 0, - ["type"] = "Turning Point", - ["ETA"] = 770.11827075433, - ["alt_type"] = "BARO", - ["formation_template"] = "", - ["y"] = 383065.42857142, - ["x"] = 74622.857142856, - ["name"] = "DictKey_WptName_115", - ["ETA_locked"] = false, - ["speed"] = 5.5555555555556, - ["action"] = "Off Road", - ["task"] = - { - ["id"] = "ComboTask", - ["params"] = - { - ["tasks"] = - { - }, -- end of ["tasks"] - }, -- end of ["params"] - }, -- end of ["task"] - ["speed_locked"] = true, - }, -- end of [7] - [8] = - { - ["alt"] = 0, - ["type"] = "Turning Point", - ["ETA"] = 880.37829737045, - ["alt_type"] = "BARO", - ["formation_template"] = "", - ["y"] = 383854, - ["x"] = 74699.999999999, - ["name"] = "DictKey_WptName_116", - ["ETA_locked"] = false, - ["speed"] = 5.5555555555556, - ["action"] = "Off Road", - ["task"] = - { - ["id"] = "ComboTask", - ["params"] = - { - ["tasks"] = - { - }, -- end of ["tasks"] - }, -- end of ["params"] - }, -- end of ["task"] - ["speed_locked"] = true, - }, -- end of [8] - [9] = - { - ["alt"] = 0, - ["type"] = "Turning Point", - ["ETA"] = 1022.9987298305, - ["alt_type"] = "BARO", - ["formation_template"] = "", - ["y"] = 384016.85714285, - ["x"] = 74125.714285713, - ["name"] = "DictKey_WptName_117", - ["ETA_locked"] = false, - ["speed"] = 5.5555555555556, - ["action"] = "Off Road", - ["task"] = - { - ["id"] = "ComboTask", - ["params"] = - { - ["tasks"] = - { - }, -- end of ["tasks"] - }, -- end of ["params"] - }, -- end of ["task"] - ["speed_locked"] = true, - }, -- end of [9] - [10] = - { - ["alt"] = 0, - ["type"] = "Turning Point", - ["ETA"] = 1130.4462965402, - ["alt_type"] = "BARO", - ["formation_template"] = "", - ["y"] = 384139.71428571, - ["x"] = 73745.714285713, - ["name"] = "DictKey_WptName_118", - ["ETA_locked"] = false, - ["speed"] = 5.5555555555556, - ["action"] = "Off Road", - ["task"] = - { - ["id"] = "ComboTask", - ["params"] = - { - ["tasks"] = - { - }, -- end of ["tasks"] - }, -- end of ["params"] - }, -- end of ["task"] - ["speed_locked"] = true, - }, -- end of [10] - [11] = - { - ["alt"] = 0, - ["type"] = "Turning Point", - ["ETA"] = 1202.3323287903, - ["alt_type"] = "BARO", - ["formation_template"] = "", - ["y"] = 384594, - ["x"] = 73497.142857142, - ["name"] = "DictKey_WptName_119", - ["ETA_locked"] = false, - ["speed"] = 5.5555555555556, - ["action"] = "Off Road", - ["task"] = - { - ["id"] = "ComboTask", - ["params"] = - { - ["tasks"] = - { - }, -- end of ["tasks"] - }, -- end of ["params"] - }, -- end of ["task"] - ["speed_locked"] = true, - }, -- end of [11] - [12] = - { - ["alt"] = 0, - ["type"] = "Turning Point", - ["ETA"] = 1295.5443977294, - ["alt_type"] = "BARO", - ["formation_template"] = "", - ["y"] = 385005.42857142, - ["x"] = 73502.857142856, - ["name"] = "DictKey_WptName_120", - ["ETA_locked"] = false, - ["speed"] = 5.5555555555556, - ["action"] = "Off Road", - ["task"] = - { - ["id"] = "ComboTask", - ["params"] = - { - ["tasks"] = - { - }, -- end of ["tasks"] - }, -- end of ["params"] - }, -- end of ["task"] - ["speed_locked"] = true, - }, -- end of [12] - }, -- end of ["points"] - }, -- end of ["route"] - ["groupId"] = 28, - ["tasks"] = - { - }, -- end of ["tasks"] - ["hidden"] = false, - ["units"] = - { - [1] = - { - ["type"] = "ATMZ-5", - ["transportable"] = - { - ["randomTransportable"] = false, - }, -- end of ["transportable"] - ["unitId"] = 28, - ["skill"] = "Average", - ["y"] = 383708.28571428, - ["x"] = 74474.285714285, - ["name"] = "DictKey_UnitName_94", - ["playerCanDrive"] = false, - ["heading"] = 1.9575082980065, - }, -- end of [1] - }, -- end of ["units"] - ["y"] = 383708.28571428, - ["x"] = 74474.285714285, - ["name"] = "DictKey_GroupName_93", - ["start_time"] = 0, - ["task"] = "Ground Nothing", - }, -- end of [13] - [14] = - { - ["visible"] = false, - ["taskSelected"] = true, - ["route"] = - { - ["spans"] = - { - [1] = - { - [1] = - { - ["y"] = 383571.14285714, - ["x"] = 73948.857142857, - }, -- end of [1] - [2] = - { - ["y"] = 383579.71428571, - ["x"] = 73983.142857143, - }, -- end of [2] - }, -- end of [1] - }, -- end of ["spans"] - ["points"] = - { - [1] = - { - ["alt"] = 0, - ["type"] = "Turning Point", - ["ETA"] = 0, - ["alt_type"] = "BARO", - ["formation_template"] = "", - ["y"] = 383796.85714285, - ["x"] = 74505.714285714, - ["name"] = "DictKey_WptName_98", - ["ETA_locked"] = true, - ["speed"] = 5.5555555555556, - ["action"] = "Off Road", - ["task"] = - { - ["id"] = "ComboTask", - ["params"] = - { - ["tasks"] = - { - }, -- end of ["tasks"] - }, -- end of ["params"] - }, -- end of ["task"] - ["speed_locked"] = true, - }, -- end of [1] - }, -- end of ["points"] - }, -- end of ["route"] - ["groupId"] = 29, - ["tasks"] = - { - }, -- end of ["tasks"] - ["hidden"] = false, - ["units"] = - { - [1] = - { - ["type"] = "ATMZ-5", - ["transportable"] = - { - ["randomTransportable"] = false, - }, -- end of ["transportable"] - ["unitId"] = 29, - ["skill"] = "Average", - ["y"] = 383796.85714285, - ["x"] = 74505.714285714, - ["name"] = "DictKey_UnitName_97", - ["playerCanDrive"] = false, - ["heading"] = 0.24497866312666, - }, -- end of [1] - }, -- end of ["units"] - ["y"] = 383796.85714285, - ["x"] = 74505.714285714, - ["name"] = "DictKey_GroupName_96", - ["start_time"] = 0, - ["task"] = "Ground Nothing", - }, -- end of [14] - [15] = - { - ["visible"] = false, - ["taskSelected"] = true, - ["route"] = - { - ["spans"] = - { - [1] = - { - [1] = - { - ["y"] = 383571.14285714, - ["x"] = 73948.857142857, - }, -- end of [1] - [2] = - { - ["y"] = 383579.71428571, - ["x"] = 73983.142857143, - }, -- end of [2] - }, -- end of [1] - }, -- end of ["spans"] - ["points"] = - { - [1] = - { - ["alt"] = 0, - ["type"] = "Turning Point", - ["ETA"] = 0, - ["alt_type"] = "BARO", - ["formation_template"] = "", - ["y"] = 383756.85714285, - ["x"] = 74551.428571428, - ["name"] = "DictKey_WptName_101", - ["ETA_locked"] = true, - ["speed"] = 5.5555555555556, - ["action"] = "Off Road", - ["task"] = - { - ["id"] = "ComboTask", - ["params"] = - { - ["tasks"] = - { - }, -- end of ["tasks"] - }, -- end of ["params"] - }, -- end of ["task"] - ["speed_locked"] = true, - }, -- end of [1] - }, -- end of ["points"] - }, -- end of ["route"] - ["groupId"] = 30, - ["tasks"] = - { - }, -- end of ["tasks"] - ["hidden"] = false, - ["units"] = - { - [1] = - { - ["type"] = "ATMZ-5", - ["transportable"] = - { - ["randomTransportable"] = false, - }, -- end of ["transportable"] - ["unitId"] = 30, - ["skill"] = "Average", - ["y"] = 383756.85714285, - ["x"] = 74551.428571428, - ["name"] = "DictKey_UnitName_100", - ["playerCanDrive"] = false, - ["heading"] = 0.24497866312666, - }, -- end of [1] - }, -- end of ["units"] - ["y"] = 383756.85714285, - ["x"] = 74551.428571428, - ["name"] = "DictKey_GroupName_99", - ["start_time"] = 0, - ["task"] = "Ground Nothing", - }, -- end of [15] - [16] = - { - ["visible"] = false, - ["taskSelected"] = true, - ["route"] = - { - ["spans"] = - { - [2] = - { - [1] = - { - ["y"] = 385691.14285715, - ["x"] = 74085.714285712, - }, -- end of [1] - [2] = - { - ["y"] = 385691.14285715, - ["x"] = 74085.714285712, - }, -- end of [2] - }, -- end of [2] - [1] = - { - [1] = - { - ["y"] = 381912.57142857, - ["x"] = 74635.714285712, - }, -- end of [1] - [2] = - { - ["y"] = 385691.14285715, - ["x"] = 74085.714285712, - }, -- end of [2] - }, -- end of [1] - }, -- end of ["spans"] - ["points"] = - { - [1] = - { - ["alt"] = 0, - ["type"] = "Turning Point", - ["ETA"] = 0, - ["alt_type"] = "BARO", - ["formation_template"] = "", - ["y"] = 381912.57142857, - ["x"] = 74635.714285712, - ["name"] = "DictKey_WptName_156", - ["ETA_locked"] = true, - ["speed"] = 5.5555555555556, - ["action"] = "Off Road", - ["task"] = - { - ["id"] = "ComboTask", - ["params"] = - { - ["tasks"] = - { - }, -- end of ["tasks"] - }, -- end of ["params"] - }, -- end of ["task"] - ["speed_locked"] = true, - }, -- end of [1] - [2] = - { - ["alt"] = 0, - ["type"] = "Turning Point", - ["ETA"] = 763.30343927398, - ["alt_type"] = "BARO", - ["formation_template"] = "", - ["y"] = 385691.14285715, - ["x"] = 74085.714285712, - ["name"] = "DictKey_WptName_217", - ["ETA_locked"] = false, - ["speed"] = 5.5555555555556, - ["action"] = "Off Road", - ["task"] = - { - ["id"] = "ComboTask", - ["params"] = - { - ["tasks"] = - { - }, -- end of ["tasks"] - }, -- end of ["params"] - }, -- end of ["task"] - ["speed_locked"] = true, - }, -- end of [2] - }, -- end of ["points"] - }, -- end of ["route"] - ["groupId"] = 32, - ["tasks"] = - { - }, -- end of ["tasks"] - ["hidden"] = false, - ["units"] = - { - [1] = - { - ["type"] = "Ural-4320 APA-5D", - ["transportable"] = - { - ["randomTransportable"] = false, - }, -- end of ["transportable"] - ["unitId"] = 32, - ["skill"] = "Average", - ["y"] = 381912.57142857, - ["x"] = 74635.714285712, - ["name"] = "DictKey_UnitName_155", - ["playerCanDrive"] = false, - ["heading"] = 1.7153388778726, - }, -- end of [1] - }, -- end of ["units"] - ["y"] = 381912.57142857, - ["x"] = 74635.714285712, - ["name"] = "DictKey_GroupName_154", - ["start_time"] = 0, - ["task"] = "Ground Nothing", - }, -- end of [16] - [17] = - { - ["visible"] = false, - ["taskSelected"] = true, - ["route"] = - { - ["spans"] = - { - [1] = - { - [1] = - { - ["y"] = 386048.28571429, - ["x"] = 75049.999999998, - }, -- end of [1] - [2] = - { - ["y"] = 381948.28571429, - ["x"] = 72807.142857141, - }, -- end of [2] - }, -- end of [1] - [2] = - { - [1] = - { - ["y"] = 388646.85714287, - ["x"] = 73799.999999998, - }, -- end of [1] - [2] = - { - ["y"] = 388646.85714287, - ["x"] = 73799.999999998, - }, -- end of [2] - }, -- end of [2] - }, -- end of ["spans"] - ["points"] = - { - [1] = - { - ["alt"] = 0, - ["type"] = "Turning Point", - ["ETA"] = 0, - ["alt_type"] = "BARO", - ["formation_template"] = "", - ["y"] = 386048.28571429, - ["x"] = 75049.999999998, - ["name"] = "DictKey_WptName_220", - ["ETA_locked"] = true, - ["speed"] = 5.5555555555556, - ["action"] = "Off Road", - ["task"] = - { - ["id"] = "ComboTask", - ["params"] = - { - ["tasks"] = - { - }, -- end of ["tasks"] - }, -- end of ["params"] - }, -- end of ["task"] - ["speed_locked"] = true, - }, -- end of [1] - [2] = - { - ["alt"] = 0, - ["type"] = "Turning Point", - ["ETA"] = 841.20700454161, - ["alt_type"] = "BARO", - ["formation_template"] = "", - ["y"] = 381948.28571429, - ["x"] = 72807.142857141, - ["name"] = "DictKey_WptName_221", - ["ETA_locked"] = false, - ["speed"] = 5.5555555555556, - ["action"] = "Off Road", - ["task"] = - { - ["id"] = "ComboTask", - ["params"] = - { - ["tasks"] = - { - }, -- end of ["tasks"] - }, -- end of ["params"] - }, -- end of ["task"] - ["speed_locked"] = true, - }, -- end of [2] - }, -- end of ["points"] - }, -- end of ["route"] - ["groupId"] = 41, - ["tasks"] = - { - }, -- end of ["tasks"] - ["hidden"] = false, - ["units"] = - { - [1] = - { - ["type"] = "GAZ-3307", - ["transportable"] = - { - ["randomTransportable"] = false, - }, -- end of ["transportable"] - ["unitId"] = 41, - ["skill"] = "Average", - ["y"] = 386048.28571429, - ["x"] = 75049.999999998, - ["name"] = "DictKey_UnitName_219", - ["playerCanDrive"] = false, - ["heading"] = -2.0713628575797, - }, -- end of [1] - }, -- end of ["units"] - ["y"] = 386048.28571429, - ["x"] = 75049.999999998, - ["name"] = "DictKey_GroupName_218", - ["start_time"] = 0, - ["task"] = "Ground Nothing", - }, -- end of [17] - [18] = - { - ["visible"] = false, - ["taskSelected"] = true, - ["route"] = - { - ["spans"] = - { - [1] = - { - [1] = - { - ["y"] = 386019.71428572, - ["x"] = 73507.142857141, - }, -- end of [1] - [2] = - { - ["y"] = 382805.42857143, - ["x"] = 75099.999999998, - }, -- end of [2] - }, -- end of [1] - [2] = - { - [1] = - { - ["y"] = 380112.57142857, - ["x"] = 70457.142857141, - }, -- end of [1] - [2] = - { - ["y"] = 380112.57142857, - ["x"] = 70457.142857141, - }, -- end of [2] - }, -- end of [2] - }, -- end of ["spans"] - ["points"] = - { - [1] = - { - ["alt"] = 0, - ["type"] = "Turning Point", - ["ETA"] = 0, - ["alt_type"] = "BARO", - ["formation_template"] = "", - ["y"] = 386019.71428572, - ["x"] = 73507.142857141, - ["name"] = "DictKey_WptName_224", - ["ETA_locked"] = true, - ["speed"] = 5.5555555555556, - ["action"] = "Off Road", - ["task"] = - { - ["id"] = "ComboTask", - ["params"] = - { - ["tasks"] = - { - }, -- end of ["tasks"] - }, -- end of ["params"] - }, -- end of ["task"] - ["speed_locked"] = true, - }, -- end of [1] - [2] = - { - ["alt"] = 0, - ["type"] = "Turning Point", - ["ETA"] = 645.7166403244, - ["alt_type"] = "BARO", - ["formation_template"] = "", - ["y"] = 382805.42857143, - ["x"] = 75099.999999998, - ["name"] = "DictKey_WptName_225", - ["ETA_locked"] = false, - ["speed"] = 5.5555555555556, - ["action"] = "Off Road", - ["task"] = - { - ["id"] = "ComboTask", - ["params"] = - { - ["tasks"] = - { - }, -- end of ["tasks"] - }, -- end of ["params"] - }, -- end of ["task"] - ["speed_locked"] = true, - }, -- end of [2] - }, -- end of ["points"] - }, -- end of ["route"] - ["groupId"] = 42, - ["tasks"] = - { - }, -- end of ["tasks"] - ["hidden"] = false, - ["units"] = - { - [1] = - { - ["type"] = "GAZ-3307", - ["transportable"] = - { - ["randomTransportable"] = false, - }, -- end of ["transportable"] - ["unitId"] = 42, - ["skill"] = "Average", - ["y"] = 386019.71428572, - ["x"] = 73507.142857141, - ["name"] = "DictKey_UnitName_223", - ["playerCanDrive"] = false, - ["heading"] = -1.110710590532, - }, -- end of [1] - }, -- end of ["units"] - ["y"] = 386019.71428572, - ["x"] = 73507.142857141, - ["name"] = "DictKey_GroupName_222", - ["start_time"] = 0, - ["task"] = "Ground Nothing", - }, -- end of [18] - [19] = - { - ["visible"] = false, - ["taskSelected"] = true, - ["route"] = - { - ["spans"] = - { - [1] = - { - [1] = - { - ["y"] = 382505.42857143, - ["x"] = 75757.142857141, - }, -- end of [1] - [2] = - { - ["y"] = 384891.14285714, - ["x"] = 73121.428571427, - }, -- end of [2] - }, -- end of [1] - [2] = - { - [1] = - { - ["y"] = 384891.14285714, - ["x"] = 73121.428571427, - }, -- end of [1] - [2] = - { - ["y"] = 384891.14285714, - ["x"] = 73121.428571427, - }, -- end of [2] - }, -- end of [2] - }, -- end of ["spans"] - ["points"] = - { - [1] = - { - ["alt"] = 0, - ["type"] = "Turning Point", - ["ETA"] = 0, - ["alt_type"] = "BARO", - ["formation_template"] = "", - ["y"] = 382505.42857143, - ["x"] = 75757.142857141, - ["name"] = "DictKey_WptName_228", - ["ETA_locked"] = true, - ["speed"] = 5.5555555555556, - ["action"] = "Off Road", - ["task"] = - { - ["id"] = "ComboTask", - ["params"] = - { - ["tasks"] = - { - }, -- end of ["tasks"] - }, -- end of ["params"] - }, -- end of ["task"] - ["speed_locked"] = true, - }, -- end of [1] - [2] = - { - ["alt"] = 0, - ["type"] = "Turning Point", - ["ETA"] = 68.816348319683, - ["alt_type"] = "BARO", - ["formation_template"] = "", - ["y"] = 384891.14285714, - ["x"] = 73121.428571427, - ["name"] = "DictKey_WptName_241", - ["ETA_locked"] = false, - ["speed"] = 5.5555555555556, - ["action"] = "Off Road", - ["task"] = - { - ["id"] = "ComboTask", - ["params"] = - { - ["tasks"] = - { - }, -- end of ["tasks"] - }, -- end of ["params"] - }, -- end of ["task"] - ["speed_locked"] = true, - }, -- end of [2] - }, -- end of ["points"] - }, -- end of ["route"] - ["groupId"] = 43, - ["tasks"] = - { - }, -- end of ["tasks"] - ["hidden"] = false, - ["units"] = - { - [1] = - { - ["type"] = "GAZ-3308", - ["transportable"] = - { - ["randomTransportable"] = false, - }, -- end of ["transportable"] - ["unitId"] = 43, - ["skill"] = "Average", - ["y"] = 382505.42857143, - ["x"] = 75757.142857141, - ["name"] = "DictKey_UnitName_227", - ["playerCanDrive"] = false, - ["heading"] = 2.4059400444852, - }, -- end of [1] - }, -- end of ["units"] - ["y"] = 382505.42857143, - ["x"] = 75757.142857141, - ["name"] = "DictKey_GroupName_226", - ["start_time"] = 0, - ["task"] = "Ground Nothing", - }, -- end of [19] - }, -- end of ["group"] - }, -- end of ["vehicle"] - ["name"] = "Ukraine", - }, -- end of [2] - [3] = - { - ["id"] = 18, - ["name"] = "Abkhazia", - }, -- end of [3] - [4] = - { - ["id"] = 19, - ["name"] = "South Ossetia", - }, -- end of [4] - [5] = - { - ["id"] = 37, - ["name"] = "Kazakhstan", - }, -- end of [5] - [6] = - { - ["id"] = 24, - ["name"] = "Belarus", - }, -- end of [6] - [7] = - { - ["id"] = 27, - ["name"] = "China", - }, -- end of [7] - [8] = - { - ["id"] = 43, - ["name"] = "Serbia", - }, -- end of [8] - [9] = - { - ["id"] = 47, - ["name"] = "Syria", - }, -- end of [9] - [10] = - { - ["id"] = 34, - ["name"] = "Iran", - }, -- end of [10] - [11] = - { - ["id"] = 38, - ["name"] = "North Korea", - }, -- end of [11] - }, -- end of ["country"] - }, -- end of ["red"] - }, -- end of ["coalition"] - ["sortie"] = "DictKey_sortie_4", - ["version"] = 11, - ["goals"] = - { - }, -- end of ["goals"] - ["currentKey"] = 3403, - ["start_time"] = 43200, - ["forcedOptions"] = - { - }, -- end of ["forcedOptions"] - ["failures"] = - { - ["OIL_RADIATOR_SENSOR"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "OIL_RADIATOR_SENSOR", - ["mm"] = 0, - }, -- end of ["OIL_RADIATOR_SENSOR"] - ["TURNIND_POINTER_FAILS_NO_VACUUM"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "TURNIND_POINTER_FAILS_NO_VACUUM", - ["mm"] = 0, - }, -- end of ["TURNIND_POINTER_FAILS_NO_VACUUM"] - ["helmet"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "helmet", - ["mm"] = 0, - }, -- end of ["helmet"] - ["GUN_LEFT_IN_MOUNT_LOOSE"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "GUN_LEFT_IN_MOUNT_LOOSE", - ["mm"] = 0, - }, -- end of ["GUN_LEFT_IN_MOUNT_LOOSE"] - ["es_damage_MainInverter"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "es_damage_MainInverter", - ["mm"] = 0, - }, -- end of ["es_damage_MainInverter"] - ["rws"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "rws", - ["mm"] = 0, - }, -- end of ["rws"] - ["AN_ALR69V_FAILURE_SENSOR_TAIL_RIGHT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "AN_ALR69V_FAILURE_SENSOR_TAIL_RIGHT", - ["mm"] = 0, - }, -- end of ["AN_ALR69V_FAILURE_SENSOR_TAIL_RIGHT"] - ["MainReductor_ShaveInOil"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "MainReductor_ShaveInOil", - ["mm"] = 0, - }, -- end of ["MainReductor_ShaveInOil"] - ["asc_y"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "asc_y", - ["mm"] = 0, - }, -- end of ["asc_y"] - ["MAIN_L_GEAR_D_LOCK"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "MAIN_L_GEAR_D_LOCK", - ["mm"] = 0, - }, -- end of ["MAIN_L_GEAR_D_LOCK"] - ["AAR_47_FAILURE_SENSOR_LEFT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "AAR_47_FAILURE_SENSOR_LEFT", - ["mm"] = 0, - }, -- end of ["AAR_47_FAILURE_SENSOR_LEFT"] - ["tail_reductor_chip"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "tail_reductor_chip", - ["mm"] = 0, - }, -- end of ["tail_reductor_chip"] - ["TACAN_FAILURE_TOTAL"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "TACAN_FAILURE_TOTAL", - ["mm"] = 0, - }, -- end of ["TACAN_FAILURE_TOTAL"] - ["OIL_RADIATOR_MOTOR"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "OIL_RADIATOR_MOTOR", - ["mm"] = 0, - }, -- end of ["OIL_RADIATOR_MOTOR"] - ["SUPERCHARGER_WIRE"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "SUPERCHARGER_WIRE", - ["mm"] = 0, - }, -- end of ["SUPERCHARGER_WIRE"] - ["CADC_FAILURE_TEMPERATURE"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "CADC_FAILURE_TEMPERATURE", - ["mm"] = 0, - }, -- end of ["CADC_FAILURE_TEMPERATURE"] - ["FUSELAGE_TANK_LEAK"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "FUSELAGE_TANK_LEAK", - ["mm"] = 0, - }, -- end of ["FUSELAGE_TANK_LEAK"] - ["AN_ALE_40V_FAILURE_TOTAL"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "AN_ALE_40V_FAILURE_TOTAL", - ["mm"] = 0, - }, -- end of ["AN_ALE_40V_FAILURE_TOTAL"] - ["CADC_FAILURE_STATIC"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "CADC_FAILURE_STATIC", - ["mm"] = 0, - }, -- end of ["CADC_FAILURE_STATIC"] - ["AN_ALE_40V_FAILURE_CONTAINER_LEFT_WING"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "AN_ALE_40V_FAILURE_CONTAINER_LEFT_WING", - ["mm"] = 0, - }, -- end of ["AN_ALE_40V_FAILURE_CONTAINER_LEFT_WING"] - ["OIL_DILUTION_WIRE"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "OIL_DILUTION_WIRE", - ["mm"] = 0, - }, -- end of ["OIL_DILUTION_WIRE"] - ["FLEX_S_BKP_LAMP_DEFECTIVE"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "FLEX_S_BKP_LAMP_DEFECTIVE", - ["mm"] = 0, - }, -- end of ["FLEX_S_BKP_LAMP_DEFECTIVE"] - ["TAIL_GEAR_FAIL_GO_DOWN"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "TAIL_GEAR_FAIL_GO_DOWN", - ["mm"] = 0, - }, -- end of ["TAIL_GEAR_FAIL_GO_DOWN"] - ["GUN_FAIL_RIGHT_CENTER_GUN"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "GUN_FAIL_RIGHT_CENTER_GUN", - ["mm"] = 0, - }, -- end of ["GUN_FAIL_RIGHT_CENTER_GUN"] - ["LeftEngine_ShaveInOil"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "LeftEngine_ShaveInOil", - ["mm"] = 0, - }, -- end of ["LeftEngine_ShaveInOil"] - ["MAIN_R_GEAR_D_LOCK"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "MAIN_R_GEAR_D_LOCK", - ["mm"] = 0, - }, -- end of ["MAIN_R_GEAR_D_LOCK"] - ["R_GEAR_DLK_FAULT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "R_GEAR_DLK_FAULT", - ["mm"] = 0, - }, -- end of ["R_GEAR_DLK_FAULT"] - ["GMC_GYRO_FAILURE"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "GMC_GYRO_FAILURE", - ["mm"] = 0, - }, -- end of ["GMC_GYRO_FAILURE"] - ["L_GEAR_DLK_FAULT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "L_GEAR_DLK_FAULT", - ["mm"] = 0, - }, -- end of ["L_GEAR_DLK_FAULT"] - ["K14_FIXED_LAMP_DEFECTIVE"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "K14_FIXED_LAMP_DEFECTIVE", - ["mm"] = 0, - }, -- end of ["K14_FIXED_LAMP_DEFECTIVE"] - ["GUN_FAIL_LEFT_CENTER_GUN"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "GUN_FAIL_LEFT_CENTER_GUN", - ["mm"] = 0, - }, -- end of ["GUN_FAIL_LEFT_CENTER_GUN"] - ["engine_droop_failure"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "engine_droop_failure", - ["mm"] = 0, - }, -- end of ["engine_droop_failure"] - ["IGNITION_TERM_CONNECT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "IGNITION_TERM_CONNECT", - ["mm"] = 0, - }, -- end of ["IGNITION_TERM_CONNECT"] - ["CADC_FAILURE_TOTAL"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "CADC_FAILURE_TOTAL", - ["mm"] = 0, - }, -- end of ["CADC_FAILURE_TOTAL"] - ["es_damage_MainGenerator"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "es_damage_MainGenerator", - ["mm"] = 0, - }, -- end of ["es_damage_MainGenerator"] - ["TURNIND_POINTER_FAILS_DEFECTIVE"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "TURNIND_POINTER_FAILS_DEFECTIVE", - ["mm"] = 0, - }, -- end of ["TURNIND_POINTER_FAILS_DEFECTIVE"] - ["GUN_FAIL_RIGHT_OUT_GUN"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "GUN_FAIL_RIGHT_OUT_GUN", - ["mm"] = 0, - }, -- end of ["GUN_FAIL_RIGHT_OUT_GUN"] - ["BOMBS_DAMAGE_LINKAGE_LEFT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "BOMBS_DAMAGE_LINKAGE_LEFT", - ["mm"] = 0, - }, -- end of ["BOMBS_DAMAGE_LINKAGE_LEFT"] - ["FUSELAGE_TANK_PUMP_FAULT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "FUSELAGE_TANK_PUMP_FAULT", - ["mm"] = 0, - }, -- end of ["FUSELAGE_TANK_PUMP_FAULT"] - ["hydro_main"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "hydro_main", - ["mm"] = 0, - }, -- end of ["hydro_main"] - ["CICU_FAILURE_TOTAL"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "CICU_FAILURE_TOTAL", - ["mm"] = 0, - }, -- end of ["CICU_FAILURE_TOTAL"] - ["GUN_LEFT_OUT_MOUNT_LOOSE"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "GUN_LEFT_OUT_MOUNT_LOOSE", - ["mm"] = 0, - }, -- end of ["GUN_LEFT_OUT_MOUNT_LOOSE"] - ["TAIL_GEAR_U_LOCK"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "TAIL_GEAR_U_LOCK", - ["mm"] = 0, - }, -- end of ["TAIL_GEAR_U_LOCK"] - ["RADAR_ALT_TOTAL_FAILURE"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "RADAR_ALT_TOTAL_FAILURE", - ["mm"] = 0, - }, -- end of ["RADAR_ALT_TOTAL_FAILURE"] - ["GUN_RIGHT_CENTER_MOUNT_LOOSE"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "GUN_RIGHT_CENTER_MOUNT_LOOSE", - ["mm"] = 0, - }, -- end of ["GUN_RIGHT_CENTER_MOUNT_LOOSE"] - ["TAIL_GEAR_FAIL_GO_UP"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "TAIL_GEAR_FAIL_GO_UP", - ["mm"] = 0, - }, -- end of ["TAIL_GEAR_FAIL_GO_UP"] - ["asc_r"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "asc_r", - ["mm"] = 0, - }, -- end of ["asc_r"] - ["BOMBS_SOLENOID_FAULT_LEFT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "BOMBS_SOLENOID_FAULT_LEFT", - ["mm"] = 0, - }, -- end of ["BOMBS_SOLENOID_FAULT_LEFT"] - ["sas_yaw_left"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "sas_yaw_left", - ["mm"] = 0, - }, -- end of ["sas_yaw_left"] - ["DEFECTIVE_MECHANISM"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "DEFECTIVE_MECHANISM", - ["mm"] = 0, - }, -- end of ["DEFECTIVE_MECHANISM"] - ["PITOT_HEAT_ELEMENT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "PITOT_HEAT_ELEMENT", - ["mm"] = 0, - }, -- end of ["PITOT_HEAT_ELEMENT"] - ["ILS_FAILURE_ANT_LOCALIZER"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "ILS_FAILURE_ANT_LOCALIZER", - ["mm"] = 0, - }, -- end of ["ILS_FAILURE_ANT_LOCALIZER"] - ["AN_ALE_40V_FAILURE_CONTAINER_LEFT_GEAR"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "AN_ALE_40V_FAILURE_CONTAINER_LEFT_GEAR", - ["mm"] = 0, - }, -- end of ["AN_ALE_40V_FAILURE_CONTAINER_LEFT_GEAR"] - ["CARBAIR_SHORT_CIRCUIT_BLB"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "CARBAIR_SHORT_CIRCUIT_BLB", - ["mm"] = 0, - }, -- end of ["CARBAIR_SHORT_CIRCUIT_BLB"] - ["LEFT_TANK_PUMP_FAULT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "LEFT_TANK_PUMP_FAULT", - ["mm"] = 0, - }, -- end of ["LEFT_TANK_PUMP_FAULT"] - ["Surge_RightEngine"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "Surge_RightEngine", - ["mm"] = 0, - }, -- end of ["Surge_RightEngine"] - ["RightEngine_Fire"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["mm"] = 0, - }, -- end of ["RightEngine_Fire"] - ["GUN_FAIL_LEFT_IN_GUN"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "GUN_FAIL_LEFT_IN_GUN", - ["mm"] = 0, - }, -- end of ["GUN_FAIL_LEFT_IN_GUN"] - ["CADC_FAILURE_TAS"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "CADC_FAILURE_TAS", - ["mm"] = 0, - }, -- end of ["CADC_FAILURE_TAS"] - ["STARTER_SOL_SHORT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "STARTER_SOL_SHORT", - ["mm"] = 0, - }, -- end of ["STARTER_SOL_SHORT"] - ["asc_p"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "asc_p", - ["mm"] = 0, - }, -- end of ["asc_p"] - ["BOMBS_ARMING_BROKEN_SOLENOID_LEFT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "BOMBS_ARMING_BROKEN_SOLENOID_LEFT", - ["mm"] = 0, - }, -- end of ["BOMBS_ARMING_BROKEN_SOLENOID_LEFT"] - ["GUN_LEFT_IN_AMMUN_FAULT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "GUN_LEFT_IN_AMMUN_FAULT", - ["mm"] = 0, - }, -- end of ["GUN_LEFT_IN_AMMUN_FAULT"] - ["PUMP_RELIEF_VALVE_SCREEN_CLOGGED"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "PUMP_RELIEF_VALVE_SCREEN_CLOGGED", - ["mm"] = 0, - }, -- end of ["PUMP_RELIEF_VALVE_SCREEN_CLOGGED"] - ["abris_hardware"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "abris_hardware", - ["mm"] = 0, - }, -- end of ["abris_hardware"] - ["EEC_Failure_LeftEngine"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "EEC_Failure_LeftEngine", - ["mm"] = 0, - }, -- end of ["EEC_Failure_LeftEngine"] - ["COMPASS_POINTER_PULLS"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "COMPASS_POINTER_PULLS", - ["mm"] = 0, - }, -- end of ["COMPASS_POINTER_PULLS"] - ["Failure_RightEngine"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "Failure_RightEngine", - ["mm"] = 0, - }, -- end of ["Failure_RightEngine"] - ["ecm"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "ecm", - ["mm"] = 0, - }, -- end of ["ecm"] - ["CLOCK_FAILURE"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "CLOCK_FAILURE", - ["mm"] = 0, - }, -- end of ["CLOCK_FAILURE"] - ["TURNIND_INCORRECT_SENS_VAC_HIGH"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "TURNIND_INCORRECT_SENS_VAC_HIGH", - ["mm"] = 0, - }, -- end of ["TURNIND_INCORRECT_SENS_VAC_HIGH"] - ["OIL_RADIATOR_WIRING"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "OIL_RADIATOR_WIRING", - ["mm"] = 0, - }, -- end of ["OIL_RADIATOR_WIRING"] - ["IGNITION_NO_OUTPUT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "IGNITION_NO_OUTPUT", - ["mm"] = 0, - }, -- end of ["IGNITION_NO_OUTPUT"] - ["AAR_47_FAILURE_TOTAL"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "AAR_47_FAILURE_TOTAL", - ["mm"] = 0, - }, -- end of ["AAR_47_FAILURE_TOTAL"] - ["PILOT_KILLED_FAILURE"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "PILOT_KILLED_FAILURE", - ["mm"] = 0, - }, -- end of ["PILOT_KILLED_FAILURE"] - ["GUN_LEFT_CENTER_MOUNT_LOOSE"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "GUN_LEFT_CENTER_MOUNT_LOOSE", - ["mm"] = 0, - }, -- end of ["GUN_LEFT_CENTER_MOUNT_LOOSE"] - ["GUN_LEFT_OUT_AMMUN_FAULT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "GUN_LEFT_OUT_AMMUN_FAULT", - ["mm"] = 0, - }, -- end of ["GUN_LEFT_OUT_AMMUN_FAULT"] - ["COMPASS_ERRATIC_OPERATION"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "COMPASS_ERRATIC_OPERATION", - ["mm"] = 0, - }, -- end of ["COMPASS_ERRATIC_OPERATION"] - ["asc_a"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "asc_a", - ["mm"] = 0, - }, -- end of ["asc_a"] - ["AIRSPEED_INDICATOR_FAILURE"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "AIRSPEED_INDICATOR_FAILURE", - ["mm"] = 0, - }, -- end of ["AIRSPEED_INDICATOR_FAILURE"] - ["GUN_LEFT_CENTER_BARREL_WORN"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "GUN_LEFT_CENTER_BARREL_WORN", - ["mm"] = 0, - }, -- end of ["GUN_LEFT_CENTER_BARREL_WORN"] - ["abris_software"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "abris_software", - ["mm"] = 0, - }, -- end of ["abris_software"] - ["GUN_FAIL_LEFT_OUT_GUN"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "GUN_FAIL_LEFT_OUT_GUN", - ["mm"] = 0, - }, -- end of ["GUN_FAIL_LEFT_OUT_GUN"] - ["PUMP_FAILS"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "PUMP_FAILS", - ["mm"] = 0, - }, -- end of ["PUMP_FAILS"] - ["ROCKETS_INTERVALOMETER_WIRING"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "ROCKETS_INTERVALOMETER_WIRING", - ["mm"] = 0, - }, -- end of ["ROCKETS_INTERVALOMETER_WIRING"] - ["MainReductor_LowOilPressure"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "MainReductor_LowOilPressure", - ["mm"] = 0, - }, -- end of ["MainReductor_LowOilPressure"] - ["GUN_RIGHT_IN_AMMUN_FAULT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "GUN_RIGHT_IN_AMMUN_FAULT", - ["mm"] = 0, - }, -- end of ["GUN_RIGHT_IN_AMMUN_FAULT"] - ["D2_LEFT_CYLINDER"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "D2_LEFT_CYLINDER", - ["mm"] = 0, - }, -- end of ["D2_LEFT_CYLINDER"] - ["Surge_LeftEngine"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "Surge_LeftEngine", - ["mm"] = 0, - }, -- end of ["Surge_LeftEngine"] - ["BOMBS_RUST_LEFT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "BOMBS_RUST_LEFT", - ["mm"] = 0, - }, -- end of ["BOMBS_RUST_LEFT"] - ["GUN_RIGHT_CENTER_BARREL_WORN"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "GUN_RIGHT_CENTER_BARREL_WORN", - ["mm"] = 0, - }, -- end of ["GUN_RIGHT_CENTER_BARREL_WORN"] - ["INSUF_FUEL_PRES"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "INSUF_FUEL_PRES", - ["mm"] = 0, - }, -- end of ["INSUF_FUEL_PRES"] - ["COMPASS_NO_TORQUE"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "COMPASS_NO_TORQUE", - ["mm"] = 0, - }, -- end of ["COMPASS_NO_TORQUE"] - ["COOLANT_BREAK_BULB"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "COOLANT_BREAK_BULB", - ["mm"] = 0, - }, -- end of ["COOLANT_BREAK_BULB"] - ["PROP_GOVERNOR"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "PROP_GOVERNOR", - ["mm"] = 0, - }, -- end of ["PROP_GOVERNOR"] - ["MANIFOLD_SHIFT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "MANIFOLD_SHIFT", - ["mm"] = 0, - }, -- end of ["MANIFOLD_SHIFT"] - ["RIGHT_GUNNER_KILLED_FAILURE"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "RIGHT_GUNNER_KILLED_FAILURE", - ["mm"] = 0, - }, -- end of ["RIGHT_GUNNER_KILLED_FAILURE"] - ["UNLOAD_VALVE_NOT_UNLOAD"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "UNLOAD_VALVE_NOT_UNLOAD", - ["mm"] = 0, - }, -- end of ["UNLOAD_VALVE_NOT_UNLOAD"] - ["STARTER_BURNOUT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "STARTER_BURNOUT", - ["mm"] = 0, - }, -- end of ["STARTER_BURNOUT"] - ["UNLOAD_VALVE_NOT_LOAD"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "UNLOAD_VALVE_NOT_LOAD", - ["mm"] = 0, - }, -- end of ["UNLOAD_VALVE_NOT_LOAD"] - ["TURNIND_INCORRECT_SENS_VAC_LOW"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "TURNIND_INCORRECT_SENS_VAC_LOW", - ["mm"] = 0, - }, -- end of ["TURNIND_INCORRECT_SENS_VAC_LOW"] - ["Failure_LeftEngine"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "Failure_LeftEngine", - ["mm"] = 0, - }, -- end of ["Failure_LeftEngine"] - ["GUN_RIGHT_IN_BARREL_WORN"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "GUN_RIGHT_IN_BARREL_WORN", - ["mm"] = 0, - }, -- end of ["GUN_RIGHT_IN_BARREL_WORN"] - ["K14_MOV_LAMP_DEFECTIVE"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "K14_MOV_LAMP_DEFECTIVE", - ["mm"] = 0, - }, -- end of ["K14_MOV_LAMP_DEFECTIVE"] - ["ILS_FAILURE_TOTAL"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "ILS_FAILURE_TOTAL", - ["mm"] = 0, - }, -- end of ["ILS_FAILURE_TOTAL"] - ["GUN_RIGHT_OUT_BARREL_WORN"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "GUN_RIGHT_OUT_BARREL_WORN", - ["mm"] = 0, - }, -- end of ["GUN_RIGHT_OUT_BARREL_WORN"] - ["fuel_sys_transfer_pumps"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "fuel_sys_transfer_pumps", - ["mm"] = 0, - }, -- end of ["fuel_sys_transfer_pumps"] - ["PITOT_HEAT_WIRING"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "PITOT_HEAT_WIRING", - ["mm"] = 0, - }, -- end of ["PITOT_HEAT_WIRING"] - ["TURNIND_POINTER_NOT_SET_ZERO"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "TURNIND_POINTER_NOT_SET_ZERO", - ["mm"] = 0, - }, -- end of ["TURNIND_POINTER_NOT_SET_ZERO"] - ["MD1_GYRO_TOTAL_FAILURE"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "MD1_GYRO_TOTAL_FAILURE", - ["mm"] = 0, - }, -- end of ["MD1_GYRO_TOTAL_FAILURE"] - ["VHF_FM_RADIO_FAILURE_TOTAL"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "VHF_FM_RADIO_FAILURE_TOTAL", - ["mm"] = 0, - }, -- end of ["VHF_FM_RADIO_FAILURE_TOTAL"] - ["RIGHT_MFCD_FAILURE"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "RIGHT_MFCD_FAILURE", - ["mm"] = 0, - }, -- end of ["RIGHT_MFCD_FAILURE"] - ["F2_BOTTOM_CYLINDER"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "F2_BOTTOM_CYLINDER", - ["mm"] = 0, - }, -- end of ["F2_BOTTOM_CYLINDER"] - ["LEFT_WING_TANK_LEAK"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "LEFT_WING_TANK_LEAK", - ["mm"] = 0, - }, -- end of ["LEFT_WING_TANK_LEAK"] - ["CARBAIR_BREAK_LEADS"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "CARBAIR_BREAK_LEADS", - ["mm"] = 0, - }, -- end of ["CARBAIR_BREAK_LEADS"] - ["GUN_LEFT_IN_OPEN_CIRCUIT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "GUN_LEFT_IN_OPEN_CIRCUIT", - ["mm"] = 0, - }, -- end of ["GUN_LEFT_IN_OPEN_CIRCUIT"] - ["EGI_FAILURE_TOTAL"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "EGI_FAILURE_TOTAL", - ["mm"] = 0, - }, -- end of ["EGI_FAILURE_TOTAL"] - ["UHF_RADIO_FAILURE_TOTAL"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "UHF_RADIO_FAILURE_TOTAL", - ["mm"] = 0, - }, -- end of ["UHF_RADIO_FAILURE_TOTAL"] - ["GUN_RIGHT_CENTER_AMMUN_FAULT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "GUN_RIGHT_CENTER_AMMUN_FAULT", - ["mm"] = 0, - }, -- end of ["GUN_RIGHT_CENTER_AMMUN_FAULT"] - ["LEFT_GUNNER_KILLED_FAILURE"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "LEFT_GUNNER_KILLED_FAILURE", - ["mm"] = 0, - }, -- end of ["LEFT_GUNNER_KILLED_FAILURE"] - ["VHF_VT207_DEFECTIVE"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "VHF_VT207_DEFECTIVE", - ["mm"] = 0, - }, -- end of ["VHF_VT207_DEFECTIVE"] - ["RightEngine_LowOilPressure"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "RightEngine_LowOilPressure", - ["mm"] = 0, - }, -- end of ["RightEngine_LowOilPressure"] - ["radar"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "radar", - ["mm"] = 0, - }, -- end of ["radar"] - ["RIGHT_TANK_PUMP_FAULT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "RIGHT_TANK_PUMP_FAULT", - ["mm"] = 0, - }, -- end of ["RIGHT_TANK_PUMP_FAULT"] - ["COOLANT_UNPRES"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "COOLANT_UNPRES", - ["mm"] = 0, - }, -- end of ["COOLANT_UNPRES"] - ["ARN_82_FAILURE_TOTAL"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "ARN_82_FAILURE_TOTAL", - ["mm"] = 0, - }, -- end of ["ARN_82_FAILURE_TOTAL"] - ["FLEX_S_NO_POWER_SUPPLY"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "FLEX_S_NO_POWER_SUPPLY", - ["mm"] = 0, - }, -- end of ["FLEX_S_NO_POWER_SUPPLY"] - ["eos"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "eos", - ["mm"] = 0, - }, -- end of ["eos"] - ["HYDRO_LOW_AIR_PRESSURE"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "HYDRO_LOW_AIR_PRESSURE", - ["mm"] = 0, - }, -- end of ["HYDRO_LOW_AIR_PRESSURE"] - ["K14_MOTOR_DEFECTIVE"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "K14_MOTOR_DEFECTIVE", - ["mm"] = 0, - }, -- end of ["K14_MOTOR_DEFECTIVE"] - ["GENERATOR_FAULT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "GENERATOR_FAULT", - ["mm"] = 0, - }, -- end of ["GENERATOR_FAULT"] - ["FUEL_PUMP_FAILURE"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "FUEL_PUMP_FAILURE", - ["mm"] = 0, - }, -- end of ["FUEL_PUMP_FAILURE"] - ["RADAR_ALTIMETR_LEFT_ANT_FAILURE"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "RADAR_ALTIMETR_LEFT_ANT_FAILURE", - ["mm"] = 0, - }, -- end of ["RADAR_ALTIMETR_LEFT_ANT_FAILURE"] - ["hydro"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "hydro", - ["mm"] = 0, - }, -- end of ["hydro"] - ["BAT_SOLENOID_DEFECTIVE"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "BAT_SOLENOID_DEFECTIVE", - ["mm"] = 0, - }, -- end of ["BAT_SOLENOID_DEFECTIVE"] - ["LeftEngine_Fire"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["mm"] = 0, - }, -- end of ["LeftEngine_Fire"] - ["SUPERCHARGER_LIGHT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "SUPERCHARGER_LIGHT", - ["mm"] = 0, - }, -- end of ["SUPERCHARGER_LIGHT"] - ["L_GEAR_UPL_FAULT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "L_GEAR_UPL_FAULT", - ["mm"] = 0, - }, -- end of ["L_GEAR_UPL_FAULT"] - ["fs_damage_right_cell_boost_pump"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "fs_damage_right_cell_boost_pump", - ["mm"] = 0, - }, -- end of ["fs_damage_right_cell_boost_pump"] - ["TACH_RESISTANCE_ADJ"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "TACH_RESISTANCE_ADJ", - ["mm"] = 0, - }, -- end of ["TACH_RESISTANCE_ADJ"] - ["MAGNETO_1"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "MAGNETO_1", - ["mm"] = 0, - }, -- end of ["MAGNETO_1"] - ["BOMBS_NO_VOLATAGE_AT_RACK_RIGHT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "BOMBS_NO_VOLATAGE_AT_RACK_RIGHT", - ["mm"] = 0, - }, -- end of ["BOMBS_NO_VOLATAGE_AT_RACK_RIGHT"] - ["GUN_RIGHT_OUT_MOUNT_LOOSE"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "GUN_RIGHT_OUT_MOUNT_LOOSE", - ["mm"] = 0, - }, -- end of ["GUN_RIGHT_OUT_MOUNT_LOOSE"] - ["TailReductor_ShaveInOil"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "TailReductor_ShaveInOil", - ["mm"] = 0, - }, -- end of ["TailReductor_ShaveInOil"] - ["R_GEAR_UPL_FAULT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "R_GEAR_UPL_FAULT", - ["mm"] = 0, - }, -- end of ["R_GEAR_UPL_FAULT"] - ["BOMBS_TRAIN_DEFECTIVE_WIRING"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "BOMBS_TRAIN_DEFECTIVE_WIRING", - ["mm"] = 0, - }, -- end of ["BOMBS_TRAIN_DEFECTIVE_WIRING"] - ["autopilot"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "autopilot", - ["mm"] = 0, - }, -- end of ["autopilot"] - ["BOMBS_TRAIN_DEFECTIVE_SWITCH"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "BOMBS_TRAIN_DEFECTIVE_SWITCH", - ["mm"] = 0, - }, -- end of ["BOMBS_TRAIN_DEFECTIVE_SWITCH"] - ["CARBAIR_SHORT_CIRCUIT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "CARBAIR_SHORT_CIRCUIT", - ["mm"] = 0, - }, -- end of ["CARBAIR_SHORT_CIRCUIT"] - ["STARTER_RELAY"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "STARTER_RELAY", - ["mm"] = 0, - }, -- end of ["STARTER_RELAY"] - ["AN_ALE_40V_FAILURE_CONTAINER_RIGHT_WING"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "AN_ALE_40V_FAILURE_CONTAINER_RIGHT_WING", - ["mm"] = 0, - }, -- end of ["AN_ALE_40V_FAILURE_CONTAINER_RIGHT_WING"] - ["TACAN_FAILURE_RECEIVER"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "TACAN_FAILURE_RECEIVER", - ["mm"] = 0, - }, -- end of ["TACAN_FAILURE_RECEIVER"] - ["GUN_RIGHT_IN_MOUNT_LOOSE"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "GUN_RIGHT_IN_MOUNT_LOOSE", - ["mm"] = 0, - }, -- end of ["GUN_RIGHT_IN_MOUNT_LOOSE"] - ["hydro_right"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "hydro_right", - ["mm"] = 0, - }, -- end of ["hydro_right"] - ["sas_yaw_right"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "sas_yaw_right", - ["mm"] = 0, - }, -- end of ["sas_yaw_right"] - ["DOORS_TVC_BROKEN"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "DOORS_TVC_BROKEN", - ["mm"] = 0, - }, -- end of ["DOORS_TVC_BROKEN"] - ["SADL_FAILURE_TOTAL"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "SADL_FAILURE_TOTAL", - ["mm"] = 0, - }, -- end of ["SADL_FAILURE_TOTAL"] - ["fs_damage_left_cell_boost_pump"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "fs_damage_left_cell_boost_pump", - ["mm"] = 0, - }, -- end of ["fs_damage_left_cell_boost_pump"] - ["BOOST_REG"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "BOOST_REG", - ["mm"] = 0, - }, -- end of ["BOOST_REG"] - ["hydro_left"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "hydro_left", - ["mm"] = 0, - }, -- end of ["hydro_left"] - ["ENGINE_JAM"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "ENGINE_JAM", - ["mm"] = 0, - }, -- end of ["ENGINE_JAM"] - ["MAGNETO_2"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "MAGNETO_2", - ["mm"] = 0, - }, -- end of ["MAGNETO_2"] - ["SAR_1_95"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "SAR_1_95", - ["mm"] = 0, - }, -- end of ["SAR_1_95"] - ["BOMBS_SOLENOID_FAULT_RIGHT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "BOMBS_SOLENOID_FAULT_RIGHT", - ["mm"] = 0, - }, -- end of ["BOMBS_SOLENOID_FAULT_RIGHT"] - ["CDU_FAILURE_TOTAL"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "CDU_FAILURE_TOTAL", - ["mm"] = 0, - }, -- end of ["CDU_FAILURE_TOTAL"] - ["AN_ALR69V_FAILURE_SENSOR_NOSE_RIGHT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "AN_ALR69V_FAILURE_SENSOR_NOSE_RIGHT", - ["mm"] = 0, - }, -- end of ["AN_ALR69V_FAILURE_SENSOR_NOSE_RIGHT"] - ["TAIL_GEAR_C_CABLE"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "TAIL_GEAR_C_CABLE", - ["mm"] = 0, - }, -- end of ["TAIL_GEAR_C_CABLE"] - ["STARTER_WIRING"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "STARTER_WIRING", - ["mm"] = 0, - }, -- end of ["STARTER_WIRING"] - ["engine_driveshaft_failure"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "engine_driveshaft_failure", - ["mm"] = 0, - }, -- end of ["engine_driveshaft_failure"] - ["PUMP_RELIEF_VALVE_LEAKS"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "PUMP_RELIEF_VALVE_LEAKS", - ["mm"] = 0, - }, -- end of ["PUMP_RELIEF_VALVE_LEAKS"] - ["HUD_FAILURE"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "HUD_FAILURE", - ["mm"] = 0, - }, -- end of ["HUD_FAILURE"] - ["mfd"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "mfd", - ["mm"] = 0, - }, -- end of ["mfd"] - ["CARBAIR_GND_LEAD"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "CARBAIR_GND_LEAD", - ["mm"] = 0, - }, -- end of ["CARBAIR_GND_LEAD"] - ["GMC_MAGN_COMP_FAILURE"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "GMC_MAGN_COMP_FAILURE", - ["mm"] = 0, - }, -- end of ["GMC_MAGN_COMP_FAILURE"] - ["es_damage_GeneratorLeft"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "es_damage_GeneratorLeft", - ["mm"] = 0, - }, -- end of ["es_damage_GeneratorLeft"] - ["ILS_FAILURE_ANT_GLIDESLOPE"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "ILS_FAILURE_ANT_GLIDESLOPE", - ["mm"] = 0, - }, -- end of ["ILS_FAILURE_ANT_GLIDESLOPE"] - ["engine_chip"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "engine_chip", - ["mm"] = 0, - }, -- end of ["engine_chip"] - ["ARN_83_TOTAL_FAILURE"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "ARN_83_TOTAL_FAILURE", - ["mm"] = 0, - }, -- end of ["ARN_83_TOTAL_FAILURE"] - ["CADC_FAILURE_MACH"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "CADC_FAILURE_MACH", - ["mm"] = 0, - }, -- end of ["CADC_FAILURE_MACH"] - ["ROCKETS_DEFECTIVE_WIRING"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "ROCKETS_DEFECTIVE_WIRING", - ["mm"] = 0, - }, -- end of ["ROCKETS_DEFECTIVE_WIRING"] - ["COPILOT_GYRO_TOTAL_FAILURE"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "COPILOT_GYRO_TOTAL_FAILURE", - ["mm"] = 0, - }, -- end of ["COPILOT_GYRO_TOTAL_FAILURE"] - ["RightEngine_ShaveInOil"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "RightEngine_ShaveInOil", - ["mm"] = 0, - }, -- end of ["RightEngine_ShaveInOil"] - ["EEC_Failure_RightEngine"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "EEC_Failure_RightEngine", - ["mm"] = 0, - }, -- end of ["EEC_Failure_RightEngine"] - ["laser_failure"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "laser_failure", - ["mm"] = 0, - }, -- end of ["laser_failure"] - ["COOLANT_RADIATOR_SENSOR"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "COOLANT_RADIATOR_SENSOR", - ["mm"] = 0, - }, -- end of ["COOLANT_RADIATOR_SENSOR"] - ["BOMBS_NO_VOLATAGE_BOTH"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "BOMBS_NO_VOLATAGE_BOTH", - ["mm"] = 0, - }, -- end of ["BOMBS_NO_VOLATAGE_BOTH"] - ["TAIL_GEAR_D_LOCK"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "TAIL_GEAR_D_LOCK", - ["mm"] = 0, - }, -- end of ["TAIL_GEAR_D_LOCK"] - ["GUN_FAIL_RIGHT_IN_GUN"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "GUN_FAIL_RIGHT_IN_GUN", - ["mm"] = 0, - }, -- end of ["GUN_FAIL_RIGHT_IN_GUN"] - ["COOLANT_RADIATOR_WIRING"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "COOLANT_RADIATOR_WIRING", - ["mm"] = 0, - }, -- end of ["COOLANT_RADIATOR_WIRING"] - ["CADC_FAILURE_DYNAMIC"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "CADC_FAILURE_DYNAMIC", - ["mm"] = 0, - }, -- end of ["CADC_FAILURE_DYNAMIC"] - ["CARBAIR_SHORT_CIRCUIT_LEADS"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "CARBAIR_SHORT_CIRCUIT_LEADS", - ["mm"] = 0, - }, -- end of ["CARBAIR_SHORT_CIRCUIT_LEADS"] - ["NOSE_AIRSPEED_INDICATOR_FAILURE"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "NOSE_AIRSPEED_INDICATOR_FAILURE", - ["mm"] = 0, - }, -- end of ["NOSE_AIRSPEED_INDICATOR_FAILURE"] - ["BATTERY_OVERHEAT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "BATTERY_OVERHEAT", - ["mm"] = 0, - }, -- end of ["BATTERY_OVERHEAT"] - ["ROOF_AIRSPEED_INDICATOR_FAILURE"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "ROOF_AIRSPEED_INDICATOR_FAILURE", - ["mm"] = 0, - }, -- end of ["ROOF_AIRSPEED_INDICATOR_FAILURE"] - ["SUPERCHARGER_SOLENOID"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "SUPERCHARGER_SOLENOID", - ["mm"] = 0, - }, -- end of ["SUPERCHARGER_SOLENOID"] - ["TGP_FAILURE_RIGHT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "TGP_FAILURE_RIGHT", - ["mm"] = 0, - }, -- end of ["TGP_FAILURE_RIGHT"] - ["VHF_AM_RADIO_FAILURE_TOTAL"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "VHF_AM_RADIO_FAILURE_TOTAL", - ["mm"] = 0, - }, -- end of ["VHF_AM_RADIO_FAILURE_TOTAL"] - ["GUN_RIGHT_OUT_OPEN_CIRCUIT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "GUN_RIGHT_OUT_OPEN_CIRCUIT", - ["mm"] = 0, - }, -- end of ["GUN_RIGHT_OUT_OPEN_CIRCUIT"] - ["sas_pitch_left"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "sas_pitch_left", - ["mm"] = 0, - }, -- end of ["sas_pitch_left"] - ["BOMBS_ARMING_NO_VOLATAGE_LEFT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "BOMBS_ARMING_NO_VOLATAGE_LEFT", - ["mm"] = 0, - }, -- end of ["BOMBS_ARMING_NO_VOLATAGE_LEFT"] - ["STARTER_SOLENOID"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "STARTER_SOLENOID", - ["mm"] = 0, - }, -- end of ["STARTER_SOLENOID"] - ["TACH_BREAK_CIRCUIT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "TACH_BREAK_CIRCUIT", - ["mm"] = 0, - }, -- end of ["TACH_BREAK_CIRCUIT"] - ["hydro_main_irreversible_valve"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "hydro_main_irreversible_valve", - ["mm"] = 0, - }, -- end of ["hydro_main_irreversible_valve"] - ["COOLANT_DEFECTIVE_IND"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "COOLANT_DEFECTIVE_IND", - ["mm"] = 0, - }, -- end of ["COOLANT_DEFECTIVE_IND"] - ["hud"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "hud", - ["mm"] = 0, - }, -- end of ["hud"] - ["INT_HYDRO_LEAK"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "INT_HYDRO_LEAK", - ["mm"] = 0, - }, -- end of ["INT_HYDRO_LEAK"] - ["BOMBS_DAMAGE_ELINKAGE_LEFT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "BOMBS_DAMAGE_ELINKAGE_LEFT", - ["mm"] = 0, - }, -- end of ["BOMBS_DAMAGE_ELINKAGE_LEFT"] - ["SAR_1_2_95"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "SAR_1_2_95", - ["mm"] = 0, - }, -- end of ["SAR_1_2_95"] - ["fuel_sys_left_transfer_pump"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "fuel_sys_left_transfer_pump", - ["mm"] = 0, - }, -- end of ["fuel_sys_left_transfer_pump"] - ["LeftEngine_LowOilPressure"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "LeftEngine_LowOilPressure", - ["mm"] = 0, - }, -- end of ["LeftEngine_LowOilPressure"] - ["FAULTY_ROCKET_LEFT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "FAULTY_ROCKET_LEFT", - ["mm"] = 0, - }, -- end of ["FAULTY_ROCKET_LEFT"] - ["COOLANT_POOR_CONNTECT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "COOLANT_POOR_CONNTECT", - ["mm"] = 0, - }, -- end of ["COOLANT_POOR_CONNTECT"] - ["GUN_RIGHT_OUT_AMMUN_FAULT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "GUN_RIGHT_OUT_AMMUN_FAULT", - ["mm"] = 0, - }, -- end of ["GUN_RIGHT_OUT_AMMUN_FAULT"] - ["TGP_FAILURE_LEFT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "TGP_FAILURE_LEFT", - ["mm"] = 0, - }, -- end of ["TGP_FAILURE_LEFT"] - ["es_damage_StarterGenerator"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "es_damage_StarterGenerator", - ["mm"] = 0, - }, -- end of ["es_damage_StarterGenerator"] - ["es_damage_Battery"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "es_damage_Battery", - ["mm"] = 0, - }, -- end of ["es_damage_Battery"] - ["ILS_FAILURE_ANT_MARKER"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "ILS_FAILURE_ANT_MARKER", - ["mm"] = 0, - }, -- end of ["ILS_FAILURE_ANT_MARKER"] - ["VHF_CRYSTAL"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "VHF_CRYSTAL", - ["mm"] = 0, - }, -- end of ["VHF_CRYSTAL"] - ["TURNIND_POINTER_VIBRATES"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "TURNIND_POINTER_VIBRATES", - ["mm"] = 0, - }, -- end of ["TURNIND_POINTER_VIBRATES"] - ["GUN_LEFT_CENTER_OPEN_CIRCUIT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "GUN_LEFT_CENTER_OPEN_CIRCUIT", - ["mm"] = 0, - }, -- end of ["GUN_LEFT_CENTER_OPEN_CIRCUIT"] - ["sas_pitch_right"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "sas_pitch_right", - ["mm"] = 0, - }, -- end of ["sas_pitch_right"] - ["fs_damage_engine_pump"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "fs_damage_engine_pump", - ["mm"] = 0, - }, -- end of ["fs_damage_engine_pump"] - ["VHF_SHORTED_CTL_BOX"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "VHF_SHORTED_CTL_BOX", - ["mm"] = 0, - }, -- end of ["VHF_SHORTED_CTL_BOX"] - ["TACH_POOR_CONNECTION"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "TACH_POOR_CONNECTION", - ["mm"] = 0, - }, -- end of ["TACH_POOR_CONNECTION"] - ["CADC_FAILURE_IAS"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "CADC_FAILURE_IAS", - ["mm"] = 0, - }, -- end of ["CADC_FAILURE_IAS"] - ["main_reductor_chip"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "main_reductor_chip", - ["mm"] = 0, - }, -- end of ["main_reductor_chip"] - ["VHF_SQUELCH_RELAY"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "VHF_SQUELCH_RELAY", - ["mm"] = 0, - }, -- end of ["VHF_SQUELCH_RELAY"] - ["AN_ALR69V_FAILURE_SENSOR_TAIL_LEFT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "AN_ALR69V_FAILURE_SENSOR_TAIL_LEFT", - ["mm"] = 0, - }, -- end of ["AN_ALR69V_FAILURE_SENSOR_TAIL_LEFT"] - ["RADAR_ALTIMETR_RIGHT_ANT_FAILURE"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "RADAR_ALTIMETR_RIGHT_ANT_FAILURE", - ["mm"] = 0, - }, -- end of ["RADAR_ALTIMETR_RIGHT_ANT_FAILURE"] - ["F2_TOP_CYLINDER"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "F2_TOP_CYLINDER", - ["mm"] = 0, - }, -- end of ["F2_TOP_CYLINDER"] - ["FLEX_S_MAIN_LAMP_DEFECTIVE"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "FLEX_S_MAIN_LAMP_DEFECTIVE", - ["mm"] = 0, - }, -- end of ["FLEX_S_MAIN_LAMP_DEFECTIVE"] - ["MANIFOLD_LINE_LEAK"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "MANIFOLD_LINE_LEAK", - ["mm"] = 0, - }, -- end of ["MANIFOLD_LINE_LEAK"] - ["es_damage_GeneratorRight"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "es_damage_GeneratorRight", - ["mm"] = 0, - }, -- end of ["es_damage_GeneratorRight"] - ["CADC_FAILURE_PRESSURE_ALT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "CADC_FAILURE_PRESSURE_ALT", - ["mm"] = 0, - }, -- end of ["CADC_FAILURE_PRESSURE_ALT"] - ["K14_NO_POWER_SUPPLY"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "K14_NO_POWER_SUPPLY", - ["mm"] = 0, - }, -- end of ["K14_NO_POWER_SUPPLY"] - ["es_damage_SpareInverter"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "es_damage_SpareInverter", - ["mm"] = 0, - }, -- end of ["es_damage_SpareInverter"] - ["l_conv"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "l_conv", - ["mm"] = 0, - }, -- end of ["l_conv"] - ["BOMBS_DAMAGE_LINKAGE_RIGHT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "BOMBS_DAMAGE_LINKAGE_RIGHT", - ["mm"] = 0, - }, -- end of ["BOMBS_DAMAGE_LINKAGE_RIGHT"] - ["BOMBS_NO_VOLATAGE_AT_RACK_LEFT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "BOMBS_NO_VOLATAGE_AT_RACK_LEFT", - ["mm"] = 0, - }, -- end of ["BOMBS_NO_VOLATAGE_AT_RACK_LEFT"] - ["COOLANT_SHORT_CIRCUIT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "COOLANT_SHORT_CIRCUIT", - ["mm"] = 0, - }, -- end of ["COOLANT_SHORT_CIRCUIT"] - ["HORIZON_BAR_NOT_SETTLE"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "HORIZON_BAR_NOT_SETTLE", - ["mm"] = 0, - }, -- end of ["HORIZON_BAR_NOT_SETTLE"] - ["CADC_FAILURE_BARO_ALT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "CADC_FAILURE_BARO_ALT", - ["mm"] = 0, - }, -- end of ["CADC_FAILURE_BARO_ALT"] - ["GUN_RIGHT_CENTER_OPEN_CIRCUIT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "GUN_RIGHT_CENTER_OPEN_CIRCUIT", - ["mm"] = 0, - }, -- end of ["GUN_RIGHT_CENTER_OPEN_CIRCUIT"] - ["IFFCC_FAILURE_TOTAL"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "IFFCC_FAILURE_TOTAL", - ["mm"] = 0, - }, -- end of ["IFFCC_FAILURE_TOTAL"] - ["RIGHT_WING_TANK_LEAK"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "RIGHT_WING_TANK_LEAK", - ["mm"] = 0, - }, -- end of ["RIGHT_WING_TANK_LEAK"] - ["asc"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "asc", - ["mm"] = 0, - }, -- end of ["asc"] - ["TACH_BREAK_IN_INDICATOR"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "TACH_BREAK_IN_INDICATOR", - ["mm"] = 0, - }, -- end of ["TACH_BREAK_IN_INDICATOR"] - ["TransitionalReductor_ShaveInOil"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "TransitionalReductor_ShaveInOil", - ["mm"] = 0, - }, -- end of ["TransitionalReductor_ShaveInOil"] - ["engine_surge_failure"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "engine_surge_failure", - ["mm"] = 0, - }, -- end of ["engine_surge_failure"] - ["EXT_HYDRO_LEAK"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "EXT_HYDRO_LEAK", - ["mm"] = 0, - }, -- end of ["EXT_HYDRO_LEAK"] - ["BOMBS_ARMING_BROKEN_SOLENOID_RIGHT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "BOMBS_ARMING_BROKEN_SOLENOID_RIGHT", - ["mm"] = 0, - }, -- end of ["BOMBS_ARMING_BROKEN_SOLENOID_RIGHT"] - ["DEFECTIVE_INSTRUMENT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "DEFECTIVE_INSTRUMENT", - ["mm"] = 0, - }, -- end of ["DEFECTIVE_INSTRUMENT"] - ["AN_ALR69V_FAILURE_SENSOR_NOSE_LEFT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "AN_ALR69V_FAILURE_SENSOR_NOSE_LEFT", - ["mm"] = 0, - }, -- end of ["AN_ALR69V_FAILURE_SENSOR_NOSE_LEFT"] - ["mlws"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "mlws", - ["mm"] = 0, - }, -- end of ["mlws"] - ["BOMBS_ARMING_NO_VOLATAGE_BOTH"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "BOMBS_ARMING_NO_VOLATAGE_BOTH", - ["mm"] = 0, - }, -- end of ["BOMBS_ARMING_NO_VOLATAGE_BOTH"] - ["BAT_SOLENOID_WIRING"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "BAT_SOLENOID_WIRING", - ["mm"] = 0, - }, -- end of ["BAT_SOLENOID_WIRING"] - ["STARTER_LOSE_CON"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "STARTER_LOSE_CON", - ["mm"] = 0, - }, -- end of ["STARTER_LOSE_CON"] - ["FUEL_VALVE_LEAK"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "FUEL_VALVE_LEAK", - ["mm"] = 0, - }, -- end of ["FUEL_VALVE_LEAK"] - ["FLEX_S_NO_GUN_SIGN"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "FLEX_S_NO_GUN_SIGN", - ["mm"] = 0, - }, -- end of ["FLEX_S_NO_GUN_SIGN"] - ["fuel_sys_right_transfer_pump"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "fuel_sys_right_transfer_pump", - ["mm"] = 0, - }, -- end of ["fuel_sys_right_transfer_pump"] - ["COOLANT_RADIATOR_MOTOR"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "COOLANT_RADIATOR_MOTOR", - ["mm"] = 0, - }, -- end of ["COOLANT_RADIATOR_MOTOR"] - ["CARBAIR_OPEN_CIRCUIT_BLB"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "CARBAIR_OPEN_CIRCUIT_BLB", - ["mm"] = 0, - }, -- end of ["CARBAIR_OPEN_CIRCUIT_BLB"] - ["AAR_47_FAILURE_SENSOR_TAIL"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "AAR_47_FAILURE_SENSOR_TAIL", - ["mm"] = 0, - }, -- end of ["AAR_47_FAILURE_SENSOR_TAIL"] - ["GUN_LEFT_OUT_OPEN_CIRCUIT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "GUN_LEFT_OUT_OPEN_CIRCUIT", - ["mm"] = 0, - }, -- end of ["GUN_LEFT_OUT_OPEN_CIRCUIT"] - ["TACAN_FAILURE_TRANSMITTER"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "TACAN_FAILURE_TRANSMITTER", - ["mm"] = 0, - }, -- end of ["TACAN_FAILURE_TRANSMITTER"] - ["ecf"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["mm"] = 0, - }, -- end of ["ecf"] - ["l_gen"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "l_gen", - ["mm"] = 0, - }, -- end of ["l_gen"] - ["BOMBS_ARMING_BROKEN_WIRING_RIGHT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "BOMBS_ARMING_BROKEN_WIRING_RIGHT", - ["mm"] = 0, - }, -- end of ["BOMBS_ARMING_BROKEN_WIRING_RIGHT"] - ["GUN_LEFT_IN_BARREL_WORN"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "GUN_LEFT_IN_BARREL_WORN", - ["mm"] = 0, - }, -- end of ["GUN_LEFT_IN_BARREL_WORN"] - ["r_gen"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "r_gen", - ["mm"] = 0, - }, -- end of ["r_gen"] - ["GUN_LEFT_CENTER_AMMUN_FAULT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "GUN_LEFT_CENTER_AMMUN_FAULT", - ["mm"] = 0, - }, -- end of ["GUN_LEFT_CENTER_AMMUN_FAULT"] - ["AAR_47_FAILURE_SENSOR_RIGHT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "AAR_47_FAILURE_SENSOR_RIGHT", - ["mm"] = 0, - }, -- end of ["AAR_47_FAILURE_SENSOR_RIGHT"] - ["ROCKETS_INTERVALOMETER_SEQ"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "ROCKETS_INTERVALOMETER_SEQ", - ["mm"] = 0, - }, -- end of ["ROCKETS_INTERVALOMETER_SEQ"] - ["hydro_common"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "hydro_common", - ["mm"] = 0, - }, -- end of ["hydro_common"] - ["SAR_2_95"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "SAR_2_95", - ["mm"] = 0, - }, -- end of ["SAR_2_95"] - ["SAR_2_101"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "SAR_2_101", - ["mm"] = 0, - }, -- end of ["SAR_2_101"] - ["BOOSTER_COIL"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "BOOSTER_COIL", - ["mm"] = 0, - }, -- end of ["BOOSTER_COIL"] - ["ARN_83_ADF_DAMAGE"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "ARN_83_ADF_DAMAGE", - ["mm"] = 0, - }, -- end of ["ARN_83_ADF_DAMAGE"] - ["FAULTY_ROCKET_RIGHT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "FAULTY_ROCKET_RIGHT", - ["mm"] = 0, - }, -- end of ["FAULTY_ROCKET_RIGHT"] - ["GUN_RIGHT_IN_OPEN_CIRCUIT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "GUN_RIGHT_IN_OPEN_CIRCUIT", - ["mm"] = 0, - }, -- end of ["GUN_RIGHT_IN_OPEN_CIRCUIT"] - ["COMPASS_ERRATIC_INDIACATON"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "COMPASS_ERRATIC_INDIACATON", - ["mm"] = 0, - }, -- end of ["COMPASS_ERRATIC_INDIACATON"] - ["OIL_DILUTION_SOLENOID"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "OIL_DILUTION_SOLENOID", - ["mm"] = 0, - }, -- end of ["OIL_DILUTION_SOLENOID"] - ["PUMP_SEPARATOR_CLOGGED"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "PUMP_SEPARATOR_CLOGGED", - ["mm"] = 0, - }, -- end of ["PUMP_SEPARATOR_CLOGGED"] - ["LEFT_MFCD_FAILURE"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "LEFT_MFCD_FAILURE", - ["mm"] = 0, - }, -- end of ["LEFT_MFCD_FAILURE"] - ["BOMBS_RUST_RIGHT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "BOMBS_RUST_RIGHT", - ["mm"] = 0, - }, -- end of ["BOMBS_RUST_RIGHT"] - ["CLOGGED_FUEL_STRAINER"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "CLOGGED_FUEL_STRAINER", - ["mm"] = 0, - }, -- end of ["CLOGGED_FUEL_STRAINER"] - ["SAR_1_101"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "SAR_1_101", - ["mm"] = 0, - }, -- end of ["SAR_1_101"] - ["r_engine"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "r_engine", - ["mm"] = 0, - }, -- end of ["r_engine"] - ["r_conv"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "r_conv", - ["mm"] = 0, - }, -- end of ["r_conv"] - ["A11_CLOCK_FAILURE"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "A11_CLOCK_FAILURE", - ["mm"] = 0, - }, -- end of ["A11_CLOCK_FAILURE"] - ["DOORS_TV_JAMMED"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "DOORS_TV_JAMMED", - ["mm"] = 0, - }, -- end of ["DOORS_TV_JAMMED"] - ["D2_RIGHT_CYLINDER"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "D2_RIGHT_CYLINDER", - ["mm"] = 0, - }, -- end of ["D2_RIGHT_CYLINDER"] - ["COPILOT_KILLED_FAILURE"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "COPILOT_KILLED_FAILURE", - ["mm"] = 0, - }, -- end of ["COPILOT_KILLED_FAILURE"] - ["AN_ALR69V_FAILURE_TOTAL"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "AN_ALR69V_FAILURE_TOTAL", - ["mm"] = 0, - }, -- end of ["AN_ALR69V_FAILURE_TOTAL"] - ["GMC_TOTAL_FAILURE"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "GMC_TOTAL_FAILURE", - ["mm"] = 0, - }, -- end of ["GMC_TOTAL_FAILURE"] - ["VHF_VT_BURNED_OUT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "VHF_VT_BURNED_OUT", - ["mm"] = 0, - }, -- end of ["VHF_VT_BURNED_OUT"] - ["BOMBS_DAMAGE_ELINKAGE_RIGHT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "BOMBS_DAMAGE_ELINKAGE_RIGHT", - ["mm"] = 0, - }, -- end of ["BOMBS_DAMAGE_ELINKAGE_RIGHT"] - ["l_engine"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "l_engine", - ["mm"] = 0, - }, -- end of ["l_engine"] - ["AAR_47_FAILURE_SENSOR_BOTTOM"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "AAR_47_FAILURE_SENSOR_BOTTOM", - ["mm"] = 0, - }, -- end of ["AAR_47_FAILURE_SENSOR_BOTTOM"] - ["BOMBS_ARMING_BROKEN_WIRING_LEFT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "BOMBS_ARMING_BROKEN_WIRING_LEFT", - ["mm"] = 0, - }, -- end of ["BOMBS_ARMING_BROKEN_WIRING_LEFT"] - ["AN_ALE_40V_FAILURE_CONTAINER_RIGHT_GEAR"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "AN_ALE_40V_FAILURE_CONTAINER_RIGHT_GEAR", - ["mm"] = 0, - }, -- end of ["AN_ALE_40V_FAILURE_CONTAINER_RIGHT_GEAR"] - ["GUN_LEFT_OUT_BARREL_WORN"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "GUN_LEFT_OUT_BARREL_WORN", - ["mm"] = 0, - }, -- end of ["GUN_LEFT_OUT_BARREL_WORN"] - ["JADRO_1A_FAILURE_TOTAL"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["mm"] = 0, - }, -- end of ["JADRO_1A_FAILURE_TOTAL"] - ["BOMBS_ARMING_NO_VOLATAGE_RIGHT"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "BOMBS_ARMING_NO_VOLATAGE_RIGHT", - ["mm"] = 0, - }, -- end of ["BOMBS_ARMING_NO_VOLATAGE_RIGHT"] - ["IFFCC_FAILURE_GUN"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "IFFCC_FAILURE_GUN", - ["mm"] = 0, - }, -- end of ["IFFCC_FAILURE_GUN"] - ["APU_Fire"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["mm"] = 0, - }, -- end of ["APU_Fire"] - ["TURNIND_INCORRECT_SENS_DEFECTIVE"] = - { - ["hh"] = 0, - ["prob"] = 100, - ["enable"] = false, - ["mmint"] = 1, - ["id"] = "TURNIND_INCORRECT_SENS_DEFECTIVE", - ["mm"] = 0, - }, -- end of ["TURNIND_INCORRECT_SENS_DEFECTIVE"] - }, -- end of ["failures"] -} -- end of mission diff --git a/Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC/options b/Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC/options deleted file mode 100644 index e68f02ebd..000000000 --- a/Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC/options +++ /dev/null @@ -1,212 +0,0 @@ -options = -{ - ["difficulty"] = - { - ["fuel"] = false, - ["labels"] = false, - ["easyRadar"] = false, - ["easyCommunication"] = true, - ["miniHUD"] = false, - ["setGlobal"] = true, - ["birds"] = 0, - ["optionsView"] = "optview_all", - ["permitCrash"] = true, - ["immortal"] = false, - ["avionicsLanguage"] = "native", - ["cockpitVisualRM"] = true, - ["padlock"] = true, - ["reports"] = true, - ["hideStick"] = false, - ["radio"] = true, - ["map"] = true, - ["externalViews"] = true, - ["spectatorExternalViews"] = true, - ["cockpitLanguage"] = "english", - ["tips"] = true, - ["userSnapView"] = true, - ["units"] = "metric", - ["impostors"] = "medium", - ["iconsTheme"] = "nato", - ["easyFlight"] = false, - ["weapons"] = false, - ["cockpitStatusBarAllowed"] = false, - ["geffect"] = "realistic", - }, -- end of ["difficulty"] - ["playerName"] = "Killer", - ["graphics"] = - { - ["OculusRift"] = false, - ["color"] = "32", - ["preloadRadius"] = 73885, - ["heatBlr"] = 2, - ["scenes"] = "high", - ["water"] = 2, - ["visibRange"] = "High", - ["treesVisibility"] = 25000, - ["aspect"] = 1.7777777777778, - ["lights"] = 2, - ["HDR"] = 1, - ["MSAA"] = 6, - ["civTraffic"] = "high", - ["clutterMaxDistance"] = 1500, - ["terrainTextures"] = "max", - ["multiMonitorSetup"] = "1camera", - ["shadowTree"] = true, - ["fullScreen"] = false, - ["disableAero"] = false, - ["DOF"] = 0, - ["clouds"] = 1, - ["flatTerrainShadows"] = 1, - ["cockpitShadows"] = true, - ["height"] = 900, - ["width"] = 1600, - ["shadows"] = 5, - ["textures"] = 2, - ["sync"] = true, - ["LensEffects"] = 3, - ["anisotropy"] = 4, - ["TranspSSAA"] = false, - ["haze"] = 1, - ["effects"] = 3, - }, -- end of ["graphics"] - ["plugins"] = - { - ["CA"] = - { - ["kompass_options"] = 1, - ["ground_target_info"] = true, - ["ground_aim_helper"] = true, - ["ground_platform_shake"] = true, - ["ground_automatic"] = true, - }, -- end of ["CA"] - ["M-2000C"] = - { - ["TDC_"] = false, - ["CPLocalList"] = "default", - }, -- end of ["M-2000C"] - ["A-10C"] = - { - ["CPLocalList"] = "default", - }, -- end of ["A-10C"] - ["FC3"] = - { - ["CPLocalList_F-15C"] = "default", - ["CPLocalList_MiG-29S"] = "default", - ["CPLocalList_MiG-29A"] = "default", - ["CPLocalList_Su-25"] = "default", - ["CPLocalList_A-10A"] = "default", - ["CPLocalList_Su-27"] = "chinese", - ["CPLocalList_MiG-29G"] = "default", - ["CPLocalList_Su-33"] = "default", - }, -- end of ["FC3"] - ["Hawk"] = - { - ["CPLocalList"] = "high", - }, -- end of ["Hawk"] - ["P-51D"] = - { - ["assistance"] = 100, - ["CPLocalList"] = "default", - ["autoRudder"] = false, - }, -- end of ["P-51D"] - ["TF-51D"] = - { - ["assistance"] = 100, - ["CPLocalList"] = "default", - ["autoRudder"] = false, - }, -- end of ["TF-51D"] - ["MiG-21Bis"] = - { - ["Engine"] = false, - ["Shake"] = 100, - ["CustomCockpit"] = false, - ["Reticle"] = false, - ["Freeze"] = false, - }, -- end of ["MiG-21Bis"] - ["F-86F"] = - { - ["landSeatAdjustF86"] = true, - ["CPLocalList"] = "default", - ["NoseWheelSteeringSimpleBehaviourF86"] = true, - ["gunCamera"] = 0, - }, -- end of ["F-86F"] - ["Su-25T"] = - { - ["CPLocalList"] = "default", - }, -- end of ["Su-25T"] - ["Mi-8MTV2"] = - { - ["altMi8TrimmingMethod"] = false, - ["Mi8AutopilotAdjustment"] = false, - ["Mi8RudderTrimmer"] = false, - ["controlHelperMi8"] = false, - ["CPLocalList"] = "default", - ["weapTooltipsMi8"] = true, - ["Mi8FOV"] = 120, - }, -- end of ["Mi-8MTV2"] - ["MiG-15bis"] = - { - ["autoLeanToAimMiG15"] = true, - ["CPLocalList"] = "chinese", - ["gunCamera"] = 0, - ["aiHelper"] = false, - }, -- end of ["MiG-15bis"] - ["FW-190D9"] = - { - ["assistance"] = 100, - ["CPLocalList"] = "default", - ["autoRudder"] = false, - }, -- end of ["FW-190D9"] - ["UH-1H"] = - { - ["UHRudderTrimmer"] = false, - ["autoPilot"] = true, - ["altUHTrimmingMethod"] = false, - ["CPLocalList"] = "default", - ["weapTooltips"] = true, - ["UHTrackIRAiming"] = true, - }, -- end of ["UH-1H"] - ["Ka-50"] = - { - ["altTrimmingMethod"] = false, - ["Ka50RudderTrimmer"] = false, - ["CPLocalList"] = "english", - }, -- end of ["Ka-50"] - }, -- end of ["plugins"] - ["views"] = - { - ["cockpit"] = - { - ["mirrors"] = false, - ["reflections"] = false, - ["avionics"] = 3, - ["russianHud"] = false, - }, -- end of ["cockpit"] - }, -- end of ["views"] - ["sound"] = - { - ["hear_in_helmet"] = true, - ["headphones"] = -15, - ["cockpit"] = 0, - ["GBreathEffect"] = true, - ["gui"] = 0, - ["volume"] = 0, - ["radioSpeech"] = true, - ["music"] = -100, - ["subtitles"] = true, - ["world"] = 0, - }, -- end of ["sound"] - ["miscellaneous"] = - { - ["headmove"] = true, - ["f5_nearest_ac"] = true, - ["f11_free_camera"] = true, - ["F2_view_effects"] = 2, - ["f10_awacs"] = true, - ["Coordinate_Display"] = "Lat Long", - ["accidental_failures"] = false, - ["force_feedback_enabled"] = true, - ["synchronize_controls"] = false, - ["show_pilot_body"] = true, - }, -- end of ["miscellaneous"] -} -- end of options diff --git a/Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC/warehouses b/Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC/warehouses deleted file mode 100644 index 68ca70eb9..000000000 --- a/Moose Test Missions/Moose_Test_FAC/Moose_Test_FAC/warehouses +++ /dev/null @@ -1,807 +0,0 @@ -warehouses = -{ - ["airports"] = - { - [12] = - { - ["gasoline"] = - { - ["InitFuel"] = 100, - }, -- end of ["gasoline"] - ["unlimitedMunitions"] = true, - ["methanol_mixture"] = - { - ["InitFuel"] = 100, - }, -- end of ["methanol_mixture"] - ["OperatingLevel_Air"] = 10, - ["diesel"] = - { - ["InitFuel"] = 100, - }, -- end of ["diesel"] - ["speed"] = 16.666666, - ["size"] = 100, - ["periodicity"] = 30, - ["suppliers"] = - { - }, -- end of ["suppliers"] - ["coalition"] = "NEUTRAL", - ["jet_fuel"] = - { - ["InitFuel"] = 100, - }, -- end of ["jet_fuel"] - ["OperatingLevel_Eqp"] = 10, - ["unlimitedFuel"] = true, - ["aircrafts"] = - { - }, -- end of ["aircrafts"] - ["weapons"] = - { - }, -- end of ["weapons"] - ["OperatingLevel_Fuel"] = 10, - ["unlimitedAircrafts"] = true, - }, -- end of [12] - [13] = - { - ["gasoline"] = - { - ["InitFuel"] = 100, - }, -- end of ["gasoline"] - ["unlimitedMunitions"] = true, - ["methanol_mixture"] = - { - ["InitFuel"] = 100, - }, -- end of ["methanol_mixture"] - ["OperatingLevel_Air"] = 10, - ["diesel"] = - { - ["InitFuel"] = 100, - }, -- end of ["diesel"] - ["speed"] = 16.666666, - ["size"] = 100, - ["periodicity"] = 30, - ["suppliers"] = - { - }, -- end of ["suppliers"] - ["coalition"] = "NEUTRAL", - ["jet_fuel"] = - { - ["InitFuel"] = 100, - }, -- end of ["jet_fuel"] - ["OperatingLevel_Eqp"] = 10, - ["unlimitedFuel"] = true, - ["aircrafts"] = - { - }, -- end of ["aircrafts"] - ["weapons"] = - { - }, -- end of ["weapons"] - ["OperatingLevel_Fuel"] = 10, - ["unlimitedAircrafts"] = true, - }, -- end of [13] - [14] = - { - ["gasoline"] = - { - ["InitFuel"] = 100, - }, -- end of ["gasoline"] - ["unlimitedMunitions"] = true, - ["methanol_mixture"] = - { - ["InitFuel"] = 100, - }, -- end of ["methanol_mixture"] - ["OperatingLevel_Air"] = 10, - ["diesel"] = - { - ["InitFuel"] = 100, - }, -- end of ["diesel"] - ["speed"] = 16.666666, - ["size"] = 100, - ["periodicity"] = 30, - ["suppliers"] = - { - }, -- end of ["suppliers"] - ["coalition"] = "NEUTRAL", - ["jet_fuel"] = - { - ["InitFuel"] = 100, - }, -- end of ["jet_fuel"] - ["OperatingLevel_Eqp"] = 10, - ["unlimitedFuel"] = true, - ["aircrafts"] = - { - }, -- end of ["aircrafts"] - ["weapons"] = - { - }, -- end of ["weapons"] - ["OperatingLevel_Fuel"] = 10, - ["unlimitedAircrafts"] = true, - }, -- end of [14] - [15] = - { - ["gasoline"] = - { - ["InitFuel"] = 100, - }, -- end of ["gasoline"] - ["unlimitedMunitions"] = true, - ["methanol_mixture"] = - { - ["InitFuel"] = 100, - }, -- end of ["methanol_mixture"] - ["OperatingLevel_Air"] = 10, - ["diesel"] = - { - ["InitFuel"] = 100, - }, -- end of ["diesel"] - ["speed"] = 16.666666, - ["size"] = 100, - ["periodicity"] = 30, - ["suppliers"] = - { - }, -- end of ["suppliers"] - ["coalition"] = "NEUTRAL", - ["jet_fuel"] = - { - ["InitFuel"] = 100, - }, -- end of ["jet_fuel"] - ["OperatingLevel_Eqp"] = 10, - ["unlimitedFuel"] = true, - ["aircrafts"] = - { - }, -- end of ["aircrafts"] - ["weapons"] = - { - }, -- end of ["weapons"] - ["OperatingLevel_Fuel"] = 10, - ["unlimitedAircrafts"] = true, - }, -- end of [15] - [16] = - { - ["gasoline"] = - { - ["InitFuel"] = 100, - }, -- end of ["gasoline"] - ["unlimitedMunitions"] = true, - ["methanol_mixture"] = - { - ["InitFuel"] = 100, - }, -- end of ["methanol_mixture"] - ["OperatingLevel_Air"] = 10, - ["diesel"] = - { - ["InitFuel"] = 100, - }, -- end of ["diesel"] - ["speed"] = 16.666666, - ["size"] = 100, - ["periodicity"] = 30, - ["suppliers"] = - { - }, -- end of ["suppliers"] - ["coalition"] = "NEUTRAL", - ["jet_fuel"] = - { - ["InitFuel"] = 100, - }, -- end of ["jet_fuel"] - ["OperatingLevel_Eqp"] = 10, - ["unlimitedFuel"] = true, - ["aircrafts"] = - { - }, -- end of ["aircrafts"] - ["weapons"] = - { - }, -- end of ["weapons"] - ["OperatingLevel_Fuel"] = 10, - ["unlimitedAircrafts"] = true, - }, -- end of [16] - [17] = - { - ["gasoline"] = - { - ["InitFuel"] = 100, - }, -- end of ["gasoline"] - ["unlimitedMunitions"] = true, - ["methanol_mixture"] = - { - ["InitFuel"] = 100, - }, -- end of ["methanol_mixture"] - ["OperatingLevel_Air"] = 10, - ["diesel"] = - { - ["InitFuel"] = 100, - }, -- end of ["diesel"] - ["speed"] = 16.666666, - ["size"] = 100, - ["periodicity"] = 30, - ["suppliers"] = - { - }, -- end of ["suppliers"] - ["coalition"] = "NEUTRAL", - ["jet_fuel"] = - { - ["InitFuel"] = 100, - }, -- end of ["jet_fuel"] - ["OperatingLevel_Eqp"] = 10, - ["unlimitedFuel"] = true, - ["aircrafts"] = - { - }, -- end of ["aircrafts"] - ["weapons"] = - { - }, -- end of ["weapons"] - ["OperatingLevel_Fuel"] = 10, - ["unlimitedAircrafts"] = true, - }, -- end of [17] - [18] = - { - ["gasoline"] = - { - ["InitFuel"] = 100, - }, -- end of ["gasoline"] - ["unlimitedMunitions"] = true, - ["methanol_mixture"] = - { - ["InitFuel"] = 100, - }, -- end of ["methanol_mixture"] - ["OperatingLevel_Air"] = 10, - ["diesel"] = - { - ["InitFuel"] = 100, - }, -- end of ["diesel"] - ["speed"] = 16.666666, - ["size"] = 100, - ["periodicity"] = 30, - ["suppliers"] = - { - }, -- end of ["suppliers"] - ["coalition"] = "NEUTRAL", - ["jet_fuel"] = - { - ["InitFuel"] = 100, - }, -- end of ["jet_fuel"] - ["OperatingLevel_Eqp"] = 10, - ["unlimitedFuel"] = true, - ["aircrafts"] = - { - }, -- end of ["aircrafts"] - ["weapons"] = - { - }, -- end of ["weapons"] - ["OperatingLevel_Fuel"] = 10, - ["unlimitedAircrafts"] = true, - }, -- end of [18] - [19] = - { - ["gasoline"] = - { - ["InitFuel"] = 100, - }, -- end of ["gasoline"] - ["unlimitedMunitions"] = true, - ["methanol_mixture"] = - { - ["InitFuel"] = 100, - }, -- end of ["methanol_mixture"] - ["OperatingLevel_Air"] = 10, - ["diesel"] = - { - ["InitFuel"] = 100, - }, -- end of ["diesel"] - ["speed"] = 16.666666, - ["size"] = 100, - ["periodicity"] = 30, - ["suppliers"] = - { - }, -- end of ["suppliers"] - ["coalition"] = "NEUTRAL", - ["jet_fuel"] = - { - ["InitFuel"] = 100, - }, -- end of ["jet_fuel"] - ["OperatingLevel_Eqp"] = 10, - ["unlimitedFuel"] = true, - ["aircrafts"] = - { - }, -- end of ["aircrafts"] - ["weapons"] = - { - }, -- end of ["weapons"] - ["OperatingLevel_Fuel"] = 10, - ["unlimitedAircrafts"] = true, - }, -- end of [19] - [20] = - { - ["gasoline"] = - { - ["InitFuel"] = 100, - }, -- end of ["gasoline"] - ["unlimitedMunitions"] = true, - ["methanol_mixture"] = - { - ["InitFuel"] = 100, - }, -- end of ["methanol_mixture"] - ["OperatingLevel_Air"] = 10, - ["diesel"] = - { - ["InitFuel"] = 100, - }, -- end of ["diesel"] - ["speed"] = 16.666666, - ["size"] = 100, - ["periodicity"] = 30, - ["suppliers"] = - { - }, -- end of ["suppliers"] - ["coalition"] = "NEUTRAL", - ["jet_fuel"] = - { - ["InitFuel"] = 100, - }, -- end of ["jet_fuel"] - ["OperatingLevel_Eqp"] = 10, - ["unlimitedFuel"] = true, - ["aircrafts"] = - { - }, -- end of ["aircrafts"] - ["weapons"] = - { - }, -- end of ["weapons"] - ["OperatingLevel_Fuel"] = 10, - ["unlimitedAircrafts"] = true, - }, -- end of [20] - [21] = - { - ["gasoline"] = - { - ["InitFuel"] = 100, - }, -- end of ["gasoline"] - ["unlimitedMunitions"] = true, - ["methanol_mixture"] = - { - ["InitFuel"] = 100, - }, -- end of ["methanol_mixture"] - ["OperatingLevel_Air"] = 10, - ["diesel"] = - { - ["InitFuel"] = 100, - }, -- end of ["diesel"] - ["speed"] = 16.666666, - ["size"] = 100, - ["periodicity"] = 30, - ["suppliers"] = - { - }, -- end of ["suppliers"] - ["coalition"] = "NEUTRAL", - ["jet_fuel"] = - { - ["InitFuel"] = 100, - }, -- end of ["jet_fuel"] - ["OperatingLevel_Eqp"] = 10, - ["unlimitedFuel"] = true, - ["aircrafts"] = - { - }, -- end of ["aircrafts"] - ["weapons"] = - { - }, -- end of ["weapons"] - ["OperatingLevel_Fuel"] = 10, - ["unlimitedAircrafts"] = true, - }, -- end of [21] - [22] = - { - ["gasoline"] = - { - ["InitFuel"] = 100, - }, -- end of ["gasoline"] - ["unlimitedMunitions"] = true, - ["methanol_mixture"] = - { - ["InitFuel"] = 100, - }, -- end of ["methanol_mixture"] - ["OperatingLevel_Air"] = 10, - ["diesel"] = - { - ["InitFuel"] = 100, - }, -- end of ["diesel"] - ["speed"] = 16.666666, - ["size"] = 100, - ["periodicity"] = 30, - ["suppliers"] = - { - }, -- end of ["suppliers"] - ["coalition"] = "NEUTRAL", - ["jet_fuel"] = - { - ["InitFuel"] = 100, - }, -- end of ["jet_fuel"] - ["OperatingLevel_Eqp"] = 10, - ["unlimitedFuel"] = true, - ["aircrafts"] = - { - }, -- end of ["aircrafts"] - ["weapons"] = - { - }, -- end of ["weapons"] - ["OperatingLevel_Fuel"] = 10, - ["unlimitedAircrafts"] = true, - }, -- end of [22] - [23] = - { - ["gasoline"] = - { - ["InitFuel"] = 100, - }, -- end of ["gasoline"] - ["unlimitedMunitions"] = true, - ["methanol_mixture"] = - { - ["InitFuel"] = 100, - }, -- end of ["methanol_mixture"] - ["OperatingLevel_Air"] = 10, - ["diesel"] = - { - ["InitFuel"] = 100, - }, -- end of ["diesel"] - ["speed"] = 16.666666, - ["size"] = 100, - ["periodicity"] = 30, - ["suppliers"] = - { - }, -- end of ["suppliers"] - ["coalition"] = "NEUTRAL", - ["jet_fuel"] = - { - ["InitFuel"] = 100, - }, -- end of ["jet_fuel"] - ["OperatingLevel_Eqp"] = 10, - ["unlimitedFuel"] = true, - ["aircrafts"] = - { - }, -- end of ["aircrafts"] - ["weapons"] = - { - }, -- end of ["weapons"] - ["OperatingLevel_Fuel"] = 10, - ["unlimitedAircrafts"] = true, - }, -- end of [23] - [24] = - { - ["gasoline"] = - { - ["InitFuel"] = 100, - }, -- end of ["gasoline"] - ["unlimitedMunitions"] = true, - ["methanol_mixture"] = - { - ["InitFuel"] = 100, - }, -- end of ["methanol_mixture"] - ["OperatingLevel_Air"] = 10, - ["diesel"] = - { - ["InitFuel"] = 100, - }, -- end of ["diesel"] - ["speed"] = 16.666666, - ["size"] = 100, - ["periodicity"] = 30, - ["suppliers"] = - { - }, -- end of ["suppliers"] - ["coalition"] = "NEUTRAL", - ["jet_fuel"] = - { - ["InitFuel"] = 100, - }, -- end of ["jet_fuel"] - ["OperatingLevel_Eqp"] = 10, - ["unlimitedFuel"] = true, - ["aircrafts"] = - { - }, -- end of ["aircrafts"] - ["weapons"] = - { - }, -- end of ["weapons"] - ["OperatingLevel_Fuel"] = 10, - ["unlimitedAircrafts"] = true, - }, -- end of [24] - [25] = - { - ["gasoline"] = - { - ["InitFuel"] = 100, - }, -- end of ["gasoline"] - ["unlimitedMunitions"] = true, - ["methanol_mixture"] = - { - ["InitFuel"] = 100, - }, -- end of ["methanol_mixture"] - ["OperatingLevel_Air"] = 10, - ["diesel"] = - { - ["InitFuel"] = 100, - }, -- end of ["diesel"] - ["speed"] = 16.666666, - ["size"] = 100, - ["periodicity"] = 30, - ["suppliers"] = - { - }, -- end of ["suppliers"] - ["coalition"] = "NEUTRAL", - ["jet_fuel"] = - { - ["InitFuel"] = 100, - }, -- end of ["jet_fuel"] - ["OperatingLevel_Eqp"] = 10, - ["unlimitedFuel"] = true, - ["aircrafts"] = - { - }, -- end of ["aircrafts"] - ["weapons"] = - { - }, -- end of ["weapons"] - ["OperatingLevel_Fuel"] = 10, - ["unlimitedAircrafts"] = true, - }, -- end of [25] - [26] = - { - ["gasoline"] = - { - ["InitFuel"] = 100, - }, -- end of ["gasoline"] - ["unlimitedMunitions"] = true, - ["methanol_mixture"] = - { - ["InitFuel"] = 100, - }, -- end of ["methanol_mixture"] - ["OperatingLevel_Air"] = 10, - ["diesel"] = - { - ["InitFuel"] = 100, - }, -- end of ["diesel"] - ["speed"] = 16.666666, - ["size"] = 100, - ["periodicity"] = 30, - ["suppliers"] = - { - }, -- end of ["suppliers"] - ["coalition"] = "NEUTRAL", - ["jet_fuel"] = - { - ["InitFuel"] = 100, - }, -- end of ["jet_fuel"] - ["OperatingLevel_Eqp"] = 10, - ["unlimitedFuel"] = true, - ["aircrafts"] = - { - }, -- end of ["aircrafts"] - ["weapons"] = - { - }, -- end of ["weapons"] - ["OperatingLevel_Fuel"] = 10, - ["unlimitedAircrafts"] = true, - }, -- end of [26] - [27] = - { - ["gasoline"] = - { - ["InitFuel"] = 100, - }, -- end of ["gasoline"] - ["unlimitedMunitions"] = true, - ["methanol_mixture"] = - { - ["InitFuel"] = 100, - }, -- end of ["methanol_mixture"] - ["OperatingLevel_Air"] = 10, - ["diesel"] = - { - ["InitFuel"] = 100, - }, -- end of ["diesel"] - ["speed"] = 16.666666, - ["size"] = 100, - ["periodicity"] = 30, - ["suppliers"] = - { - }, -- end of ["suppliers"] - ["coalition"] = "NEUTRAL", - ["jet_fuel"] = - { - ["InitFuel"] = 100, - }, -- end of ["jet_fuel"] - ["OperatingLevel_Eqp"] = 10, - ["unlimitedFuel"] = true, - ["aircrafts"] = - { - }, -- end of ["aircrafts"] - ["weapons"] = - { - }, -- end of ["weapons"] - ["OperatingLevel_Fuel"] = 10, - ["unlimitedAircrafts"] = true, - }, -- end of [27] - [28] = - { - ["gasoline"] = - { - ["InitFuel"] = 100, - }, -- end of ["gasoline"] - ["unlimitedMunitions"] = true, - ["methanol_mixture"] = - { - ["InitFuel"] = 100, - }, -- end of ["methanol_mixture"] - ["OperatingLevel_Air"] = 10, - ["diesel"] = - { - ["InitFuel"] = 100, - }, -- end of ["diesel"] - ["speed"] = 16.666666, - ["size"] = 100, - ["periodicity"] = 30, - ["suppliers"] = - { - }, -- end of ["suppliers"] - ["coalition"] = "NEUTRAL", - ["jet_fuel"] = - { - ["InitFuel"] = 100, - }, -- end of ["jet_fuel"] - ["OperatingLevel_Eqp"] = 10, - ["unlimitedFuel"] = true, - ["aircrafts"] = - { - }, -- end of ["aircrafts"] - ["weapons"] = - { - }, -- end of ["weapons"] - ["OperatingLevel_Fuel"] = 10, - ["unlimitedAircrafts"] = true, - }, -- end of [28] - [29] = - { - ["gasoline"] = - { - ["InitFuel"] = 100, - }, -- end of ["gasoline"] - ["unlimitedMunitions"] = true, - ["methanol_mixture"] = - { - ["InitFuel"] = 100, - }, -- end of ["methanol_mixture"] - ["OperatingLevel_Air"] = 10, - ["diesel"] = - { - ["InitFuel"] = 100, - }, -- end of ["diesel"] - ["speed"] = 16.666666, - ["size"] = 100, - ["periodicity"] = 30, - ["suppliers"] = - { - }, -- end of ["suppliers"] - ["coalition"] = "NEUTRAL", - ["jet_fuel"] = - { - ["InitFuel"] = 100, - }, -- end of ["jet_fuel"] - ["OperatingLevel_Eqp"] = 10, - ["unlimitedFuel"] = true, - ["aircrafts"] = - { - }, -- end of ["aircrafts"] - ["weapons"] = - { - }, -- end of ["weapons"] - ["OperatingLevel_Fuel"] = 10, - ["unlimitedAircrafts"] = true, - }, -- end of [29] - [30] = - { - ["gasoline"] = - { - ["InitFuel"] = 100, - }, -- end of ["gasoline"] - ["unlimitedMunitions"] = true, - ["methanol_mixture"] = - { - ["InitFuel"] = 100, - }, -- end of ["methanol_mixture"] - ["OperatingLevel_Air"] = 10, - ["diesel"] = - { - ["InitFuel"] = 100, - }, -- end of ["diesel"] - ["speed"] = 16.666666, - ["size"] = 100, - ["periodicity"] = 30, - ["suppliers"] = - { - }, -- end of ["suppliers"] - ["coalition"] = "NEUTRAL", - ["jet_fuel"] = - { - ["InitFuel"] = 100, - }, -- end of ["jet_fuel"] - ["OperatingLevel_Eqp"] = 10, - ["unlimitedFuel"] = true, - ["aircrafts"] = - { - }, -- end of ["aircrafts"] - ["weapons"] = - { - }, -- end of ["weapons"] - ["OperatingLevel_Fuel"] = 10, - ["unlimitedAircrafts"] = true, - }, -- end of [30] - [31] = - { - ["gasoline"] = - { - ["InitFuel"] = 100, - }, -- end of ["gasoline"] - ["unlimitedMunitions"] = true, - ["methanol_mixture"] = - { - ["InitFuel"] = 100, - }, -- end of ["methanol_mixture"] - ["OperatingLevel_Air"] = 10, - ["diesel"] = - { - ["InitFuel"] = 100, - }, -- end of ["diesel"] - ["speed"] = 16.666666, - ["size"] = 100, - ["periodicity"] = 30, - ["suppliers"] = - { - }, -- end of ["suppliers"] - ["coalition"] = "NEUTRAL", - ["jet_fuel"] = - { - ["InitFuel"] = 100, - }, -- end of ["jet_fuel"] - ["OperatingLevel_Eqp"] = 10, - ["unlimitedFuel"] = true, - ["aircrafts"] = - { - }, -- end of ["aircrafts"] - ["weapons"] = - { - }, -- end of ["weapons"] - ["OperatingLevel_Fuel"] = 10, - ["unlimitedAircrafts"] = true, - }, -- end of [31] - [32] = - { - ["gasoline"] = - { - ["InitFuel"] = 100, - }, -- end of ["gasoline"] - ["unlimitedMunitions"] = true, - ["methanol_mixture"] = - { - ["InitFuel"] = 100, - }, -- end of ["methanol_mixture"] - ["OperatingLevel_Air"] = 10, - ["diesel"] = - { - ["InitFuel"] = 100, - }, -- end of ["diesel"] - ["speed"] = 16.666666, - ["size"] = 100, - ["periodicity"] = 30, - ["suppliers"] = - { - }, -- end of ["suppliers"] - ["coalition"] = "NEUTRAL", - ["jet_fuel"] = - { - ["InitFuel"] = 100, - }, -- end of ["jet_fuel"] - ["OperatingLevel_Eqp"] = 10, - ["unlimitedFuel"] = true, - ["aircrafts"] = - { - }, -- end of ["aircrafts"] - ["weapons"] = - { - }, -- end of ["weapons"] - ["OperatingLevel_Fuel"] = 10, - ["unlimitedAircrafts"] = true, - }, -- end of [32] - }, -- end of ["airports"] - ["warehouses"] = - { - }, -- end of ["warehouses"] -} -- end of warehouses diff --git a/Moose Test Missions/Moose_Test_GROUP_SwitchWayPoint/MOOSE_Test_GROUP_SwitchWayPoint.miz b/Moose Test Missions/Moose_Test_GROUP_SwitchWayPoint/MOOSE_Test_GROUP_SwitchWayPoint.miz deleted file mode 100644 index 30b9cf435..000000000 Binary files a/Moose Test Missions/Moose_Test_GROUP_SwitchWayPoint/MOOSE_Test_GROUP_SwitchWayPoint.miz and /dev/null differ diff --git a/Moose Test Missions/Moose_Test_GROUP_TaskFollow/Moose_Test_GROUP_TaskFollow.miz b/Moose Test Missions/Moose_Test_GROUP_TaskFollow/Moose_Test_GROUP_TaskFollow.miz deleted file mode 100644 index cfd60b143..000000000 Binary files a/Moose Test Missions/Moose_Test_GROUP_TaskFollow/Moose_Test_GROUP_TaskFollow.miz and /dev/null differ diff --git a/Moose Test Missions/Moose_Test_MENU_CLIENT/Moose_Test_MENU_CLIENT.miz b/Moose Test Missions/Moose_Test_MENU_CLIENT/Moose_Test_MENU_CLIENT.miz deleted file mode 100644 index d916af50a..000000000 Binary files a/Moose Test Missions/Moose_Test_MENU_CLIENT/Moose_Test_MENU_CLIENT.miz and /dev/null differ diff --git a/Moose Test Missions/Moose_Test_MENU_COALITION/Moose_Test_MENU_COALITION.miz b/Moose Test Missions/Moose_Test_MENU_COALITION/Moose_Test_MENU_COALITION.miz deleted file mode 100644 index 184f6e529..000000000 Binary files a/Moose Test Missions/Moose_Test_MENU_COALITION/Moose_Test_MENU_COALITION.miz and /dev/null differ diff --git a/Moose Test Missions/Moose_Test_MENU_GROUP/Moose_Test_MENU_GROUP.miz b/Moose Test Missions/Moose_Test_MENU_GROUP/Moose_Test_MENU_GROUP.miz deleted file mode 100644 index 6cf962f3d..000000000 Binary files a/Moose Test Missions/Moose_Test_MENU_GROUP/Moose_Test_MENU_GROUP.miz and /dev/null differ diff --git a/Moose Test Missions/Moose_Test_MISSILETRAINER/Moose_Test_MISSILETRAINER.miz b/Moose Test Missions/Moose_Test_MISSILETRAINER/Moose_Test_MISSILETRAINER.miz deleted file mode 100644 index f56629584..000000000 Binary files a/Moose Test Missions/Moose_Test_MISSILETRAINER/Moose_Test_MISSILETRAINER.miz and /dev/null differ diff --git a/Moose Test Missions/Moose_Test_PATROLZONE/MOOSE_Test_PATROLZONE.miz b/Moose Test Missions/Moose_Test_PATROLZONE/MOOSE_Test_PATROLZONE.miz deleted file mode 100644 index 49ae50a9d..000000000 Binary files a/Moose Test Missions/Moose_Test_PATROLZONE/MOOSE_Test_PATROLZONE.miz and /dev/null differ diff --git a/Moose Test Missions/Moose_Test_PATROLZONE/Moose_Test_PATROLZONE.lua b/Moose Test Missions/Moose_Test_PATROLZONE/Moose_Test_PATROLZONE.lua deleted file mode 100644 index 786a1f014..000000000 --- a/Moose Test Missions/Moose_Test_PATROLZONE/Moose_Test_PATROLZONE.lua +++ /dev/null @@ -1,8 +0,0 @@ - -local PatrolZoneGroup = GROUP:FindByName( "Patrol Zone" ) -local PatrolZone = ZONE_POLYGON:New( "PatrolZone", PatrolZoneGroup ) - -local PatrolGroup = GROUP:FindByName( "Patrol Group" ) - -local Patrol = PATROLZONE:New( PatrolGroup, PatrolZone, 3000, 6000, 300, 600 ) -Patrol:ManageFuel( 0.2, 60 ) \ No newline at end of file diff --git a/Moose Test Missions/Moose_Test_SCHEDULER/Moose_Test_SCHEDULER.lua b/Moose Test Missions/Moose_Test_SCHEDULER/Moose_Test_SCHEDULER.lua deleted file mode 100644 index 9113e32c9..000000000 --- a/Moose Test Missions/Moose_Test_SCHEDULER/Moose_Test_SCHEDULER.lua +++ /dev/null @@ -1,8 +0,0 @@ -GroupTest = GROUP:FindByName("Test") - -TestScheduler = SCHEDULER:New( nil, - function() - - MESSAGE:New("Hello World", 5 ):ToAll() - - end, {}, 10, 10 ) \ No newline at end of file diff --git a/Moose Test Missions/Moose_Test_SCHEDULER/Moose_Test_SCHEDULER.miz b/Moose Test Missions/Moose_Test_SCHEDULER/Moose_Test_SCHEDULER.miz deleted file mode 100644 index 5b2f8b10f..000000000 Binary files a/Moose Test Missions/Moose_Test_SCHEDULER/Moose_Test_SCHEDULER.miz and /dev/null differ diff --git a/Moose Test Missions/Moose_Test_SEAD/MOOSE_Test_SEAD.miz b/Moose Test Missions/Moose_Test_SEAD/MOOSE_Test_SEAD.miz deleted file mode 100644 index 9abf920ae..000000000 Binary files a/Moose Test Missions/Moose_Test_SEAD/MOOSE_Test_SEAD.miz and /dev/null differ diff --git a/Moose Test Missions/Moose_Test_SET_AIRBASE/Moose_Test_SET_AIRBASE.miz b/Moose Test Missions/Moose_Test_SET_AIRBASE/Moose_Test_SET_AIRBASE.miz deleted file mode 100644 index 7a3e88cd5..000000000 Binary files a/Moose Test Missions/Moose_Test_SET_AIRBASE/Moose_Test_SET_AIRBASE.miz and /dev/null differ diff --git a/Moose Test Missions/Moose_Test_SET_CLIENT/Moose_Test_SET_CLIENT.miz b/Moose Test Missions/Moose_Test_SET_CLIENT/Moose_Test_SET_CLIENT.miz deleted file mode 100644 index ec0d88122..000000000 Binary files a/Moose Test Missions/Moose_Test_SET_CLIENT/Moose_Test_SET_CLIENT.miz and /dev/null differ diff --git a/Moose Test Missions/Moose_Test_SET_GROUP/Moose_Test_SET_GROUP.miz b/Moose Test Missions/Moose_Test_SET_GROUP/Moose_Test_SET_GROUP.miz deleted file mode 100644 index 132056ea6..000000000 Binary files a/Moose Test Missions/Moose_Test_SET_GROUP/Moose_Test_SET_GROUP.miz and /dev/null differ diff --git a/Moose Test Missions/Moose_Test_SPAWN/MOOSE_Test_SPAWN.miz b/Moose Test Missions/Moose_Test_SPAWN/MOOSE_Test_SPAWN.miz deleted file mode 100644 index caa7395b4..000000000 Binary files a/Moose Test Missions/Moose_Test_SPAWN/MOOSE_Test_SPAWN.miz and /dev/null differ diff --git a/Moose Test Missions/Moose_Test_SPAWN/Moose_Test_SPAWN_CleanUp/MOOSE_Test_SPAWN_CleanUp.miz b/Moose Test Missions/Moose_Test_SPAWN/Moose_Test_SPAWN_CleanUp/MOOSE_Test_SPAWN_CleanUp.miz deleted file mode 100644 index 40c31ff8f..000000000 Binary files a/Moose Test Missions/Moose_Test_SPAWN/Moose_Test_SPAWN_CleanUp/MOOSE_Test_SPAWN_CleanUp.miz and /dev/null differ diff --git a/Moose Test Missions/Moose_Test_SPAWN/Moose_Test_SPAWN_Limit_Scheduled/MOOSE_Test_SPAWN_Limit_Scheduled.lua b/Moose Test Missions/Moose_Test_SPAWN/Moose_Test_SPAWN_Limit_Scheduled/MOOSE_Test_SPAWN_Limit_Scheduled.lua deleted file mode 100644 index 7eab6eb60..000000000 --- a/Moose Test Missions/Moose_Test_SPAWN/Moose_Test_SPAWN_Limit_Scheduled/MOOSE_Test_SPAWN_Limit_Scheduled.lua +++ /dev/null @@ -1,15 +0,0 @@ --- Tests Gudauta --- -------------- --- Limited spawning of groups, scheduled every 30 seconds ... -Spawn_Plane_Limited_Scheduled = SPAWN:New( "Spawn Plane Limited Scheduled" ):Limit( 4, 20 ):SpawnScheduled( 30, 0 ) -Spawn_Helicopter_Limited_Scheduled = SPAWN:New( "Spawn Helicopter Limited Scheduled" ):Limit( 4, 20 ):SpawnScheduled( 30, 0 ) -Spawn_Ground_Limited_Scheduled = SPAWN:New( "Spawn Vehicle Limited Scheduled" ):Limit( 4, 20 ):SpawnScheduled( 90, 0 ) - --- Tests Sukhumi --- ------------- --- Limited spawning of groups, scheduled every seconds with destruction. -Spawn_Plane_Limited_Scheduled_RandomizeRoute = SPAWN:New( "Spawn Plane Limited Scheduled Destroy" ):Limit( 4, 20 ):SpawnScheduled( 10, 0 ) -Spawn_Helicopter_Limited_Scheduled_RandomizeRoute = SPAWN:New( "Spawn Helicopter Limited Scheduled Destroy" ):Limit( 4, 20 ):SpawnScheduled( 10, 0 ) -Spawn_Vehicle_Limited_Scheduled_RandomizeRoute = SPAWN:New( "Spawn Vehicle Limited Scheduled Destroy" ):Limit( 4, 20 ):SpawnScheduled( 10, 0 ) - - diff --git a/Moose Test Missions/Moose_Test_SPAWN/Moose_Test_SPAWN_Limit_Scheduled/MOOSE_Test_SPAWN_Limit_Scheduled.miz b/Moose Test Missions/Moose_Test_SPAWN/Moose_Test_SPAWN_Limit_Scheduled/MOOSE_Test_SPAWN_Limit_Scheduled.miz deleted file mode 100644 index abd980dbe..000000000 Binary files a/Moose Test Missions/Moose_Test_SPAWN/Moose_Test_SPAWN_Limit_Scheduled/MOOSE_Test_SPAWN_Limit_Scheduled.miz and /dev/null differ diff --git a/Moose Test Missions/Moose_Test_SPAWN/Moose_Test_SPAWN_RandomizeTemplate/MOOSE_Test_SPAWN_RandomizeTemplate.lua b/Moose Test Missions/Moose_Test_SPAWN/Moose_Test_SPAWN_RandomizeTemplate/MOOSE_Test_SPAWN_RandomizeTemplate.lua deleted file mode 100644 index 0f20f3834..000000000 --- a/Moose Test Missions/Moose_Test_SPAWN/Moose_Test_SPAWN_RandomizeTemplate/MOOSE_Test_SPAWN_RandomizeTemplate.lua +++ /dev/null @@ -1,10 +0,0 @@ --- Tests Gudauta --- -------------- --- Limited spawning of groups, scheduled every 15 seconds, with RandomizeTemplate ... - -Templates = { "Template1", "Template2", "Template3", "Template4" } - -Spawn_Ground1 = SPAWN:New( "Spawn Vehicle1" ):Limit( 4, 20 ):RandomizeTemplate(Templates):SpawnScheduled( 15, 0 ) -Spawn_Ground2 = SPAWN:New( "Spawn Vehicle2" ):Limit( 4, 20 ):RandomizeTemplate(Templates):SpawnScheduled( 15, 0 ) - - diff --git a/Moose Test Missions/Moose_Test_SPAWN/Moose_Test_SPAWN_RandomizeTemplate/MOOSE_Test_SPAWN_RandomizeTemplate.miz b/Moose Test Missions/Moose_Test_SPAWN/Moose_Test_SPAWN_RandomizeTemplate/MOOSE_Test_SPAWN_RandomizeTemplate.miz deleted file mode 100644 index 574db1571..000000000 Binary files a/Moose Test Missions/Moose_Test_SPAWN/Moose_Test_SPAWN_RandomizeTemplate/MOOSE_Test_SPAWN_RandomizeTemplate.miz and /dev/null differ diff --git a/Moose Test Missions/Moose_Test_SPAWN/Moose_Test_SPAWN_Repeat/MOOSE_Test_SPAWN_Repeat.miz b/Moose Test Missions/Moose_Test_SPAWN/Moose_Test_SPAWN_Repeat/MOOSE_Test_SPAWN_Repeat.miz deleted file mode 100644 index 863f66df9..000000000 Binary files a/Moose Test Missions/Moose_Test_SPAWN/Moose_Test_SPAWN_Repeat/MOOSE_Test_SPAWN_Repeat.miz and /dev/null differ diff --git a/Moose Test Missions/Moose_Test_SPAWN/Moose_Test_SPAWN_SpawnFromStatic/Moose_Test_SPAWN_SpawnFromStatic.miz b/Moose Test Missions/Moose_Test_SPAWN/Moose_Test_SPAWN_SpawnFromStatic/Moose_Test_SPAWN_SpawnFromStatic.miz deleted file mode 100644 index 0b1e7209d..000000000 Binary files a/Moose Test Missions/Moose_Test_SPAWN/Moose_Test_SPAWN_SpawnFromStatic/Moose_Test_SPAWN_SpawnFromStatic.miz and /dev/null differ diff --git a/Moose Test Missions/Moose_Test_SPAWN/Moose_Test_SPAWN_SpawnFromUnit/Moose_Test_SPAWN_SpawnFromUnit.miz b/Moose Test Missions/Moose_Test_SPAWN/Moose_Test_SPAWN_SpawnFromUnit/Moose_Test_SPAWN_SpawnFromUnit.miz deleted file mode 100644 index e8d569882..000000000 Binary files a/Moose Test Missions/Moose_Test_SPAWN/Moose_Test_SPAWN_SpawnFromUnit/Moose_Test_SPAWN_SpawnFromUnit.miz and /dev/null differ diff --git a/Moose Test Missions/Moose_Test_SPAWN/Moose_Test_SPAWN_SpawnFromVec2/Moose_Test_SPAWN_SpawnFromVec2.miz b/Moose Test Missions/Moose_Test_SPAWN/Moose_Test_SPAWN_SpawnFromVec2/Moose_Test_SPAWN_SpawnFromVec2.miz deleted file mode 100644 index cff7bded1..000000000 Binary files a/Moose Test Missions/Moose_Test_SPAWN/Moose_Test_SPAWN_SpawnFromVec2/Moose_Test_SPAWN_SpawnFromVec2.miz and /dev/null differ diff --git a/Moose Test Missions/Moose_Test_SPAWN/Moose_Test_SPAWN_SpawnFromVec3/Moose_Test_SPAWN_SpawnFromVec3.miz b/Moose Test Missions/Moose_Test_SPAWN/Moose_Test_SPAWN_SpawnFromVec3/Moose_Test_SPAWN_SpawnFromVec3.miz deleted file mode 100644 index 1d24f86d2..000000000 Binary files a/Moose Test Missions/Moose_Test_SPAWN/Moose_Test_SPAWN_SpawnFromVec3/Moose_Test_SPAWN_SpawnFromVec3.miz and /dev/null differ diff --git a/Moose Test Missions/Moose_Test_TASK_Pickup_and_Deploy/MOOSE_Test_TASK_Pickup_and_Deploy.miz b/Moose Test Missions/Moose_Test_TASK_Pickup_and_Deploy/MOOSE_Test_TASK_Pickup_and_Deploy.miz deleted file mode 100644 index 15e4eb9bd..000000000 Binary files a/Moose Test Missions/Moose_Test_TASK_Pickup_and_Deploy/MOOSE_Test_TASK_Pickup_and_Deploy.miz and /dev/null differ diff --git a/Moose Test Missions/Moose_Test_TASK_SEAD/Moose_Test_TASK_SEAD.lua b/Moose Test Missions/Moose_Test_TASK_SEAD/Moose_Test_TASK_SEAD.lua deleted file mode 100644 index cb4f2a967..000000000 --- a/Moose Test Missions/Moose_Test_TASK_SEAD/Moose_Test_TASK_SEAD.lua +++ /dev/null @@ -1,16 +0,0 @@ - -local Mission = MISSION:New( 'SEAD Targets', "Strategic", "SEAD the enemy", coalition.side.RED ) -local Scoring = SCORING:New( "SEAD" ) - -Mission:AddScoring( Scoring ) - -local SEADGroup = GROUP:FindByName( "Test SEAD" ) -local TargetSet = SET_UNIT:New():FilterPrefixes( "US Hawk SR" ):FilterStart() - -local TargetZone = ZONE:New( "Target Zone" ) - -local TaskSEAD = TASK_SEAD:New( Mission, TargetSet, TargetZone ):SetName( "SEAD Radars" ):AssignToGroup( SEADGroup ) - - - - diff --git a/Moose Test Missions/Moose_Test_TASK_SEAD/Moose_Test_TASK_SEAD.miz b/Moose Test Missions/Moose_Test_TASK_SEAD/Moose_Test_TASK_SEAD.miz deleted file mode 100644 index 0e2ff3327..000000000 Binary files a/Moose Test Missions/Moose_Test_TASK_SEAD/Moose_Test_TASK_SEAD.miz and /dev/null differ diff --git a/Moose Test Missions/Moose_Test_WRAPPER/Moose_Test_WRAPPER.miz b/Moose Test Missions/Moose_Test_WRAPPER/Moose_Test_WRAPPER.miz deleted file mode 100644 index 6e8f846f7..000000000 Binary files a/Moose Test Missions/Moose_Test_WRAPPER/Moose_Test_WRAPPER.miz and /dev/null differ diff --git a/Moose Test Missions/Moose_Test_ZONE/Moose_Test_ZONE.miz b/Moose Test Missions/Moose_Test_ZONE/Moose_Test_ZONE.miz deleted file mode 100644 index b23403621..000000000 Binary files a/Moose Test Missions/Moose_Test_ZONE/Moose_Test_ZONE.miz and /dev/null differ diff --git a/Moose Test Missions/Moose_Test_ZONE_GROUP/Moose_Test_ZONE_GROUP.miz b/Moose Test Missions/Moose_Test_ZONE_GROUP/Moose_Test_ZONE_GROUP.miz deleted file mode 100644 index 49a954b3a..000000000 Binary files a/Moose Test Missions/Moose_Test_ZONE_GROUP/Moose_Test_ZONE_GROUP.miz and /dev/null differ diff --git a/Moose Test Missions/Moose_Test_ZONE_POLYGON/Moose_Test_ZONE_POLYGON.miz b/Moose Test Missions/Moose_Test_ZONE_POLYGON/Moose_Test_ZONE_POLYGON.miz deleted file mode 100644 index 9b297275b..000000000 Binary files a/Moose Test Missions/Moose_Test_ZONE_POLYGON/Moose_Test_ZONE_POLYGON.miz and /dev/null differ diff --git a/Moose Test Missions/Moose_Test_ZONE_RADIUS/Moose_Test_ZONE_RADIUS.miz b/Moose Test Missions/Moose_Test_ZONE_RADIUS/Moose_Test_ZONE_RADIUS.miz deleted file mode 100644 index 7929c864a..000000000 Binary files a/Moose Test Missions/Moose_Test_ZONE_RADIUS/Moose_Test_ZONE_RADIUS.miz and /dev/null differ diff --git a/Moose Test Missions/Moose_Test_ZONE_UNIT/Moose_Test_ZONE_UNIT.miz b/Moose Test Missions/Moose_Test_ZONE_UNIT/Moose_Test_ZONE_UNIT.miz deleted file mode 100644 index ee53864c0..000000000 Binary files a/Moose Test Missions/Moose_Test_ZONE_UNIT/Moose_Test_ZONE_UNIT.miz and /dev/null differ diff --git a/Moose Test Missions/PAT - Patrolling/PAT-001 - Switching Patrol Zones/PAT-001 - Switching Patrol Zones.lua b/Moose Test Missions/PAT - Patrolling/PAT-001 - Switching Patrol Zones/PAT-001 - Switching Patrol Zones.lua new file mode 100644 index 000000000..2c35574c8 --- /dev/null +++ b/Moose Test Missions/PAT - Patrolling/PAT-001 - Switching Patrol Zones/PAT-001 - Switching Patrol Zones.lua @@ -0,0 +1,80 @@ +-- This test mission models the behaviour of the AI_PATROLZONE class. +-- +-- It creates a 2 AI_PATROLZONE objects with the name Patrol1 and Patrol2. +-- Patrol1 will govern a GROUP object to patrol the zone defined by PatrolZone1, within 3000 meters and 6000 meters, within a speed of 400 and 600 km/h. +-- When the GROUP object that is assigned to Patrol has fuel below 20%, the GROUP object will orbit for 60 secondes, before returning to base. +-- +-- Patrol2 will goven a GROUP object to patrol the zone defined by PatrolZone2, within 600 meters and 1000 meters, within a speed of 300 and 400 km/h. +-- When the GROUP object that is assigned to Patrol has fuel below 20%, the GROUP object will orbit for 0 secondes, before returning to base. +-- +-- The Patrol1 and Patrol2 object have 2 state transition functions defined, which customize the default behaviour of the RTB state. +-- When Patrol1 goes RTB, it will create a new GROUP object, that will be assigned to Patrol2. +-- When Patrol2 goes RTB, it will create a new GROUP object, that will be assgined to Patrol1. +-- +-- In this way, the Patrol1 and Patrol2 objects are fluctuating the patrol pattern from PatrolZone1 and PatrolZone2 :-) + + +local PatrolZoneGroup1 = GROUP:FindByName( "Patrol Zone 1" ) +local PatrolZone1 = ZONE_POLYGON:New( "Patrol Zone 1", PatrolZoneGroup1 ) + +local PatrolZoneGroup2 = GROUP:FindByName( "Patrol Zone 2" ) +local PatrolZone2 = ZONE_POLYGON:New( "Patrol Zone 2", PatrolZoneGroup2 ) + +local PatrolSpawn = SPAWN:New( "Patrol Group" ) +local PatrolGroup = PatrolSpawn:Spawn() + +local Patrol1 = AI_PATROLZONE:New( PatrolZone1, 3000, 6000, 400, 600 ) +Patrol1:ManageFuel( 0.2, 60 ) +Patrol1:SetControllable( PatrolGroup ) +Patrol1:__Start( 5 ) + +local Patrol2 = AI_PATROLZONE:New( PatrolZone2, 600, 1000, 300, 400 ) +Patrol2:ManageFuel( 0.2, 0 ) + +--- State transition function for the PROCESS\_PATROLZONE **Patrol1** object +-- @param #AI_PATROLZONE self +-- @param Wrapper.Group#GROUP AIGroup +-- @return #boolean If false is returned, then the OnAfter state transition function will not be called. +function Patrol1:OnBeforeRTB( AIGroup ) + AIGroup:MessageToRed( "Returning to base", 20 ) +end + +--- State transition function for the PROCESS\_PATROLZONE **Patrol1** object +-- @param Process_PatrolCore.Zone#AI_PATROLZONE self +-- @param Wrapper.Group#GROUP AIGroup +function Patrol1:OnAfterRTB( AIGroup ) + local NewGroup = PatrolSpawn:Spawn() + Patrol2:SetControllable( NewGroup ) + Patrol2:__Start( 1 ) +end + +--- State transition function for the PROCESS\_PATROLZONE **Patrol1** object +-- @param Process_PatrolCore.Zone#AI_PATROLZONE self +-- @param Wrapper.Group#GROUP AIGroup +function Patrol1:OnAfterPatrol( AIGroup ) + AIGroup:MessageToRed( "Patrolling in zone " .. PatrolZone1:GetName() , 20 ) +end + +--- State transition function for the PROCESS\_PATROLZONE **Patrol2** object +-- @param #AI_PATROLZONE self +-- @param Wrapper.Group#GROUP AIGroup +-- @return #boolean If false is returned, then the OnAfter state transition function will not be called. +function Patrol2:OnBeforeRTB( AIGroup ) + AIGroup:MessageToRed( "Returning to base", 20 ) +end + +--- State transition function for the PROCESS\_PATROLZONE **Patrol2** object +-- @param Process_PatrolCore.Zone#AI_PATROLZONE self +-- @param Wrapper.Group#GROUP AIGroup +function Patrol2:OnAfterRTB( AIGroup ) + local NewGroup = PatrolSpawn:Spawn() + Patrol1:SetControllable( NewGroup ) + Patrol1:__Start( 1 ) +end + +--- State transition function for the PROCESS\_PATROLZONE **Patrol2** object +-- @param Process_PatrolCore.Zone#AI_PATROLZONE self +-- @param Wrapper.Group#GROUP AIGroup +function Patrol2:OnAfterPatrol( AIGroup ) + AIGroup:MessageToRed( "Patrolling in zone " .. PatrolZone2:GetName() , 20 ) +end diff --git a/Moose Test Missions/PAT - Patrolling/PAT-001 - Switching Patrol Zones/PAT-001 - Switching Patrol Zones.miz b/Moose Test Missions/PAT - Patrolling/PAT-001 - Switching Patrol Zones/PAT-001 - Switching Patrol Zones.miz new file mode 100644 index 000000000..a750a80e3 Binary files /dev/null and b/Moose Test Missions/PAT - Patrolling/PAT-001 - Switching Patrol Zones/PAT-001 - Switching Patrol Zones.miz differ diff --git a/Moose Test Missions/SCH - Scheduler/SCH-000 - Simple Scheduling/SCH-000 - Simple Scheduling.lua b/Moose Test Missions/SCH - Scheduler/SCH-000 - Simple Scheduling/SCH-000 - Simple Scheduling.lua new file mode 100644 index 000000000..be50637aa --- /dev/null +++ b/Moose Test Missions/SCH - Scheduler/SCH-000 - Simple Scheduling/SCH-000 - Simple Scheduling.lua @@ -0,0 +1,33 @@ +--- Simple function scheduling +-- +-- === +-- +-- Author: FlightControl +-- Date Created: 12 Dec 2016 +-- +-- # Situation +-- Uses the Tracing functions from BASE within the DCS.log file. Check the DCS.log file for the results. +-- Create a new SCHEDULER object. +-- Check the DCS.log. +-- +-- # Test cases: +-- +-- 1. The log should contain a "Hello World" line that is fired off 10 seconds after mission start. +-- +-- +-- # Status: TESTED - 12 Dec 2016 + +local TestScheduler = SCHEDULER:New( nil, + function() + BASE:E( "Hello World 1") + end, {}, 1 + ) + +SCHEDULER:New( nil, + function() + BASE:E( "Hello World 2") + end, {}, 2 + ) + +collectgarbage() + diff --git a/Moose Test Missions/SCH - Scheduler/SCH-000 - Simple Scheduling/SCH-000 - Simple Scheduling.miz b/Moose Test Missions/SCH - Scheduler/SCH-000 - Simple Scheduling/SCH-000 - Simple Scheduling.miz new file mode 100644 index 000000000..cdd67e9ba Binary files /dev/null and b/Moose Test Missions/SCH - Scheduler/SCH-000 - Simple Scheduling/SCH-000 - Simple Scheduling.miz differ diff --git a/Moose Test Missions/SCH - Scheduler/SCH-001 - Simple Object Scheduling/SCH-001 - Simple Object Scheduling.lua b/Moose Test Missions/SCH - Scheduler/SCH-001 - Simple Object Scheduling/SCH-001 - Simple Object Scheduling.lua new file mode 100644 index 000000000..e759b7017 --- /dev/null +++ b/Moose Test Missions/SCH - Scheduler/SCH-001 - Simple Object Scheduling/SCH-001 - Simple Object Scheduling.lua @@ -0,0 +1,34 @@ +--- Simple Object Scheduling +-- +-- === +-- +-- Author: FlightControl +-- Date Created: 12 Dec 2016 +-- +-- # Situation +-- Uses the Tracing functions from BASE within the DCS.log file. Check the DCS.log file for the results. +-- Create a new SCHEDULER object. +-- Check the DCS.log. +-- +-- # Test cases: +-- +-- 1. Tracing of a scheduler in an Object. +-- The log should contain a "Hello World" line of the object, that is fired off 1 seconds after mission start. +-- +-- # Status: TESTED - 12 Dec 2016 + +local TEST_BASE = { + ClassName = "TEST_BASE", + } + +function TEST_BASE:New( Message ) + self = BASE:Inherit( self, BASE:New() ) + + local TestScheduler = SCHEDULER:New( self, + function( Object, Message ) + Object:E( Message ) + end, { Message }, 1 + ) +end + +local Test = TEST_BASE:New( "Hello World" ) \ No newline at end of file diff --git a/Moose Test Missions/SCH - Scheduler/SCH-001 - Simple Object Scheduling/SCH-001 - Simple Object Scheduling.miz b/Moose Test Missions/SCH - Scheduler/SCH-001 - Simple Object Scheduling/SCH-001 - Simple Object Scheduling.miz new file mode 100644 index 000000000..4269e3141 Binary files /dev/null and b/Moose Test Missions/SCH - Scheduler/SCH-001 - Simple Object Scheduling/SCH-001 - Simple Object Scheduling.miz differ diff --git a/Moose Test Missions/SCH - Scheduler/SCH-100 - Simple Repeat Scheduling/SCH-100 - Simple Repeat Scheduling.lua b/Moose Test Missions/SCH - Scheduler/SCH-100 - Simple Repeat Scheduling/SCH-100 - Simple Repeat Scheduling.lua new file mode 100644 index 000000000..4e1c84183 --- /dev/null +++ b/Moose Test Missions/SCH - Scheduler/SCH-100 - Simple Repeat Scheduling/SCH-100 - Simple Repeat Scheduling.lua @@ -0,0 +1,24 @@ +--- Simple repeat scheduling of a function. +-- +-- === +-- +-- Author: FlightControl +-- Date Created: 13 Dec 2016 +-- +-- # Situation +-- Uses the Tracing functions from BASE within the DCS.log file. Check the DCS.log file for the results. +-- Create a new SCHEDULER object. +-- Check the DCS.log. +-- +-- # Test cases: +-- +-- 1. The log should contain "Hello World Repeat" lines that is fired off 1 second after mission start and is repeated every 1 seconds. +-- +-- +-- # Status: TESTED - 13 Dec 2016 + +local TestScheduler = SCHEDULER:New( nil, + function() + BASE:E( "Hello World Repeat") + end, {}, 1, 1 + ) \ No newline at end of file diff --git a/Moose Test Missions/SCH - Scheduler/SCH-100 - Simple Repeat Scheduling/SCH-100 - Simple Repeat Scheduling.miz b/Moose Test Missions/SCH - Scheduler/SCH-100 - Simple Repeat Scheduling/SCH-100 - Simple Repeat Scheduling.miz new file mode 100644 index 000000000..98263f585 Binary files /dev/null and b/Moose Test Missions/SCH - Scheduler/SCH-100 - Simple Repeat Scheduling/SCH-100 - Simple Repeat Scheduling.miz differ diff --git a/Moose Test Missions/SCH - Scheduler/SCH-110 - Object Repeat Scheduling/SCH-110 - Object Repeat Scheduling.lua b/Moose Test Missions/SCH - Scheduler/SCH-110 - Object Repeat Scheduling/SCH-110 - Object Repeat Scheduling.lua new file mode 100644 index 000000000..67f721125 --- /dev/null +++ b/Moose Test Missions/SCH - Scheduler/SCH-110 - Object Repeat Scheduling/SCH-110 - Object Repeat Scheduling.lua @@ -0,0 +1,44 @@ +--- Object Repeat Scheduling. +-- +-- === +-- +-- Author: FlightControl +-- Date Created: 13 Dec 2016 +-- +-- # Situation +-- Uses the Tracing functions from BASE within the DCS.log file. Check the DCS.log file for the results. +-- Create a new SCHEDULER object. +-- Check the DCS.log. +-- +-- Three Test objects are created. +-- +-- # Test cases: +-- +-- 1. Object Test1 should start after 1 seconds showing every second "Hello World Repeat 1". +-- 2. Object Test2 should start after 2 seconds showing every 2 seconds "Hello World Repeat 2" and stop after one minute. +-- 3. Object Test3 should start after 10 seconds showing with a 10 seconds randomized interval of 10 seconds "Hello World Repeat 3" and stop after one minute. +-- +-- # Status: TESTED - 13 Dec 2016 + +local TEST_BASE = { + ClassName = "TEST_BASE", + } + +function TEST_BASE:New( Message, Start, Repeat, Randomize, Stop ) + self = BASE:Inherit( self, BASE:New() ) + + self.TestScheduler = SCHEDULER:New( self, + function( Object, Message ) + Object:E( Message ) + end, { Message }, Start, Repeat, Randomize, Stop + ) + return self +end + +do +local Test1 = TEST_BASE:New( "Hello World Repeat 1", 1, 1 ) +end + +local Test2 = TEST_BASE:New( "Hello World Repeat 2", 2, 2, 0, 60 ) + +local Test3 = TEST_BASE:New( "Hello World Repeat 3", 10, 10, 1.0, 60 ) diff --git a/Moose Test Missions/SCH - Scheduler/SCH-110 - Object Repeat Scheduling/SCH-110 - Object Repeat Scheduling.miz b/Moose Test Missions/SCH - Scheduler/SCH-110 - Object Repeat Scheduling/SCH-110 - Object Repeat Scheduling.miz new file mode 100644 index 000000000..7967b4026 Binary files /dev/null and b/Moose Test Missions/SCH - Scheduler/SCH-110 - Object Repeat Scheduling/SCH-110 - Object Repeat Scheduling.miz differ diff --git a/Moose Test Missions/SCH - Scheduler/SCH-200 - Simple Repeat Scheduling Stop and Start/SCH-200 - Simple Repeat Scheduling Stop and Start.lua b/Moose Test Missions/SCH - Scheduler/SCH-200 - Simple Repeat Scheduling Stop and Start/SCH-200 - Simple Repeat Scheduling Stop and Start.lua new file mode 100644 index 000000000..107ba46d1 --- /dev/null +++ b/Moose Test Missions/SCH - Scheduler/SCH-200 - Simple Repeat Scheduling Stop and Start/SCH-200 - Simple Repeat Scheduling Stop and Start.lua @@ -0,0 +1,41 @@ +--- Simple repeat scheduling of a function. +-- +-- === +-- +-- Author: FlightControl +-- Date Created: 14 Dec 2016 +-- +-- # Situation +-- Uses the Tracing functions from BASE within the DCS.log file. Check the DCS.log file for the results. +-- Create a new SCHEDULER object. +-- Check the DCS.log. +-- +-- Start a schedule called TestScheduler. TestScheduler will repeat the words "Hello World Repeat" every second in the log. +-- After 10 seconds, TestScheduler will stop the scheduler. +-- After 20 seconds, TestScheduler will restart the scheduler. +-- +-- # Test cases: +-- +-- 1. Check that the "Hello World Repeat" lines are consistent with the scheduling timing. They should stop showing after 10 seconds, and restart after 20 seconds. +-- +-- +-- # Status: TESTED - 14 Dec 2016 + +local TestScheduler = SCHEDULER:New( nil, + function() + BASE:E( timer.getTime() .. " - Hello World Repeat") + end, {}, 1, 1 + ) + +SCHEDULER:New( nil, + function() + TestScheduler:Stop() + end, {}, 10 + ) + +SCHEDULER:New( nil, + function() + TestScheduler:Start() + end, {}, 20 + ) + \ No newline at end of file diff --git a/Moose Test Missions/SCH - Scheduler/SCH-200 - Simple Repeat Scheduling Stop and Start/SCH-200 - Simple Repeat Scheduling Stop and Start.miz b/Moose Test Missions/SCH - Scheduler/SCH-200 - Simple Repeat Scheduling Stop and Start/SCH-200 - Simple Repeat Scheduling Stop and Start.miz new file mode 100644 index 000000000..67dde599f Binary files /dev/null and b/Moose Test Missions/SCH - Scheduler/SCH-200 - Simple Repeat Scheduling Stop and Start/SCH-200 - Simple Repeat Scheduling Stop and Start.miz differ diff --git a/Moose Test Missions/SCH - Scheduler/SCH-300 - GC Simple Object Scheduling/SCH-300 - GC Simple Object Scheduling.lua b/Moose Test Missions/SCH - Scheduler/SCH-300 - GC Simple Object Scheduling/SCH-300 - GC Simple Object Scheduling.lua new file mode 100644 index 000000000..f5e5dab2d --- /dev/null +++ b/Moose Test Missions/SCH - Scheduler/SCH-300 - GC Simple Object Scheduling/SCH-300 - GC Simple Object Scheduling.lua @@ -0,0 +1,60 @@ +--- No Object Scheduling because of garbage collect and Object nillification. +-- +-- === +-- +-- Author: FlightControl +-- Date Created: 12 Dec 2016 +-- +-- # Situation +-- Uses the Tracing functions from BASE within the DCS.log file. Check the DCS.log file for the results. +-- Create a new SCHEDULER object. +-- Check the DCS.log. +-- +-- A Test object is created. +-- It is nillified directly after the Schedule has been planned. +-- There should be no schedule fired. +-- The Test object should be garbage collected! +-- +-- THIS IS A VERY IMPORTANT TEST! +-- +-- # Test cases: +-- +-- 1. No schedule should be fired! The destructors of the Test object should be shown. +-- 2. Commend the nillification of the Test object in the source, and test again. +-- The schedule should now be fired and Hello World should be logged through the Test object. +-- +-- # Status: STARTED - 12 Dec 2016 + +local TEST_BASE = { + ClassName = "TEST_BASE", + } + +function TEST_BASE:New( Message ) + self = BASE:Inherit( self, BASE:New() ) + + self.TestScheduler = SCHEDULER:New( self, + function( Object, Message ) + Object:E( Message ) + end, { Message }, 1 + ) + return self +end + +do +local Test1 = TEST_BASE:New( "Hello World Test 1" ) +Test1 = nil +BASE:E( Test1 ) +end + +local Test2 = TEST_BASE:New( "Hello World Test 2" ) +BASE:E( Test2 ) + +local Test3 = TEST_BASE:New( "Hello World Test 3" ) +Test3 = nil +BASE:E( Test3 ) + +collectgarbage() + +BASE:E( "Collect Garbage executed." ) +BASE:E( "You should only see a Hello Worlld message for Test 2!" ) +BASE:E( "Check if Test 1 and Test 3 are garbage collected!" ) \ No newline at end of file diff --git a/Moose Test Missions/SCH - Scheduler/SCH-300 - GC Simple Object Scheduling/SCH-300 - GC Simple Object Scheduling.miz b/Moose Test Missions/SCH - Scheduler/SCH-300 - GC Simple Object Scheduling/SCH-300 - GC Simple Object Scheduling.miz new file mode 100644 index 000000000..f689f3988 Binary files /dev/null and b/Moose Test Missions/SCH - Scheduler/SCH-300 - GC Simple Object Scheduling/SCH-300 - GC Simple Object Scheduling.miz differ diff --git a/Moose Test Missions/SCH - Scheduler/SCH-310 - GC Object Repeat Scheduling/SCH-310 - GC Object Repeat Scheduling.lua b/Moose Test Missions/SCH - Scheduler/SCH-310 - GC Object Repeat Scheduling/SCH-310 - GC Object Repeat Scheduling.lua new file mode 100644 index 000000000..54faeb602 --- /dev/null +++ b/Moose Test Missions/SCH - Scheduler/SCH-310 - GC Object Repeat Scheduling/SCH-310 - GC Object Repeat Scheduling.lua @@ -0,0 +1,80 @@ +--- Object Repeat Scheduling. +-- +-- === +-- +-- Author: FlightControl +-- Date Created: 13 Dec 2016 +-- +-- # Situation +-- Three objects Test1, Test2, Test 3 are created with schedule a function. +-- After 15 seconds, Test1 is nillified and Garbage Collect is done. +-- After 30 seconds, Test2 is nillified and Garbage Collect is done. +-- After 45 seconds, Test3 is nillified and Garbage Collect is done. +-- Uses the Tracing functions from BASE within the DCS.log file. Check the DCS.log file for the results. +-- Create a new SCHEDULER object. +-- Check the DCS.log. +-- +-- +-- Three Test objects are created. +-- +-- # Test cases: +-- +-- 1. Object Test1 should start after 1 seconds showing every second "Hello World Repeat 1". +-- 2. Object Test2 should start after 2 seconds showing every 2 seconds "Hello World Repeat 2" and stop after one minute. +-- 3. Object Test3 should start after 10 seconds showing with a 10 seconds randomized interval of 10 seconds "Hello World Repeat 3" and stop after one minute. +-- 4. After 15 seconds, Test1 should stop working. No "Hello World Repeat 1" may be shown after 15 seconds. +-- 5. After 30 seconds, Test2 should stop working. No "Hello World Repeat 2" may be shown after 30 seconds. +-- 6. After 45 seconds, Test3 should stop working. No "Hello World Repeat 3" may be shown after 45 seconds. +-- +-- # Status: TESTED - 13 Dec 2016 + +local TEST_BASE = { + ClassName = "TEST_BASE", + } + +function TEST_BASE:New( Message, Start, Repeat, Randomize, Stop ) + self = BASE:Inherit( self, BASE:New() ) + + self.TestScheduler = SCHEDULER:New( self, + function( Object, Message ) + Object:E( Message ) + end, { Message }, Start, Repeat, Randomize, Stop + ) + return self +end + +do +local Test1 = TEST_BASE:New( "Hello World Repeat 1", 1, 1 ) + +-- Nillify Test1 after 15 seconds and garbage collect. +local Nil1 = SCHEDULER:New( nil, + function() + BASE:E( "Nillify Test1 and Garbage Collect" ) + Test1 = nil + collectgarbage() + end, {}, 15 ) + +end + +local Test2 = TEST_BASE:New( "Hello World Repeat 2", 2, 2, 0, 60 ) + + +local Test3 = TEST_BASE:New( "Hello World Repeat 3", 10, 10, 1.0, 60 ) + +-- Nillify Test2 after 30 seconds and garbage collect. +local Nil2 = SCHEDULER:New( nil, + function() + BASE:E( "Nillify Test2 and Garbage Collect" ) + Test2 = nil + collectgarbage() + end, {}, 30 ) + +-- Nillify Test3 after 45 seconds and garbage collect. +local Nil3 = SCHEDULER:New( nil, + function() + BASE:E( "Nillify Test3 and Garbage Collect" ) + Test3 = nil + collectgarbage() + end, {}, 45 ) + +collectgarbage() diff --git a/Moose Test Missions/SCH - Scheduler/SCH-310 - GC Object Repeat Scheduling/SCH-310 - GC Object Repeat Scheduling.miz b/Moose Test Missions/SCH - Scheduler/SCH-310 - GC Object Repeat Scheduling/SCH-310 - GC Object Repeat Scheduling.miz new file mode 100644 index 000000000..e8130b2c7 Binary files /dev/null and b/Moose Test Missions/SCH - Scheduler/SCH-310 - GC Object Repeat Scheduling/SCH-310 - GC Object Repeat Scheduling.miz differ diff --git a/Moose Test Missions/Moose_Test_SET_AIRBASE/Moose_Test_SET_AIRBASE.lua b/Moose Test Missions/SET - Data Sets/SET-001 - Airbase Sets/SET-001 - Airbase Sets.lua similarity index 100% rename from Moose Test Missions/Moose_Test_SET_AIRBASE/Moose_Test_SET_AIRBASE.lua rename to Moose Test Missions/SET - Data Sets/SET-001 - Airbase Sets/SET-001 - Airbase Sets.lua diff --git a/Moose Test Missions/SET - Data Sets/SET-001 - Airbase Sets/SET-001 - Airbase Sets.miz b/Moose Test Missions/SET - Data Sets/SET-001 - Airbase Sets/SET-001 - Airbase Sets.miz new file mode 100644 index 000000000..e18bef886 Binary files /dev/null and b/Moose Test Missions/SET - Data Sets/SET-001 - Airbase Sets/SET-001 - Airbase Sets.miz differ diff --git a/Moose Test Missions/Moose_Test_SET_GROUP/Moose_Test_SET_GROUP.lua b/Moose Test Missions/SET - Data Sets/SET-101 - Group Sets/SET-101 - Group Sets.lua similarity index 78% rename from Moose Test Missions/Moose_Test_SET_GROUP/Moose_Test_SET_GROUP.lua rename to Moose Test Missions/SET - Data Sets/SET-101 - Group Sets/SET-101 - Group Sets.lua index be6eb4cad..534d78e34 100644 --- a/Moose Test Missions/Moose_Test_SET_GROUP/Moose_Test_SET_GROUP.lua +++ b/Moose Test Missions/SET - Data Sets/SET-101 - Group Sets/SET-101 - Group Sets.lua @@ -7,10 +7,10 @@ SetVehicles = SET_GROUP:New() SetVehicles:AddGroupsByName( { "Vehicle A", "Vehicle B", "Vehicle C" } ) SetVehicles:ForEachGroup( - --- @param Group#GROUP MooseGroup + --- @param Wrapper.Group#GROUP MooseGroup function( MooseGroup ) for UnitId, UnitData in pairs( MooseGroup:GetUnits() ) do - local UnitAction = UnitData -- Unit#UNIT + local UnitAction = UnitData -- Wrapper.Unit#UNIT UnitAction:SmokeGreen() end end @@ -47,12 +47,12 @@ GroupRU_Vehicle = SpawnRU_Vehicle:Spawn() SpawnRU_Ship = SPAWN:New( 'Spawn Test RUSSIA Ship') GroupRU_Ship = SpawnRU_Ship:Spawn() -SpawnM2A2_AttackVehicle = SPAWN:New( 'Spawn Test M2A2 Attack Vehicle' ) -SpawnSAM_AttackVehicle = SPAWN:New( 'Spawn Test SAM Attack Vehicle' ) +SpawnM2A2_AttackVehicle = SPAWN:New( 'Spawn Test M2A2 Attack Vehicle' ):InitRandomizeUnits( true, 10, 4 ) +SpawnSAM_AttackVehicle = SPAWN:New( 'Spawn Test SAM Attack Vehicle' ):InitRandomizeUnits( true, 10, 4 ) for i = 1, 30 do - GroupM2A2_AttackVehicle = SpawnM2A2_AttackVehicle:SpawnInZone( ZONE:New("Spawn Zone"), true) - GroupSAM_AttackVehicle = SpawnSAM_AttackVehicle:SpawnInZone( ZONE:New("Spawn Zone"), true) + GroupM2A2_AttackVehicle = SpawnM2A2_AttackVehicle:SpawnInZone( ZONE:New("Spawn Zone") ) + GroupSAM_AttackVehicle = SpawnSAM_AttackVehicle:SpawnInZone( ZONE:New("Spawn Zone") ) end SetVehicleCompletely = SET_GROUP:New() @@ -67,13 +67,13 @@ SetVehicleNot = SET_GROUP:New() :FilterPrefixes( "Spawn Vehicle Zone Not" ) :FilterStart() -Spawn_Vehicle_Zone_Completely = SPAWN:New( 'Spawn Vehicle Zone Completely' ) -Spawn_Vehicle_Zone_Partly = SPAWN:New( 'Spawn Vehicle Zone Partly' ) -Spawn_Vehicle_Zone_Not = SPAWN:New( 'Spawn Vehicle Zone Not' ) +Spawn_Vehicle_Zone_Completely = SPAWN:New( 'Spawn Vehicle Zone Completely' ):InitRandomizeUnits( true, 10, 4) +Spawn_Vehicle_Zone_Partly = SPAWN:New( 'Spawn Vehicle Zone Partly' ):InitRandomizeUnits( true, 10, 4 ) +Spawn_Vehicle_Zone_Not = SPAWN:New( 'Spawn Vehicle Zone Not' ):InitRandomizeUnits( true, 10, 4 ) for i = 1, 30 do - Spawn_Vehicle_Zone_Completely:SpawnInZone( ZONE:New("Spawn Zone Completely"), true) - Spawn_Vehicle_Zone_Partly:SpawnInZone( ZONE:New("Spawn Zone Partly"), true) - Spawn_Vehicle_Zone_Not:SpawnInZone( ZONE:New("Spawn Zone Not"), true) + Spawn_Vehicle_Zone_Completely:SpawnInZone( ZONE:New("Spawn Zone Completely") ) + Spawn_Vehicle_Zone_Partly:SpawnInZone( ZONE:New("Spawn Zone Partly") ) + Spawn_Vehicle_Zone_Not:SpawnInZone( ZONE:New("Spawn Zone Not") ) end --DBBlue:TraceDatabase() @@ -88,30 +88,30 @@ end --SCHEDULER:New( DBNorthKoreaGroup, DBNorthKoreaGroup.Flush, { }, 1 ) SetBluePlanesGroup:ForEachGroup( - --- @param Group#GROUP MooseGroup + --- @param Wrapper.Group#GROUP MooseGroup function( MooseGroup ) for UnitId, UnitData in pairs( MooseGroup:GetUnits() ) do - local UnitAction = UnitData -- Unit#UNIT + local UnitAction = UnitData -- Wrapper.Unit#UNIT UnitAction:SmokeBlue() end end ) SetNorthKoreaGroup:ForEachGroup( - --- @param Group#GROUP MooseGroup + --- @param Wrapper.Group#GROUP MooseGroup function( MooseGroup ) for UnitId, UnitData in pairs( MooseGroup:GetUnits() ) do - local UnitAction = UnitData -- Unit#UNIT + local UnitAction = UnitData -- Wrapper.Unit#UNIT UnitAction:SmokeRed() end end ) SetSAMGroup:ForEachGroup( - --- @param Group#GROUP MooseGroup + --- @param Wrapper.Group#GROUP MooseGroup function( MooseGroup ) for UnitId, UnitData in pairs( MooseGroup:GetUnits() ) do - local UnitAction = UnitData -- Unit#UNIT + local UnitAction = UnitData -- Wrapper.Unit#UNIT UnitAction:SmokeOrange() end end @@ -121,35 +121,35 @@ GroupZoneCompletely = GROUP:FindByName( "Zone Completely" ) GroupZonePartly = GROUP:FindByName( "Zone Partly" ) GroupZoneNot = GROUP:FindByName( "Zone Not" ) -ZoneCompletely = ZONE_POLYGON:New( "Zone Completely", GroupZoneCompletely ):SmokeZone( POINT_VEC3.SmokeColor.White ) -ZonePartly = ZONE_POLYGON:New( "Zone Partly", GroupZonePartly ):SmokeZone( POINT_VEC3.SmokeColor.White ) -ZoneNot = ZONE_POLYGON:New( "Zone Not", GroupZoneNot ):SmokeZone( POINT_VEC3.SmokeColor.White ) +ZoneCompletely = ZONE_POLYGON:New( "Zone Completely", GroupZoneCompletely ):SmokeZone( SMOKECOLOR.White ) +ZonePartly = ZONE_POLYGON:New( "Zone Partly", GroupZonePartly ):SmokeZone( SMOKECOLOR.White ) +ZoneNot = ZONE_POLYGON:New( "Zone Not", GroupZoneNot ):SmokeZone( SMOKECOLOR.White ) SetVehicleCompletely:ForEachGroupCompletelyInZone( ZoneCompletely, - --- @param Group#GROUP MooseGroup + --- @param Wrapper.Group#GROUP MooseGroup function( MooseGroup ) for UnitId, UnitData in pairs( MooseGroup:GetUnits() ) do - local UnitAction = UnitData -- Unit#UNIT + local UnitAction = UnitData -- Wrapper.Unit#UNIT UnitAction:SmokeBlue() end end ) SetVehiclePartly:ForEachGroupPartlyInZone( ZonePartly, - --- @param Group#GROUP MooseGroup + --- @param Wrapper.Group#GROUP MooseGroup function( MooseGroup ) for UnitId, UnitData in pairs( MooseGroup:GetUnits() ) do - local UnitAction = UnitData -- Unit#UNIT + local UnitAction = UnitData -- Wrapper.Unit#UNIT UnitAction:SmokeBlue() end end ) SetVehicleNot:ForEachGroupNotInZone( ZoneNot, - --- @param Group#GROUP MooseGroup + --- @param Wrapper.Group#GROUP MooseGroup function( MooseGroup ) for UnitId, UnitData in pairs( MooseGroup:GetUnits() ) do - local UnitAction = UnitData -- Unit#UNIT + local UnitAction = UnitData -- Wrapper.Unit#UNIT UnitAction:SmokeBlue() end end diff --git a/Moose Test Missions/SET - Data Sets/SET-101 - Group Sets/SET-101 - Group Sets.miz b/Moose Test Missions/SET - Data Sets/SET-101 - Group Sets/SET-101 - Group Sets.miz new file mode 100644 index 000000000..d3e1c04bd Binary files /dev/null and b/Moose Test Missions/SET - Data Sets/SET-101 - Group Sets/SET-101 - Group Sets.miz differ diff --git a/Moose Test Missions/Moose_Test_SET_CLIENT/Moose_Test_SET_CLIENT.lua b/Moose Test Missions/SET - Data Sets/SET-201 - Client Sets/SET-201 - Client Sets.lua similarity index 100% rename from Moose Test Missions/Moose_Test_SET_CLIENT/Moose_Test_SET_CLIENT.lua rename to Moose Test Missions/SET - Data Sets/SET-201 - Client Sets/SET-201 - Client Sets.lua diff --git a/Moose Test Missions/SET - Data Sets/SET-201 - Client Sets/SET-201 - Client Sets.miz b/Moose Test Missions/SET - Data Sets/SET-201 - Client Sets/SET-201 - Client Sets.miz new file mode 100644 index 000000000..cf7236875 Binary files /dev/null and b/Moose Test Missions/SET - Data Sets/SET-201 - Client Sets/SET-201 - Client Sets.miz differ diff --git a/Moose Test Missions/Moose_Test_SEAD/Moose_Test_SEAD.lua b/Moose Test Missions/SEV - SEAD Evasion/SEV-001 - SEAD Evasion/SEV-001 - SEAD Evasion.lua similarity index 100% rename from Moose Test Missions/Moose_Test_SEAD/Moose_Test_SEAD.lua rename to Moose Test Missions/SEV - SEAD Evasion/SEV-001 - SEAD Evasion/SEV-001 - SEAD Evasion.lua diff --git a/Moose Test Missions/SEV - SEAD Evasion/SEV-001 - SEAD Evasion/SEV-001 - SEAD Evasion.miz b/Moose Test Missions/SEV - SEAD Evasion/SEV-001 - SEAD Evasion/SEV-001 - SEAD Evasion.miz new file mode 100644 index 000000000..0a240c859 Binary files /dev/null and b/Moose Test Missions/SEV - SEAD Evasion/SEV-001 - SEAD Evasion/SEV-001 - SEAD Evasion.miz differ diff --git a/Moose Test Missions/Moose_Test_SPAWN/MOOSE_Test_SPAWN.lua b/Moose Test Missions/SPA - Spawning/SPA-010 - Spawn Demo/SPA-010 - Spawn Demo.lua similarity index 76% rename from Moose Test Missions/Moose_Test_SPAWN/MOOSE_Test_SPAWN.lua rename to Moose Test Missions/SPA - Spawning/SPA-010 - Spawn Demo/SPA-010 - Spawn Demo.lua index 433bc8850..a6a808e92 100644 --- a/Moose Test Missions/Moose_Test_SPAWN/MOOSE_Test_SPAWN.lua +++ b/Moose Test Missions/SPA - Spawning/SPA-010 - Spawn Demo/SPA-010 - Spawn Demo.lua @@ -7,15 +7,15 @@ Spawn_Plane = SPAWN:New( "Spawn Plane" ) Group_Plane = Spawn_Plane:Spawn() -Spawn_Helicopter = SPAWN:New( "Spawn Helicopter" ):RandomizeRoute( 1, 1, 1000 ) +Spawn_Helicopter = SPAWN:New( "Spawn Helicopter" ):InitRandomizeRoute( 1, 1, 1000 ) Group_Helicopter = Spawn_Helicopter:Spawn() Group_Helicopter2 = Spawn_Helicopter:Spawn() -Spawn_Ship = SPAWN:New( "Spawn Ship" ):RandomizeRoute( 0, 0, 2000 ) +Spawn_Ship = SPAWN:New( "Spawn Ship" ):InitRandomizeRoute( 0, 0, 2000 ) Group_Ship1 = Spawn_Ship:Spawn() Group_Ship2 = Spawn_Ship:Spawn() -Spawn_Vehicle = SPAWN:New( "Spawn Vehicle" ):RandomizeRoute( 1, 0, 50 ) +Spawn_Vehicle = SPAWN:New( "Spawn Vehicle" ):InitRandomizeRoute( 1, 0, 50 ) Group_Vehicle1 = Spawn_Vehicle:Spawn() Group_Vehicle2 = Spawn_Vehicle:Spawn() Group_Vehicle3 = Spawn_Vehicle:Spawn() @@ -49,27 +49,27 @@ Spawn_Vehicle_Scheduled = SPAWN:New( "Spawn Vehicle Scheduled" ):SpawnScheduled( -- Tests Tbilisi: Limited Spawning and repeat -- ------------------------------------------ -- Spawing one group, and respawning the same group when it lands ... -Spawn_Plane_Limited_Repeat = SPAWN:New( "Spawn Plane Limited Repeat" ):Limit( 1, 1 ):InitRepeat():Spawn() -Spawn_Plane_Limited_RepeatOnLanding = SPAWN:New( "Spawn Plane Limited RepeatOnLanding" ):Limit( 1, 1 ):InitRepeatOnLanding():Spawn() -Spawn_Plane_Limited_RepeatOnEngineShutDown = SPAWN:New( "Spawn Plane Limited RepeatOnEngineShutDown" ):Limit( 1, 1 ):InitRepeatOnEngineShutDown():Spawn() -Spawn_Helicopter_Limited_Repeat = SPAWN:New( "Spawn Helicopter Limited Repeat" ):Limit( 1, 1 ):InitRepeat():Spawn() -Spawn_Helicopter_Limited_RepeatOnLanding = SPAWN:New( "Spawn Helicopter Limited RepeatOnLanding" ):Limit( 1, 1 ):InitRepeatOnLanding():Spawn() -Spawn_Helicopter_Limited_RepeatOnEngineShutDown = SPAWN:New( "Spawn Helicopter Limited RepeatOnEngineShutDown" ):Limit( 1, 1 ):InitRepeatOnEngineShutDown():Spawn() +Spawn_Plane_Limited_Repeat = SPAWN:New( "Spawn Plane Limited Repeat" ):InitLimit( 1, 1 ):InitRepeat():Spawn() +Spawn_Plane_Limited_RepeatOnLanding = SPAWN:New( "Spawn Plane Limited RepeatOnLanding" ):InitLimit( 1, 1 ):InitRepeatOnLanding():Spawn() +Spawn_Plane_Limited_RepeatOnEngineShutDown = SPAWN:New( "Spawn Plane Limited RepeatOnEngineShutDown" ):InitLimit( 1, 1 ):InitRepeatOnEngineShutDown():Spawn() +Spawn_Helicopter_Limited_Repeat = SPAWN:New( "Spawn Helicopter Limited Repeat" ):InitLimit( 1, 1 ):InitRepeat():Spawn() +Spawn_Helicopter_Limited_RepeatOnLanding = SPAWN:New( "Spawn Helicopter Limited RepeatOnLanding" ):InitLimit( 1, 1 ):InitRepeatOnLanding():Spawn() +Spawn_Helicopter_Limited_RepeatOnEngineShutDown = SPAWN:New( "Spawn Helicopter Limited RepeatOnEngineShutDown" ):InitLimit( 1, 1 ):InitRepeatOnEngineShutDown():Spawn() -- Tests Soganlug -- -------------- -- Limited spawning of groups, scheduled every 30 seconds ... -Spawn_Plane_Limited_Scheduled = SPAWN:New( "Spawn Plane Limited Scheduled" ):Limit( 2, 10 ):SpawnScheduled( 30, 0 ) -Spawn_Helicopter_Limited_Scheduled = SPAWN:New( "Spawn Helicopter Limited Scheduled" ):Limit( 2, 10 ):SpawnScheduled( 30, 0 ) -Spawn_Ground_Limited_Scheduled = SPAWN:New( "Spawn Vehicle Limited Scheduled" ):Limit( 1, 20 ):SpawnScheduled( 90, 0 ) +Spawn_Plane_Limited_Scheduled = SPAWN:New( "Spawn Plane Limited Scheduled" ):InitLimit( 2, 10 ):SpawnScheduled( 30, 0 ) +Spawn_Helicopter_Limited_Scheduled = SPAWN:New( "Spawn Helicopter Limited Scheduled" ):InitLimit( 2, 10 ):SpawnScheduled( 30, 0 ) +Spawn_Ground_Limited_Scheduled = SPAWN:New( "Spawn Vehicle Limited Scheduled" ):InitLimit( 1, 20 ):SpawnScheduled( 90, 0 ) -- Tests Sukhumi -- ------------- -- Limited spawning of groups, scheduled every seconds with route randomization. -Spawn_Plane_Limited_Scheduled_RandomizeRoute = SPAWN:New( "Spawn Plane Limited Scheduled RandomizeRoute" ):Limit( 5, 10 ):RandomizeRoute( 1, 1, 4000 ):SpawnScheduled( 2, 0 ) -Spawn_Helicopter_Limited_Scheduled_RandomizeRoute = SPAWN:New( "Spawn Helicopter Limited Scheduled RandomizeRoute" ):Limit( 5, 10 ):RandomizeRoute( 1, 1, 4000 ):SpawnScheduled( 2, 0 ) -Spawn_Vehicle_Limited_Scheduled_RandomizeRoute = SPAWN:New( "Spawn Vehicle Limited Scheduled RandomizeRoute" ):Limit( 10, 10 ):RandomizeRoute( 1, 1, 1000 ):SpawnScheduled( 1, 0 ) +Spawn_Plane_Limited_Scheduled_RandomizeRoute = SPAWN:New( "Spawn Plane Limited Scheduled RandomizeRoute" ):InitLimit( 5, 10 ):InitRandomizeRoute( 1, 1, 4000 ):SpawnScheduled( 2, 0 ) +Spawn_Helicopter_Limited_Scheduled_RandomizeRoute = SPAWN:New( "Spawn Helicopter Limited Scheduled RandomizeRoute" ):InitLimit( 5, 10 ):InitRandomizeRoute( 1, 1, 4000 ):SpawnScheduled( 2, 0 ) +Spawn_Vehicle_Limited_Scheduled_RandomizeRoute = SPAWN:New( "Spawn Vehicle Limited Scheduled RandomizeRoute" ):InitLimit( 10, 10 ):InitRandomizeRoute( 1, 1, 1000 ):SpawnScheduled( 1, 0 ) -- Tests Kutaisi @@ -77,15 +77,15 @@ Spawn_Vehicle_Limited_Scheduled_RandomizeRoute = SPAWN:New( "Spawn Vehicle Limit -- Tests the CleanUp functionality. -- Limited spawning of groups, scheduled every 10 seconds, who are engaging into combat. Some helicopters may crash land on the ground. -- Observe when helicopters land but are not dead and are out of the danger zone, that they get removed after a while (+/- 180 seconds) and ReSpawn. -Spawn_Helicopter_Scheduled_CleanUp = SPAWN:New( "Spawn Helicopter Scheduled CleanUp" ):Limit( 3, 100 ):RandomizeRoute( 1, 1, 1000 ):CleanUp( 60 ):SpawnScheduled( 10, 0 ) -Spawn_Vehicle_Scheduled_CleanUp = SPAWN:New( "Spawn Vehicle Scheduled CleanUp" ):Limit( 3, 100 ):RandomizeRoute( 1, 1, 1000 ):SpawnScheduled( 10, 0 ) +Spawn_Helicopter_Scheduled_CleanUp = SPAWN:New( "Spawn Helicopter Scheduled CleanUp" ):InitLimit( 3, 100 ):InitRandomizeRoute( 1, 1, 1000 ):CleanUp( 60 ):SpawnScheduled( 10, 0 ) +Spawn_Vehicle_Scheduled_CleanUp = SPAWN:New( "Spawn Vehicle Scheduled CleanUp" ):InitLimit( 3, 100 ):InitRandomizeRoute( 1, 1, 1000 ):SpawnScheduled( 10, 0 ) -- Maykop -- ------ -- Creates arrays of groups ready to be spawned and dynamic spawning of groups from another group. -- SpawnTestVisible creates an array of 200 groups, every 20 groups with 20 meters space in between, and will activate a group of the array every 10 seconds with a 0.2 time randomization. -SpawnTestVisible = SPAWN:New( "Spawn Vehicle Visible Scheduled" ):Limit( 200, 200 ):Array( 59, 20, 30, 30 ):SpawnScheduled( 10, 0.2 ) +SpawnTestVisible = SPAWN:New( "Spawn Vehicle Visible Scheduled" ):InitLimit( 200, 200 ):InitArray( 59, 20, 30, 30 ):SpawnScheduled( 10, 0.2 ) -- Spawn_Templates_Visible contains different templates... Spawn_Templates_Visible = { "Spawn Vehicle Visible Template A", @@ -104,20 +104,20 @@ Spawn_Templates_Visible = { "Spawn Vehicle Visible Template A", -- and chooses for each group from the templates specified in Spawn_Templates_Visible. Spawn_Vehicle_Visible_RandomizeTemplate_Scheduled = SPAWN:New( "Spawn Vehicle Visible RandomizeTemplate Scheduled" ) - :Limit( 80, 80 ) - :RandomizeTemplate( Spawn_Templates_Visible ) - :RandomizeRoute( 1, 1, 300 ) - :Array( 49, 20, 8, 8 ) + :InitLimit( 80, 80 ) + :InitRandomizeTemplate( Spawn_Templates_Visible ) + :InitRandomizeRoute( 1, 1, 300 ) + :InitArray( 49, 20, 8, 8 ) :SpawnScheduled( 1, 0.2 ) -- Spawn_Infantry allows to spawn 10 Infantry groups. Spawn_Infantry = SPAWN:New( "Spawn Infantry" ) - :Limit( 10, 10 ) + :InitLimit( 10, 10 ) -- Spawn_Vehicle_Host reserves 10 vehicle groups, shown within an array arranged by 5 vehicles in a row with a distance of 8 meters, and schedules a vehicle each 10 seconds with a 20% variation. Spawn_Vehicle_Host = SPAWN:New( "Spawn Vehicle Host" ) - :Limit( 10, 10 ) - :Array( 0, 5, 8, 8 ) + :InitLimit( 10, 10 ) + :InitArray( 0, 5, 8, 8 ) :SpawnScheduled( 10, 0.2 ) -- Spawn_Vehicle_SpawnToZone allows to spawn 10 vehicle groups. @@ -128,7 +128,7 @@ Spawn_Vehicle_Host = SPAWN:New( "Spawn Vehicle Host" ) -- InfantryGroup:Route( InfantryRoute ) -- --------------------------------------------------------------------------------------------- Spawn_Vehicle_SpawnToZone = SPAWN:New( "Spawn Vehicle SpawnToZone" ) - :Limit( 10, 10 ) + :InitLimit( 10, 10 ) -- Spawn_Helicopter_SpawnToZone will fly to a location, hover, and spawn one vehicle on the ground, the helicopter will land -- and the vehicle will drive to a random location within the defined zone. @@ -139,7 +139,7 @@ Spawn_Vehicle_SpawnToZone = SPAWN:New( "Spawn Vehicle SpawnToZone" ) -- InfantryDropGroup:TaskRouteToZone( ZONE:New( "Target Zone" ), true, 80 ) -- ------------------------------------------------------------------------------------------------------ Spawn_Helicopter_SpawnToZone = SPAWN:New( "Spawn Helicopter SpawnToZone" ) - :Limit( 10, 10 ) + :InitLimit( 10, 10 ) :SpawnScheduled( 60, 0.2 ) diff --git a/Moose Test Missions/SPA - Spawning/SPA-010 - Spawn Demo/SPA-010 - Spawn Demo.miz b/Moose Test Missions/SPA - Spawning/SPA-010 - Spawn Demo/SPA-010 - Spawn Demo.miz new file mode 100644 index 000000000..39932e811 Binary files /dev/null and b/Moose Test Missions/SPA - Spawning/SPA-010 - Spawn Demo/SPA-010 - Spawn Demo.miz differ diff --git a/Moose Test Missions/Moose_Test_SPAWN/Moose_Test_SPAWN_CleanUp/MOOSE_Test_SPAWN_CleanUp.lua b/Moose Test Missions/SPA - Spawning/SPA-100 - CleanUp Inactive Units/SPA-100 - CleanUp Inactive Units.lua similarity index 69% rename from Moose Test Missions/Moose_Test_SPAWN/Moose_Test_SPAWN_CleanUp/MOOSE_Test_SPAWN_CleanUp.lua rename to Moose Test Missions/SPA - Spawning/SPA-100 - CleanUp Inactive Units/SPA-100 - CleanUp Inactive Units.lua index c0ff838b5..3c8c3967f 100644 --- a/Moose Test Missions/Moose_Test_SPAWN/Moose_Test_SPAWN_CleanUp/MOOSE_Test_SPAWN_CleanUp.lua +++ b/Moose Test Missions/SPA - Spawning/SPA-100 - CleanUp Inactive Units/SPA-100 - CleanUp Inactive Units.lua @@ -3,6 +3,6 @@ -- Tests the CleanUp functionality. -- Limited spawning of groups, scheduled every 10 seconds, who are engaging into combat. Some helicopters may crash land on the ground. -- Observe when helicopters land but are not dead and are out of the danger zone, that they get removed after a while (+/- 180 seconds) and ReSpawn. -Spawn_Helicopter_Scheduled_CleanUp = SPAWN:New( "Spawn Helicopter Scheduled CleanUp" ):Limit( 3, 100 ):RandomizeRoute( 1, 1, 1000 ):CleanUp( 60 ):SpawnScheduled( 10, 0 ) -Spawn_Vehicle_Scheduled_CleanUp = SPAWN:New( "Spawn Vehicle Scheduled CleanUp" ):Limit( 3, 100 ):RandomizeRoute( 1, 1, 1000 ):SpawnScheduled( 10, 0 ) +Spawn_Helicopter_Scheduled_CleanUp = SPAWN:New( "Spawn Helicopter Scheduled CleanUp" ):InitLimit( 3, 100 ):InitRandomizeRoute( 1, 1, 1000 ):CleanUp( 60 ):SpawnScheduled( 10, 0 ) +Spawn_Vehicle_Scheduled_CleanUp = SPAWN:New( "Spawn Vehicle Scheduled CleanUp" ):InitLimit( 3, 100 ):InitRandomizeRoute( 1, 1, 1000 ):SpawnScheduled( 10, 0 ) diff --git a/Moose Test Missions/SPA - Spawning/SPA-100 - CleanUp Inactive Units/SPA-100 - CleanUp Inactive Units.miz b/Moose Test Missions/SPA - Spawning/SPA-100 - CleanUp Inactive Units/SPA-100 - CleanUp Inactive Units.miz new file mode 100644 index 000000000..0245a4547 Binary files /dev/null and b/Moose Test Missions/SPA - Spawning/SPA-100 - CleanUp Inactive Units/SPA-100 - CleanUp Inactive Units.miz differ diff --git a/Moose Test Missions/SPA - Spawning/SPA-110 - Limit Spawning/SPA-110 - Limit Spawning.lua b/Moose Test Missions/SPA - Spawning/SPA-110 - Limit Spawning/SPA-110 - Limit Spawning.lua new file mode 100644 index 000000000..1ff4c90c0 --- /dev/null +++ b/Moose Test Missions/SPA - Spawning/SPA-110 - Limit Spawning/SPA-110 - Limit Spawning.lua @@ -0,0 +1,17 @@ +--- +-- Tests Gudauta +-- ------------- +-- Limited scheduled spawning of groups... +Spawn_Plane_Limited_Scheduled = SPAWN:New( "Spawn Plane Limited Scheduled" ):InitLimit( 4, 20 ):SpawnScheduled( 30, 0 ) +Spawn_Helicopter_Limited_Scheduled = SPAWN:New( "Spawn Helicopter Limited Scheduled" ):InitLimit( 4, 20 ):SpawnScheduled( 30, 0 ) +Spawn_Ground_Limited_Scheduled = SPAWN:New( "Spawn Vehicle Limited Scheduled" ):InitLimit( 4, 20 ):SpawnScheduled( 90, 0 ) + +--- +-- Tests Sukhumi +-- ------------- +-- Limited scheduled spawning of groups with destruction... +Spawn_Plane_Limited_Scheduled_RandomizeRoute = SPAWN:New( "Spawn Plane Limited Scheduled Destroy" ):InitLimit( 4, 20 ):SpawnScheduled( 10, 0 ) +Spawn_Helicopter_Limited_Scheduled_RandomizeRoute = SPAWN:New( "Spawn Helicopter Limited Scheduled Destroy" ):InitLimit( 4, 20 ):SpawnScheduled( 10, 0 ) +Spawn_Vehicle_Limited_Scheduled_RandomizeRoute = SPAWN:New( "Spawn Vehicle Limited Scheduled Destroy" ):InitLimit( 4, 20 ):SpawnScheduled( 10, 0 ) + + diff --git a/Moose Test Missions/SPA - Spawning/SPA-110 - Limit Spawning/SPA-110 - Limit Spawning.miz b/Moose Test Missions/SPA - Spawning/SPA-110 - Limit Spawning/SPA-110 - Limit Spawning.miz new file mode 100644 index 000000000..6b19bddef Binary files /dev/null and b/Moose Test Missions/SPA - Spawning/SPA-110 - Limit Spawning/SPA-110 - Limit Spawning.miz differ diff --git a/Moose Test Missions/Moose_Test_SPAWN/Moose_Test_SPAWN_Repeat/MOOSE_Test_SPAWN_Repeat.lua b/Moose Test Missions/SPA - Spawning/SPA-120 - Repeat Spawning/SPA-120 - Repeat Spawning.lua similarity index 94% rename from Moose Test Missions/Moose_Test_SPAWN/Moose_Test_SPAWN_Repeat/MOOSE_Test_SPAWN_Repeat.lua rename to Moose Test Missions/SPA - Spawning/SPA-120 - Repeat Spawning/SPA-120 - Repeat Spawning.lua index 76f282b8c..209ce7e16 100644 --- a/Moose Test Missions/Moose_Test_SPAWN/Moose_Test_SPAWN_Repeat/MOOSE_Test_SPAWN_Repeat.lua +++ b/Moose Test Missions/SPA - Spawning/SPA-120 - Repeat Spawning/SPA-120 - Repeat Spawning.lua @@ -6,11 +6,7 @@ -- Upon landing: -- 1. The KA-50 and the C-101EB should respawn itself directly when landed. -- 2. the MI-8MTV2 and the A-10C should respawn itself when the air unit has parked at the ramp. --- @module MOOSE_Test_SPAWN_Repeat --- @author FlightControl - - - +-- do diff --git a/Moose Test Missions/SPA - Spawning/SPA-120 - Repeat Spawning/SPA-120 - Repeat Spawning.miz b/Moose Test Missions/SPA - Spawning/SPA-120 - Repeat Spawning/SPA-120 - Repeat Spawning.miz new file mode 100644 index 000000000..40dbc6dd4 Binary files /dev/null and b/Moose Test Missions/SPA - Spawning/SPA-120 - Repeat Spawning/SPA-120 - Repeat Spawning.miz differ diff --git a/Moose Test Missions/SPA - Spawning/SPA-200 - Randomize Unit Types/SPA-200 - Randomize Unit Types.lua b/Moose Test Missions/SPA - Spawning/SPA-200 - Randomize Unit Types/SPA-200 - Randomize Unit Types.lua new file mode 100644 index 000000000..4302dd371 --- /dev/null +++ b/Moose Test Missions/SPA - Spawning/SPA-200 - Randomize Unit Types/SPA-200 - Randomize Unit Types.lua @@ -0,0 +1,11 @@ +--- +-- Tests Gudauta +-- -------------- +-- Limited and scheduled spawning of groups, with RandomizeTemplate ... + +Templates = { "Template1", "Template2", "Template3", "Template4" } + +Spawn_Ground1 = SPAWN:New( "Spawn Vehicle1" ):InitLimit( 4, 20 ):InitRandomizeTemplate(Templates):SpawnScheduled( 15, 0 ) +Spawn_Ground2 = SPAWN:New( "Spawn Vehicle2" ):InitLimit( 4, 20 ):InitRandomizeTemplate(Templates):SpawnScheduled( 15, 0 ) + + diff --git a/Moose Test Missions/SPA - Spawning/SPA-200 - Randomize Unit Types/SPA-200 - Randomize Unit Types.miz b/Moose Test Missions/SPA - Spawning/SPA-200 - Randomize Unit Types/SPA-200 - Randomize Unit Types.miz new file mode 100644 index 000000000..5aa4e2919 Binary files /dev/null and b/Moose Test Missions/SPA - Spawning/SPA-200 - Randomize Unit Types/SPA-200 - Randomize Unit Types.miz differ diff --git a/Moose Test Missions/SPA - Spawning/SPA-220 - Randomize Zones/SPA-220 - Randomize Zones.lua b/Moose Test Missions/SPA - Spawning/SPA-220 - Randomize Zones/SPA-220 - Randomize Zones.lua new file mode 100644 index 000000000..99388199b --- /dev/null +++ b/Moose Test Missions/SPA - Spawning/SPA-220 - Randomize Zones/SPA-220 - Randomize Zones.lua @@ -0,0 +1,51 @@ +-- This test will create 3 different zones of different types. +-- 100 groups of 1 unit will be spawned. +-- The test is about testing the zone randomization, and the place where the units are created. + +local Iterations = 100 +local Iteration = 1 + +-- The PolygonGroup route defines zone 1 +local ZonePolygonGroup = GROUP:FindByName( "ZonePolygon" ) + +-- The ZoneUnit defines zone 4. +local ZoneUnit = UNIT:FindByName( "ZoneUnit" ) + +-- The ZoneGroup defines zone 5 +local ZoneGroup = GROUP:FindByName( "ZoneGroup" ) + +-- This is the array that models the different zones types. +-- The selection of the zones is done by taking into account the probability of the zone. +-- The zone probabibility is 0 = 0%, 1 = 100% +-- The default value of the probability is 1. +-- Note that the SetZoneProbability is a method, that returns the self object of the zone, +-- allowing to use the method within the zone array declaration! +local SpawnZones = { + ZONE_POLYGON:New( "Zone 1", ZonePolygonGroup ):SetZoneProbability( 0.8 ), + ZONE_RADIUS:New( "Zone 2", ZONE:New( "GroundZone2" ):GetVec2(), 5000 ):SetZoneProbability( 0.2 ), + ZONE:New( "GroundZone3" ):SetZoneProbability( 0.2 ), + ZONE_UNIT:New( "Zone 4", ZoneUnit, 5000 ):SetZoneProbability( 0.6 ), + ZONE_GROUP:New( "Zone 5", ZoneGroup, 5000 ):SetZoneProbability( 0.4 ), + } + +HeightLimit = 500 + +SpawnGrounds = SPAWN + :New("Ground") + :InitLimit( 100, 100 ) + -- This method will randomize the selection of the zones for each spawned Group during initialization, + -- taking into account the probability factors. + -- When you explore the code behind this method, you'll see that the GetZoneMaybe() method is used to select "maybe" the zone. + :InitRandomizeZones( SpawnZones ) + +--- Spawns these groups slowly. +SCHEDULER:New( nil, + + function( Interation, Iterations ) + do + -- Spawn Ground + SpawnGrounds:Spawn() + end + + end, {}, 0, 1, 0 +) diff --git a/Moose Test Missions/SPA - Spawning/SPA-220 - Randomize Zones/SPA-220 - Randomize Zones.miz b/Moose Test Missions/SPA - Spawning/SPA-220 - Randomize Zones/SPA-220 - Randomize Zones.miz new file mode 100644 index 000000000..081a9339a Binary files /dev/null and b/Moose Test Missions/SPA - Spawning/SPA-220 - Randomize Zones/SPA-220 - Randomize Zones.miz differ diff --git a/Moose Test Missions/Moose_Test_SPAWN/Moose_Test_SPAWN_SpawnFromStatic/Moose_Test_SPAWN_SpawnFromStatic.lua b/Moose Test Missions/SPA - Spawning/SPA-310 - Spawn at Static position/SPA-310 - Spawn at Static position.lua similarity index 66% rename from Moose Test Missions/Moose_Test_SPAWN/Moose_Test_SPAWN_SpawnFromStatic/Moose_Test_SPAWN_SpawnFromStatic.lua rename to Moose Test Missions/SPA - Spawning/SPA-310 - Spawn at Static position/SPA-310 - Spawn at Static position.lua index c2d67f822..90df88ec7 100644 --- a/Moose Test Missions/Moose_Test_SPAWN/Moose_Test_SPAWN_SpawnFromStatic/Moose_Test_SPAWN_SpawnFromStatic.lua +++ b/Moose Test Missions/SPA - Spawning/SPA-310 - Spawn at Static position/SPA-310 - Spawn at Static position.lua @@ -9,10 +9,10 @@ ShipStatics = { "ShipStatic1", "ShipStatic2", "ShipStatic3" } HeightLimit = 500 -SpawnGrounds = SPAWN:New("Ground"):Limit( 20, 10 ) -SpawnAirplanes = SPAWN:New("Airplane"):Limit( 20, 10 ) -SpawnHelicopters = SPAWN:New("Helicopter"):Limit( 20, 10 ) -SpawnShips = SPAWN:New("Ship"):Limit( 20, 10 ) +SpawnGrounds = SPAWN:New("Ground"):InitLimit( 20, 10 ):InitRandomizeUnits( true, 500, 100 ) +SpawnAirplanes = SPAWN:New("Airplane"):InitLimit( 20, 10 ):InitRandomizeUnits( true, 500, 100 ) +SpawnHelicopters = SPAWN:New("Helicopter"):InitLimit( 20, 10 ):InitRandomizeUnits( true, 500, 100 ) +SpawnShips = SPAWN:New("Ship"):InitLimit( 20, 10 ):InitRandomizeUnits( true, 500, 100 ) --- Spawns these groups slowly. SCHEDULER:New( nil, @@ -22,28 +22,28 @@ SCHEDULER:New( nil, -- Spawn Ground local StaticName = GroundStatics[ math.random( 1, 3 ) ] local SpawnStatic = STATIC:FindByName( StaticName ) - SpawnGrounds:SpawnFromUnit( SpawnStatic, 500, 100 ) + SpawnGrounds:SpawnFromUnit( SpawnStatic ) end do -- Spawn Airplanes local StaticName = AirplaneStatics[ math.random( 1, 3 ) ] local SpawnStatic = STATIC:FindByName( StaticName ) - SpawnAirplanes:SpawnFromUnit( SpawnStatic, 500, 100 ) + SpawnAirplanes:SpawnFromUnit( SpawnStatic ) end do -- Spawn Helicopters local StaticName = HelicopterStatics[ math.random( 1, 3 ) ] local SpawnStatic = STATIC:FindByName( StaticName ) - SpawnHelicopters:SpawnFromUnit( SpawnStatic, 500, 100 ) + SpawnHelicopters:SpawnFromUnit( SpawnStatic ) end do -- Spawn Ships local StaticName = ShipStatics[ math.random( 1, 3 ) ] local SpawnStatic = STATIC:FindByName( StaticName ) - SpawnShips:SpawnFromUnit( SpawnStatic, 500, 100 ) + SpawnShips:SpawnFromUnit( SpawnStatic ) end end, {}, 0, 15, 0.5 diff --git a/Moose Test Missions/SPA - Spawning/SPA-310 - Spawn at Static position/SPA-310 - Spawn at Static position.miz b/Moose Test Missions/SPA - Spawning/SPA-310 - Spawn at Static position/SPA-310 - Spawn at Static position.miz new file mode 100644 index 000000000..6c446ec42 Binary files /dev/null and b/Moose Test Missions/SPA - Spawning/SPA-310 - Spawn at Static position/SPA-310 - Spawn at Static position.miz differ diff --git a/Moose Test Missions/Moose_Test_SPAWN/Moose_Test_SPAWN_SpawnFromUnit/Moose_Test_SPAWN_SpawnFromUnit.lua b/Moose Test Missions/SPA - Spawning/SPA-320 - Spawn at Unit position/SPA-320 - Spawn at Unit position.lua similarity index 79% rename from Moose Test Missions/Moose_Test_SPAWN/Moose_Test_SPAWN_SpawnFromUnit/Moose_Test_SPAWN_SpawnFromUnit.lua rename to Moose Test Missions/SPA - Spawning/SPA-320 - Spawn at Unit position/SPA-320 - Spawn at Unit position.lua index 57436b943..b9e1ae6f2 100644 --- a/Moose Test Missions/Moose_Test_SPAWN/Moose_Test_SPAWN_SpawnFromUnit/Moose_Test_SPAWN_SpawnFromUnit.lua +++ b/Moose Test Missions/SPA - Spawning/SPA-320 - Spawn at Unit position/SPA-320 - Spawn at Unit position.lua @@ -9,10 +9,10 @@ ShipUnits = { "ShipUnit1", "ShipUnit2", "ShipUnit3" } HeightLimit = 500 -SpawnGrounds = SPAWN:New("Ground"):Limit( 20, 10 ) -SpawnAirplanes = SPAWN:New("Airplane"):Limit( 20, 10 ) -SpawnHelicopters = SPAWN:New("Helicopter"):Limit( 20, 10 ) -SpawnShips = SPAWN:New("Ship"):Limit( 20, 10 ) +SpawnGrounds = SPAWN:New("Ground"):InitLimit( 20, 10 ):InitRandomizeUnits( true, 10, 3 ) +SpawnAirplanes = SPAWN:New("Airplane"):InitLimit( 20, 10 ) +SpawnHelicopters = SPAWN:New("Helicopter"):InitLimit( 20, 10 ) +SpawnShips = SPAWN:New("Ship"):InitLimit( 20, 10 ) --- Spawns these groups slowly. SCHEDULER:New( nil, @@ -22,7 +22,7 @@ SCHEDULER:New( nil, -- Spawn Ground local UnitName = GroundUnits[ math.random( 1, 3 ) ] local SpawnUnit = UNIT:FindByName( UnitName ) - SpawnGrounds:SpawnFromUnit( SpawnUnit, 10, 3 ) + SpawnGrounds:SpawnFromUnit( SpawnUnit ) end do diff --git a/Moose Test Missions/SPA - Spawning/SPA-320 - Spawn at Unit position/SPA-320 - Spawn at Unit position.miz b/Moose Test Missions/SPA - Spawning/SPA-320 - Spawn at Unit position/SPA-320 - Spawn at Unit position.miz new file mode 100644 index 000000000..1bc0f71d1 Binary files /dev/null and b/Moose Test Missions/SPA - Spawning/SPA-320 - Spawn at Unit position/SPA-320 - Spawn at Unit position.miz differ diff --git a/Moose Test Missions/Moose_Test_SPAWN/Moose_Test_SPAWN_SpawnFromVec3/Moose_Test_SPAWN_SpawnFromVec3.lua b/Moose Test Missions/SPA - Spawning/SPA-330 - Spawn at Vec2 position/SPA-330 - Spawn at Vec2 position.lua similarity index 52% rename from Moose Test Missions/Moose_Test_SPAWN/Moose_Test_SPAWN_SpawnFromVec3/Moose_Test_SPAWN_SpawnFromVec3.lua rename to Moose Test Missions/SPA - Spawning/SPA-330 - Spawn at Vec2 position/SPA-330 - Spawn at Vec2 position.lua index 524e6df52..43fdbe219 100644 --- a/Moose Test Missions/Moose_Test_SPAWN/Moose_Test_SPAWN_SpawnFromVec3/Moose_Test_SPAWN_SpawnFromVec3.lua +++ b/Moose Test Missions/SPA - Spawning/SPA-330 - Spawn at Vec2 position/SPA-330 - Spawn at Vec2 position.lua @@ -3,16 +3,18 @@ local Iterations = 10 local Iteration = 1 GroundZones = { "GroundZone1", "GroundZone2", "GroundZone3" } +GroundRandomizeZones = { "GroundRandomizeZone1", "GroundRandomizeZone2", "GroundRandomizeZone3" } AirplaneZones = { "AirplaneZone1", "AirplaneZone2", "AirplaneZone3" } HelicopterZones = { "HelicopterZone1", "HelicopterZone2", "HelicopterZone3" } ShipZones = { "ShipZone1", "ShipZone2", "ShipZone3" } HeightLimit = 500 -SpawnGrounds = SPAWN:New("Ground"):Limit( 20, 10 ) -SpawnAirplanes = SPAWN:New("Airplane"):Limit( 20, 10 ) -SpawnHelicopters = SPAWN:New("Helicopter"):Limit( 20, 10 ) -SpawnShips = SPAWN:New("Ship"):Limit( 20, 10 ) +SpawnGrounds = SPAWN:New("Ground"):InitLimit( 20, 10 ) +SpawnRandomizeGrounds = SPAWN:New("GroundRandomize"):InitLimit( 20, 10 ):InitRandomizeUnits( true, 500, 100 ) +SpawnAirplanes = SPAWN:New("Airplane"):InitLimit( 20, 10 ) +SpawnHelicopters = SPAWN:New("Helicopter"):InitLimit( 20, 10 ) +SpawnShips = SPAWN:New("Ship"):InitLimit( 20, 10 ) --- Spawns these groups slowly. SCHEDULER:New( nil, @@ -21,29 +23,36 @@ SCHEDULER:New( nil, do -- Spawn Ground local ZoneName = GroundZones[ math.random( 1, 3 ) ] - local SpawnVec3 = POINT_VEC3:NewFromVec3( ZONE:New( ZoneName ):GetPointVec3() ) - SpawnGrounds:SpawnFromVec3( SpawnVec3:GetVec3(), 500, 100 ) + local SpawnVec3 = POINT_VEC3:NewFromVec3( ZONE:New( ZoneName ):GetVec3() ) + SpawnGrounds:SpawnFromVec2( SpawnVec3:GetVec2() ) + end + + do + -- Spawn Ground Randomize + local ZoneName = GroundRandomizeZones[ math.random( 1, 3 ) ] + local SpawnVec3 = POINT_VEC3:NewFromVec3( ZONE:New( ZoneName ):GetVec3() ) + SpawnRandomizeGrounds:SpawnFromVec2( SpawnVec3:GetVec2() ) end do -- Spawn Airplanes local ZoneName = AirplaneZones[ math.random( 1, 3 ) ] - local SpawnVec3 = POINT_VEC3:NewFromVec3( ZONE:New( ZoneName ):GetPointVec3() ) - SpawnAirplanes:SpawnFromVec3( SpawnVec3:GetVec3(), 500, 100 ) + local SpawnVec3 = POINT_VEC3:NewFromVec3( ZONE:New( ZoneName ):GetVec3() ) + SpawnAirplanes:SpawnFromVec2( SpawnVec3:GetVec2() ) end do -- Spawn Helicopters local ZoneName = HelicopterZones[ math.random( 1, 3 ) ] - local SpawnVec3 = POINT_VEC3:NewFromVec3( ZONE:New( ZoneName ):GetPointVec3() ) - SpawnHelicopters:SpawnFromVec3( SpawnVec3:GetVec3(), 500, 100 ) + local SpawnVec3 = POINT_VEC3:NewFromVec3( ZONE:New( ZoneName ):GetVec3() ) + SpawnHelicopters:SpawnFromVec2( SpawnVec3:GetVec2() ) end do -- Spawn Ships local ZoneName = ShipZones[ math.random( 1, 3 ) ] - local SpawnVec3 = POINT_VEC3:NewFromVec3( ZONE:New( ZoneName ):GetPointVec3() ) - SpawnShips:SpawnFromVec3( SpawnVec3:GetVec3(), 500, 100 ) + local SpawnVec3 = POINT_VEC3:NewFromVec3( ZONE:New( ZoneName ):GetVec3() ) + SpawnShips:SpawnFromVec2( SpawnVec3:GetVec2() ) end end, {}, 0, 15, 0.5 diff --git a/Moose Test Missions/SPA - Spawning/SPA-330 - Spawn at Vec2 position/SPA-330 - Spawn at Vec2 position.miz b/Moose Test Missions/SPA - Spawning/SPA-330 - Spawn at Vec2 position/SPA-330 - Spawn at Vec2 position.miz new file mode 100644 index 000000000..85a58cd78 Binary files /dev/null and b/Moose Test Missions/SPA - Spawning/SPA-330 - Spawn at Vec2 position/SPA-330 - Spawn at Vec2 position.miz differ diff --git a/Moose Test Missions/Moose_Test_SPAWN/Moose_Test_SPAWN_SpawnFromVec2/Moose_Test_SPAWN_SpawnFromVec2.lua b/Moose Test Missions/SPA - Spawning/SPA-340 - Spawn at Vec3 position/SPA-340 - Spawn at Vec3 position.lua similarity index 52% rename from Moose Test Missions/Moose_Test_SPAWN/Moose_Test_SPAWN_SpawnFromVec2/Moose_Test_SPAWN_SpawnFromVec2.lua rename to Moose Test Missions/SPA - Spawning/SPA-340 - Spawn at Vec3 position/SPA-340 - Spawn at Vec3 position.lua index b68256f98..df0d253ef 100644 --- a/Moose Test Missions/Moose_Test_SPAWN/Moose_Test_SPAWN_SpawnFromVec2/Moose_Test_SPAWN_SpawnFromVec2.lua +++ b/Moose Test Missions/SPA - Spawning/SPA-340 - Spawn at Vec3 position/SPA-340 - Spawn at Vec3 position.lua @@ -3,16 +3,18 @@ local Iterations = 10 local Iteration = 1 GroundZones = { "GroundZone1", "GroundZone2", "GroundZone3" } +GroundRandomizeZones = { "GroundRandomizeZone1", "GroundRandomizeZone2", "GroundRandomizeZone3" } AirplaneZones = { "AirplaneZone1", "AirplaneZone2", "AirplaneZone3" } HelicopterZones = { "HelicopterZone1", "HelicopterZone2", "HelicopterZone3" } ShipZones = { "ShipZone1", "ShipZone2", "ShipZone3" } HeightLimit = 500 -SpawnGrounds = SPAWN:New("Ground"):Limit( 20, 10 ) -SpawnAirplanes = SPAWN:New("Airplane"):Limit( 20, 10 ) -SpawnHelicopters = SPAWN:New("Helicopter"):Limit( 20, 10 ) -SpawnShips = SPAWN:New("Ship"):Limit( 20, 10 ) +SpawnGrounds = SPAWN:New("Ground"):InitLimit( 20, 10 ) +SpawnRandomizeGrounds = SPAWN:New("GroundRandomize"):InitLimit( 20, 10 ):InitRandomizeUnits( true, 500, 100 ) +SpawnAirplanes = SPAWN:New("Airplane"):InitLimit( 20, 10 ) +SpawnHelicopters = SPAWN:New("Helicopter"):InitLimit( 20, 10 ) +SpawnShips = SPAWN:New("Ship"):InitLimit( 20, 10 ) --- Spawns these groups slowly. SCHEDULER:New( nil, @@ -21,29 +23,36 @@ SCHEDULER:New( nil, do -- Spawn Ground local ZoneName = GroundZones[ math.random( 1, 3 ) ] - local SpawnVec3 = POINT_VEC3:NewFromVec3( ZONE:New( ZoneName ):GetPointVec3() ) - SpawnGrounds:SpawnFromVec2( SpawnVec3:GetVec2(), 500, 100 ) + local SpawnVec3 = POINT_VEC3:NewFromVec3( ZONE:New( ZoneName ):GetVec3() ) + SpawnGrounds:SpawnFromVec3( SpawnVec3:GetVec3() ) + end + + do + -- Spawn Ground Randomize + local ZoneName = GroundRandomizeZones[ math.random( 1, 3 ) ] + local SpawnVec3 = POINT_VEC3:NewFromVec3( ZONE:New( ZoneName ):GetVec3() ) + SpawnRandomizeGrounds:SpawnFromVec3( SpawnVec3:GetVec3() ) end do -- Spawn Airplanes local ZoneName = AirplaneZones[ math.random( 1, 3 ) ] - local SpawnVec3 = POINT_VEC3:NewFromVec3( ZONE:New( ZoneName ):GetPointVec3() ) - SpawnAirplanes:SpawnFromVec2( SpawnVec3:GetVec2(), 500, 100 ) + local SpawnVec3 = POINT_VEC3:NewFromVec3( ZONE:New( ZoneName ):GetVec3() ) + SpawnAirplanes:SpawnFromVec3( SpawnVec3:GetVec3() ) end do -- Spawn Helicopters local ZoneName = HelicopterZones[ math.random( 1, 3 ) ] - local SpawnVec3 = POINT_VEC3:NewFromVec3( ZONE:New( ZoneName ):GetPointVec3() ) - SpawnHelicopters:SpawnFromVec2( SpawnVec3:GetVec2(), 500, 100 ) + local SpawnVec3 = POINT_VEC3:NewFromVec3( ZONE:New( ZoneName ):GetVec3() ) + SpawnHelicopters:SpawnFromVec3( SpawnVec3:GetVec3() ) end do -- Spawn Ships local ZoneName = ShipZones[ math.random( 1, 3 ) ] - local SpawnVec3 = POINT_VEC3:NewFromVec3( ZONE:New( ZoneName ):GetPointVec3() ) - SpawnShips:SpawnFromVec2( SpawnVec3:GetVec2(), 500, 100 ) + local SpawnVec3 = POINT_VEC3:NewFromVec3( ZONE:New( ZoneName ):GetVec3() ) + SpawnShips:SpawnFromVec3( SpawnVec3:GetVec3() ) end end, {}, 0, 15, 0.5 diff --git a/Moose Test Missions/SPA - Spawning/SPA-340 - Spawn at Vec3 position/SPA-340 - Spawn at Vec3 position.miz b/Moose Test Missions/SPA - Spawning/SPA-340 - Spawn at Vec3 position/SPA-340 - Spawn at Vec3 position.miz new file mode 100644 index 000000000..b95aba2de Binary files /dev/null and b/Moose Test Missions/SPA - Spawning/SPA-340 - Spawn at Vec3 position/SPA-340 - Spawn at Vec3 position.miz differ diff --git a/Moose Test Missions/TAD - Task Dispatching/TAD-010 - Task Dispatching Demo/TAD-010 - Task Dispatching Demo.lua b/Moose Test Missions/TAD - Task Dispatching/TAD-010 - Task Dispatching Demo/TAD-010 - Task Dispatching Demo.lua new file mode 100644 index 000000000..a28827b61 --- /dev/null +++ b/Moose Test Missions/TAD - Task Dispatching/TAD-010 - Task Dispatching Demo/TAD-010 - Task Dispatching Demo.lua @@ -0,0 +1,15 @@ + +local HQ = GROUP:FindByName( "HQ", "Bravo HQ" ) + +local CommandCenter = COMMANDCENTER:New( HQ ) + +local Scoring = SCORING:New( "Detect Demo" ) + +local Mission = MISSION:New( CommandCenter, "Overlord", "High", "Attack Detect Mission Briefing", coalition.side.RED ):AddScoring( Scoring ) + +local FACSet = SET_GROUP:New():FilterPrefixes( "FAC" ):FilterCoalitions("red"):FilterStart() +local FACDetection = DETECTION_AREAS:New( FACSet, 10000, 3000 ) + +local AttackGroups = SET_GROUP:New():FilterCoalitions( "red" ):FilterPrefixes( "Attack" ):FilterStart() +local TaskAssign = DETECTION_DISPATCHER:New( Mission, HQ, AttackGroups, FACDetection ) + diff --git a/Moose Test Missions/TAD - Task Dispatching/TAD-010 - Task Dispatching Demo/TAD-010 - Task Dispatching Demo.miz b/Moose Test Missions/TAD - Task Dispatching/TAD-010 - Task Dispatching Demo/TAD-010 - Task Dispatching Demo.miz new file mode 100644 index 000000000..0c0d7d78d Binary files /dev/null and b/Moose Test Missions/TAD - Task Dispatching/TAD-010 - Task Dispatching Demo/TAD-010 - Task Dispatching Demo.miz differ diff --git a/Moose Test Missions/TSK - Task Modelling/TSK-010 - Task Modelling - SEAD/TSK-010 - Task Modelling - SEAD.lua b/Moose Test Missions/TSK - Task Modelling/TSK-010 - Task Modelling - SEAD/TSK-010 - Task Modelling - SEAD.lua new file mode 100644 index 000000000..0b7662db9 --- /dev/null +++ b/Moose Test Missions/TSK - Task Modelling/TSK-010 - Task Modelling - SEAD/TSK-010 - Task Modelling - SEAD.lua @@ -0,0 +1,194 @@ +--- Task Modelling - SEAD +-- +-- === +-- +-- Author: FlightControl +-- Date Created: 15 Dec 2016 +-- +-- # Situation +-- +-- This test mission is a test bed for the TASKING framework. +-- It creates an head quarters (HQ), which contains one mission with one task to be accomplished. +-- When the pilot joins the plane, it will need to accept the task using the HQ menu. +-- Once the task is accepted, the group of the pilot will be assigned to the task. +-- The pilot will need to fly to the attack zone and elimitate all targets reported. +-- A smoking system is available that the pilot can use the acquire targets. +-- Once all targets are elimitated, the task is finished, and the mission is set to complete. +-- If the pilot crashes during flying, the task will fail, and the mission is set to failed. +-- +-- Uses the Tracing functions from BASE within the DCS.log file. Check the DCS.log file for the results. +-- Create a new SCHEDULER object. +-- Check the DCS.log. +-- +-- # Test cases: +-- +-- There should only be one Task listed under Others Menu -> HQ -> SEAD Targets -> SEAD. This is the TaskSEAD2, that is copied from TaskSEAD. +-- TaskSEAD is removed from the mission once TaskSEAD2 is created. +-- +-- ## Run this mission in DCS Single Player: +-- +-- * Once started, a slot. +-- * When in the plane, join the SEAD task through the Others Menu -> HQ -> SEAD Targets -> SEAD -> SEAD Radars Vector 2. +-- * When flying, watch the messages appear. It should say that you've been assigned to the task, and that you need to route your plane to a coordinate. +-- * Exit your plane by pressing ESC, and go back to the spectators. When in single player mode, just click on Back, and then click Spectators. +-- * Immediately rejoin a Slot, select an other plane. +-- * When in the plane, you should now not be able to join the Task. No menu options are given. That is because the Task is "Aborted". +-- * However, the aborted task is replanned within 30 seconds. As such, go back to spectators, and after 30 seconds, rejoin a slot in a plane. +-- * When in the plane, you should not be able to join the Task through the Others Menu -> HQ -> SEAD Targets -> SEAD -> SEAD Radars Vector 2. +-- * Once accepted, watch the messages appear. Route to the attach zone, following the coordinates. +-- * Once at the attack zone, you'll see a message how many targets are left to be destroyed. Attack the radar emitting SAM with a kh-25. +-- * When you HIT the SAM, you'll see a scoring message appear. One point is granted. +-- * Maybe you've fired two missiles, so, you'll see another HIT maybe on the SAM, again granting a point. +-- * When the SAM is DEAD (it may take a while), you'll see a scoring message that 10 points have been granted. +-- * You'll see a scoring message appear that grants 25 points because you've hit a target of the Task. (This was programmed below). +-- * You'll see a scoring message appear that grants 250 points because all Task targets have been elimitated. (This was also programmed below). +-- * You'll see a message appear that you have Task success. The Task will be flagged as 'Success', and cannot be joined anymore. +-- * You'll see a message appear that the Mission "SEAD Targets" has been "Completed". +-- +-- ## Run this mission in DCS Multiple Player, with one player: +-- +-- * Retry the above scenario, but now running this scenario on a multi player server, while connecting with one player to the mission. Watch the consistency of the messages. +-- +-- ## Run this mission in DCS Multiple Player, with two to three players simultaneously: +-- +-- * Retry the above scenario running this scenario on a multi player server, while connecting with two or three players to the mission. Watch the consistency of the messages. +-- * When the first player has accepted the Task, the 2nd and 3rd player joining the Task, will be automatically assigned to the Task. +-- +-- ## Others things to watch out for: +-- +-- * When flying to the attack zone, a message should appear every 30 seconds with the coordinates. +-- * When in the attack zone, a message should appear every 30 seconds how many targes are left within the task. +-- * When a player aborts the task, a message is displayed of the player aborting, but only to the group assigned to execute the task. +-- * When a player joins the task, a message is displayed of the player joining, but only to the group assigned to execute the task. +-- * When a player crashes into the ground, a message is displayed of that event. +-- * In multi player, when the Task was assigned to the group, but all players in that group aborted the Task, the Task should become Aborted. It will be replanned in 30 seconds. +-- +-- # Status: TESTING - 15 Dec 2016 + + +-- Create the HQ object. +local HQ = COMMANDCENTER:New( GROUP:FindByName( "HQ" ) ) + +-- MOOSE contains a MISSION class. Use the MISSION class to setup missions, containing tasks to be executed. +-- Create the Mission object, and attach the Mission to the HQ object. +-- The Mission accepts 4 parameters: +-- 1. The HQ object +-- 2. The name of the Mission +-- 3. The type of Mission, this can be any word like "Strategic", "Tactical", "Urgent", "Optional", "Secondary"... +-- 4. The briefing of the Mission. This briefing is shown when the pilot joins a Task within the Mission. +local Mission = MISSION:New( HQ, 'SEAD Targets', "Strategic", "SEAD the enemy" ) + + +-- MOOSE contains a SCORING class. Use the SCORING class to account the scores of achievements made by the pilots. +-- The scoring system is a standalone object, so here the Scoring object is created. +local Scoring = SCORING:New( "SEAD" ) + +-- The Scoring object is attached to the Mission object. +-- By doing this, now the Mission can set at defined states in tasks ( and in processes within the tasks ) scoring values, and a text. See later. +Mission:AddScoring( Scoring ) + +-- Define the set of group of planes that can be assigned to the Mission object. +local SEADSet = SET_GROUP:New():FilterPrefixes( "Test SEAD"):FilterStart() +SEADSet:Flush() + +-- Define the set of units that are the targets. +-- Note that I use FilterOnce, which means that the set will be defined only once, +-- and will not be continuously updated! +local TargetSet = SET_UNIT:New():FilterPrefixes( "US Hawk SR" ):FilterOnce() + +-- Define the zone to where the pilot needs to navigate. +local TargetZone = ZONE:New( "Target Zone" ) + +-- MOOSE contains a TASK class. Use the TASK class to define a new Task object and attach it to a Mission object. +-- Here we define a new TaskSEAD object, and attach it to the Mission object. +-- ( The TASK class is the base class for ALL derived Task templates. +-- Task templates are TASK classes that quickly setup a Task scenario with given parameters. ) +-- +-- The TASK class is thus the primary task, and a task scenario will need to be provided to the TaskSEAD of the states and events that form the task. +-- TASK gets a couple of parameters: +-- 1. The Mission for which the Task needs to be achieved. +-- 2. The set of groups of planes that pilots can join. +-- 3. The name of the Task... This can be any name, and will be provided when the Pilot joins the task. +-- 4. A type of the Task. When Tasks are in state Planned, then a menu can be provided that group the task based on this given type. +local SEADTask = TASK:New( Mission, SEADSet, "SEAD Radars Vector 1", "SEAD" ) -- Tasking.Task#TASK + +-- This is now an important part of the Task process definition. +-- Each TASK contains a "Process Template". +-- You need to define this process Template by added Actions and Processes, otherwise, the task won't do anything. +-- This call retrieves the Finite State Machine template of the Task. +-- This template WILL NEVER DIRECTLY BE EXECUTED. +-- But, when a Pilot joins a UNIT as defined within the SEADSet, the TaskSEAD will COPY the FsmSEAD to a NEW INTERNAL OBJECT and assign the COPIED FsmSEAD to the UNIT of the player. +-- There can be many copied FsmSEAD objects internally active within TaskSEAD, for each pilot that joined the Task one is instantiated. +-- The reason why this is done, is that each unit as a role within the Task, and can have different status. +-- Therefore, the FsmSEAD is a TEMPLATE PROCESS of the TASK, and must be designed as a UNIT with a player is executing that PROCESS. + +local SEADProcess = SEADTask:GetUnitProcess() + +-- Adding a new sub-process to the Task Template. +-- At first, the task needs to be accepted by a pilot. +-- We use for this the SUB-PROCESS ACT_ASSIGN_ACCEPT. +-- The method on the FsmSEAD AddProcess accepts the following parameters: +-- 1. State From "Planned". When the Fsm is in state "Planned", allow the event "Accept". +-- 2. Event "Accept". This event can be triggered through FsmSEAD:Accept() or FsmSEAD:__Accept( 1 ). See documentation on state machines. +-- 3. The PROCESS derived class. In this case, we use the ACT_ASSIGN_ACCEPT to accept the task and provide a briefing. So, when the event "Accept" is fired, this process is executed. +-- 4. A table with the "return" states of the ACT_ASSIGN_ACCEPT process. This table indicates that for a certain return state, a further event needs to be called. +-- 4.1 When the return state is Assigned, fire the event in the Task FsmSEAD:Route() +-- 4.2 When the return state is Rejected, fire the event in the Task FsmSEAD:Eject() +-- All other AddProcess calls are working in a similar manner. +SEADProcess:AddProcess ( "Planned", "Accept", ACT_ASSIGN_ACCEPT:New( "SEAD the Area" ), { Assigned = "Route", Rejected = "Eject" } ) + +-- Same, adding a process. +SEADProcess:AddProcess ( "Assigned", "Route", ACT_ROUTE_ZONE:New( TargetZone ), { Arrived = "Update" } ) + +-- Adding a new Action... +-- Actions define also the flow of the Task, but the actions will need to be programmed within your script. +-- See the state machine explanation for further details. +-- The AddTransition received a couple of parameters: +-- 1. State From "Rejected". When the FsmSEAD is in state "Rejected", the event "Eject" can be fired. +-- 2. Event "Eject". This event can be triggered synchronously through FsmSEAD:Eject() or asynchronously through FsmSEAD:__Eject(secs). +-- 3. State To "Planned". After the event has been fired, the FsmSEAD will transition to Planned. +SEADProcess:AddTransition ( "Rejected", "Eject", "Planned" ) +SEADProcess:AddTransition ( "Arrived", "Update", "Updated" ) +SEADProcess:AddProcess ( "Updated", "Account", ACT_ACCOUNT_DEADS:New( TargetSet, "SEAD" ), { Accounted = "Success" } ) +SEADProcess:AddProcess ( "Updated", "Smoke", ACT_ASSIST_SMOKE_TARGETS_ZONE:New( TargetSet, TargetZone ) ) +SEADProcess:AddTransition ( "Accounted", "Success", "Success" ) +SEADProcess:AddTransition ( "*", "Fail", "Failed" ) + +SEADProcess:AddScoreProcess( "Updated", "Account", "Account", "destroyed a radar", 25 ) +SEADProcess:AddScoreProcess( "Updated", "Account", "Failed", "failed to destroy a radar", -10 ) + +-- Now we will set the SCORING. Scoring is set using the TaskSEAD object. +-- Scores can be set on the status of the Task, and on Process level. +SEADProcess:AddScore( "Success", "Destroyed all target radars", 250 ) +SEADProcess:AddScore( "Failed", "Failed to destroy all target radars", -100 ) + +function SEADProcess:onenterUpdated( TaskUnit ) + self:E( { self } ) + self:Account() + self:Smoke() +end + +-- Here we handle the PlayerAborted event, which is fired when a Player leaves the unit while being assigned to the Task. +-- Within the event handler, which is passed the PlayerUnit and PlayerName parameter, +-- we check if the SEADTask has still AlivePlayers assigned to the Task. +-- If not, the Task will Abort. +-- And it will be Replanned within 30 seconds. +function SEADTask:OnAfterPlayerCrashed( PlayerUnit, PlayerName ) + if not SEADTask:HasAliveUnits() then + SEADTask:__Abort() + SEADTask:__Replan( 30 ) + end +end + + +local TaskSEAD2 = TASK:New( Mission, SEADSet, "SEAD Radars Vector 2", "SEAD" ) -- Tasking.Task#TASK +TaskSEAD2:SetUnitProcess( SEADTask:GetUnitProcess():Copy() ) +Mission:AddTask( TaskSEAD2 ) + +Mission:RemoveTask( SEADTask ) + +SEADTask = nil +SEADProcess = nil + + +collectgarbage() diff --git a/Moose Test Missions/TSK - Task Modelling/TSK-010 - Task Modelling - SEAD/TSK-010 - Task Modelling - SEAD.miz b/Moose Test Missions/TSK - Task Modelling/TSK-010 - Task Modelling - SEAD/TSK-010 - Task Modelling - SEAD.miz new file mode 100644 index 000000000..4226a86c8 Binary files /dev/null and b/Moose Test Missions/TSK - Task Modelling/TSK-010 - Task Modelling - SEAD/TSK-010 - Task Modelling - SEAD.miz differ diff --git a/Moose Test Missions/Moose_Test_TASK_Pickup_and_Deploy/MOOSE_Test_TASK_Pickup_and_Deploy.lua b/Moose Test Missions/TSK - Task Modelling/TSK-020 - Task Modelling - Pickup/TSK-020 - Task Modelling - Pickup.lua similarity index 90% rename from Moose Test Missions/Moose_Test_TASK_Pickup_and_Deploy/MOOSE_Test_TASK_Pickup_and_Deploy.lua rename to Moose Test Missions/TSK - Task Modelling/TSK-020 - Task Modelling - Pickup/TSK-020 - Task Modelling - Pickup.lua index 28bf2a9d8..e9a776183 100644 --- a/Moose Test Missions/Moose_Test_TASK_Pickup_and_Deploy/MOOSE_Test_TASK_Pickup_and_Deploy.lua +++ b/Moose Test Missions/TSK - Task Modelling/TSK-020 - Task Modelling - Pickup/TSK-020 - Task Modelling - Pickup.lua @@ -25,11 +25,11 @@ do Cargo_Pickup_Zone_2 = CARGO_ZONE:New( 'Pickup Zone 2', 'DE Communication Center 2' ):RedSmoke() for CargoItem = 1, 2 do - CargoTable[CargoItem] = CARGO_GROUP:New( 'Engineers', 'Team ' .. EngineerNames[CargoItem], math.random( 70, 100 ) * 3, 'DE Infantry', Cargo_Pickup_Zone_1 ) + CargoTable[CargoItem] = AI_CARGO_GROUP:New( 'Engineers', 'Team ' .. EngineerNames[CargoItem], math.random( 70, 100 ) * 3, 'DE Infantry', Cargo_Pickup_Zone_1 ) end for CargoItem = 3, 5 do - CargoTable[CargoItem] = CARGO_GROUP:New( 'Engineers', 'Team ' .. EngineerNames[CargoItem], math.random( 70, 100 ) * 3, 'DE Infantry', Cargo_Pickup_Zone_2 ) + CargoTable[CargoItem] = AI_CARGO_GROUP:New( 'Engineers', 'Team ' .. EngineerNames[CargoItem], math.random( 70, 100 ) * 3, 'DE Infantry', Cargo_Pickup_Zone_2 ) end --Cargo_Package = CARGO_INVISIBLE:New( 'Letter', 0.1, 'DE Secret Agent', 'Pickup Zone Package' ) @@ -68,7 +68,7 @@ do Package_Pickup_Zone = CARGO_ZONE:New( 'Package Pickup Zone', 'DE Guard' ):GreenSmoke() - Cargo_Package = CARGO_PACKAGE:New( 'Letter', 'Letter to Command', 0.1, Client_Package_1 ) + Cargo_Package = AI_CARGO_PACKAGE:New( 'Letter', 'Letter to Command', 0.1, Client_Package_1 ) --Cargo_Goods = CARGO_STATIC:New( 'Goods', 20, 'Goods', 'Pickup Zone Goods', 'DE Collection Point' ) --Cargo_SlingLoad = CARGO_SLING:New( 'Basket', 40, 'Basket', 'Pickup Zone Sling Load', 'DE Cargo Guard' ) diff --git a/Moose Test Missions/TSK - Task Modelling/TSK-020 - Task Modelling - Pickup/TSK-020 - Task Modelling - Pickup.miz b/Moose Test Missions/TSK - Task Modelling/TSK-020 - Task Modelling - Pickup/TSK-020 - Task Modelling - Pickup.miz new file mode 100644 index 000000000..1a15d084c Binary files /dev/null and b/Moose Test Missions/TSK - Task Modelling/TSK-020 - Task Modelling - Pickup/TSK-020 - Task Modelling - Pickup.miz differ diff --git a/Moose Test Missions/Moose_Test_ZONE/Moose_Test_ZONE.lua b/Moose Test Missions/ZON - Zones/ZON-100 - Normal Zone/ZON-100 - Normal Zone.lua similarity index 83% rename from Moose Test Missions/Moose_Test_ZONE/Moose_Test_ZONE.lua rename to Moose Test Missions/ZON - Zones/ZON-100 - Normal Zone/ZON-100 - Normal Zone.lua index cfdfbbf70..0cc8bca29 100644 --- a/Moose Test Missions/Moose_Test_ZONE/Moose_Test_ZONE.lua +++ b/Moose Test Missions/ZON - Zones/ZON-100 - Normal Zone/ZON-100 - Normal Zone.lua @@ -6,7 +6,7 @@ local GroupInside = GROUP:FindByName( "Test Inside Polygon" ) local GroupOutside = GROUP:FindByName( "Test Outside Polygon" ) -local ZoneA = ZONE:New( "Zone A" ):SmokeZone( POINT_VEC3.SmokeColor.White, 90 ) +local ZoneA = ZONE:New( "Zone A" ):SmokeZone( SMOKECOLOR.White, 90 ) Messager = SCHEDULER:New( nil, function() diff --git a/Moose Test Missions/ZON - Zones/ZON-100 - Normal Zone/ZON-100 - Normal Zone.miz b/Moose Test Missions/ZON - Zones/ZON-100 - Normal Zone/ZON-100 - Normal Zone.miz new file mode 100644 index 000000000..d5b6201d3 Binary files /dev/null and b/Moose Test Missions/ZON - Zones/ZON-100 - Normal Zone/ZON-100 - Normal Zone.miz differ diff --git a/Moose Test Missions/Moose_Test_ZONE_GROUP/Moose_Test_ZONE_GROUP.lua b/Moose Test Missions/ZON - Zones/ZON-200 - Group Zone/ZON-200 - Group Zone.lua similarity index 90% rename from Moose Test Missions/Moose_Test_ZONE_GROUP/Moose_Test_ZONE_GROUP.lua rename to Moose Test Missions/ZON - Zones/ZON-200 - Group Zone/ZON-200 - Group Zone.lua index 76a2ffb51..795853b5a 100644 --- a/Moose Test Missions/Moose_Test_ZONE_GROUP/Moose_Test_ZONE_GROUP.lua +++ b/Moose Test Missions/ZON - Zones/ZON-200 - Group Zone/ZON-200 - Group Zone.lua @@ -15,6 +15,6 @@ Messager = SCHEDULER:New( nil, TankZoneColoring = SCHEDULER:New( nil, function() - ZoneA:FlareZone( POINT_VEC3.FlareColor.White, 90, 60 ) + ZoneA:FlareZone( FLARECOLOR.White, 90, 60 ) end, {}, 0, 5 ) \ No newline at end of file diff --git a/Moose Test Missions/ZON - Zones/ZON-200 - Group Zone/ZON-200 - Group Zone.miz b/Moose Test Missions/ZON - Zones/ZON-200 - Group Zone/ZON-200 - Group Zone.miz new file mode 100644 index 000000000..ec10bb063 Binary files /dev/null and b/Moose Test Missions/ZON - Zones/ZON-200 - Group Zone/ZON-200 - Group Zone.miz differ diff --git a/Moose Test Missions/Moose_Test_ZONE_UNIT/Moose_Test_ZONE_UNIT.lua b/Moose Test Missions/ZON - Zones/ZON-300 - Unit Zone/ZON-300 - Unit Zone.lua similarity index 90% rename from Moose Test Missions/Moose_Test_ZONE_UNIT/Moose_Test_ZONE_UNIT.lua rename to Moose Test Missions/ZON - Zones/ZON-300 - Unit Zone/ZON-300 - Unit Zone.lua index a034ab8d5..20790003f 100644 --- a/Moose Test Missions/Moose_Test_ZONE_UNIT/Moose_Test_ZONE_UNIT.lua +++ b/Moose Test Missions/ZON - Zones/ZON-300 - Unit Zone/ZON-300 - Unit Zone.lua @@ -1,8 +1,4 @@ - - - - local GroupInside = GROUP:FindByName( "Test Inside Polygon" ) local GroupOutside = GROUP:FindByName( "Test Outside Polygon" ) @@ -20,6 +16,6 @@ Messager = SCHEDULER:New( nil, TankZoneColoring = SCHEDULER:New( nil, function() - ZoneA:FlareZone( POINT_VEC3.FlareColor.White, 90, 60 ) + ZoneA:FlareZone( FLARECOLOR.White, 90, 60 ) end, {}, 0, 5 ) \ No newline at end of file diff --git a/Moose Test Missions/ZON - Zones/ZON-300 - Unit Zone/ZON-300 - Unit Zone.miz b/Moose Test Missions/ZON - Zones/ZON-300 - Unit Zone/ZON-300 - Unit Zone.miz new file mode 100644 index 000000000..7a4a994e6 Binary files /dev/null and b/Moose Test Missions/ZON - Zones/ZON-300 - Unit Zone/ZON-300 - Unit Zone.miz differ diff --git a/Moose Test Missions/Moose_Test_ZONE_RADIUS/Moose_Test_ZONE_RADIUS.lua b/Moose Test Missions/ZON - Zones/ZON-400 - Radius Zone/ZON-400 - Radius Zone.lua similarity index 91% rename from Moose Test Missions/Moose_Test_ZONE_RADIUS/Moose_Test_ZONE_RADIUS.lua rename to Moose Test Missions/ZON - Zones/ZON-400 - Radius Zone/ZON-400 - Radius Zone.lua index 34d4c7699..7eb347e91 100644 --- a/Moose Test Missions/Moose_Test_ZONE_RADIUS/Moose_Test_ZONE_RADIUS.lua +++ b/Moose Test Missions/ZON - Zones/ZON-400 - Radius Zone/ZON-400 - Radius Zone.lua @@ -7,7 +7,7 @@ local GroupInside = GROUP:FindByName( "Test Inside Polygon" ) local GroupOutside = GROUP:FindByName( "Test Outside Polygon" ) local House = STATIC:FindByName( "House" ) -local ZoneA = ZONE_RADIUS:New( "Zone A", House:GetPointVec2(), 300 ):SmokeZone( POINT_VEC3.SmokeColor.White, 90 ) +local ZoneA = ZONE_RADIUS:New( "Zone A", House:GetPointVec2(), 300 ):SmokeZone( SMOKECOLOR.White, 90 ) Messager = SCHEDULER:New( nil, function() diff --git a/Moose Test Missions/ZON - Zones/ZON-400 - Radius Zone/ZON-400 - Radius Zone.miz b/Moose Test Missions/ZON - Zones/ZON-400 - Radius Zone/ZON-400 - Radius Zone.miz new file mode 100644 index 000000000..9319318e6 Binary files /dev/null and b/Moose Test Missions/ZON - Zones/ZON-400 - Radius Zone/ZON-400 - Radius Zone.miz differ diff --git a/Moose Test Missions/Moose_Test_ZONE_POLYGON/Moose_Test_ZONE_POLYGON.lua b/Moose Test Missions/ZON - Zones/ZON-500 - Polygon Zone/ZON-500 - Polygon Zone.lua similarity index 92% rename from Moose Test Missions/Moose_Test_ZONE_POLYGON/Moose_Test_ZONE_POLYGON.lua rename to Moose Test Missions/ZON - Zones/ZON-500 - Polygon Zone/ZON-500 - Polygon Zone.lua index 55404345d..e38d859ee 100644 --- a/Moose Test Missions/Moose_Test_ZONE_POLYGON/Moose_Test_ZONE_POLYGON.lua +++ b/Moose Test Missions/ZON - Zones/ZON-500 - Polygon Zone/ZON-500 - Polygon Zone.lua @@ -8,7 +8,7 @@ local GroupOutside = GROUP:FindByName( "Test Outside Polygon" ) local GroupPolygon = GROUP:FindByName( "Polygon A" ) -local PolygonZone = ZONE_POLYGON:New( "Polygon A", GroupPolygon ):SmokeZone( POINT_VEC3.SmokeColor.White, 20 ) +local PolygonZone = ZONE_POLYGON:New( "Polygon A", GroupPolygon ):SmokeZone( SMOKECOLOR.White, 20 ) Messager = SCHEDULER:New( nil, function() diff --git a/Moose Test Missions/ZON - Zones/ZON-500 - Polygon Zone/ZON-500 - Polygon Zone.miz b/Moose Test Missions/ZON - Zones/ZON-500 - Polygon Zone/ZON-500 - Polygon Zone.miz new file mode 100644 index 000000000..74f3d898f Binary files /dev/null and b/Moose Test Missions/ZON - Zones/ZON-500 - Polygon Zone/ZON-500 - Polygon Zone.miz differ diff --git a/Moose Training/Documentation/AIBalancer.html b/Moose Training/Documentation/AIBalancer.html index 8156c7979..530e7a3be 100644 --- a/Moose Training/Documentation/AIBalancer.html +++ b/Moose Training/Documentation/AIBalancer.html @@ -18,10 +18,12 @@
@@ -100,7 +91,8 @@

1) AIBalancer#AIBALANCER class, extends Base#BASE

The AIBalancer#AIBALANCER class controls the dynamic spawning of AI GROUPS depending on a SETCLIENT. -There will be as many AI GROUPS spawned as there at CLIENTS in SETCLIENT not spawned.

+There will be as many AI GROUPS spawned as there at CLIENTS in SETCLIENT not spawned. +The AIBalancer uses the PatrolZone#PATROLZONE class to make AI patrol an zone until the fuel treshold is reached.

1.1) AIBALANCER construction method:

Create a new AIBALANCER object with the AIBALANCER.New method:

@@ -122,13 +114,35 @@ There will be as many AI GROUPS spawned as there at CLIENTS in SETCLIENT no
+

API CHANGE HISTORY

+ +

The underlying change log documents the API changes. Please read this carefully. The following notation is used:

+ +
    +
  • Added parts are expressed in bold type face.
  • +
  • Removed parts are expressed in italic type face.
  • +
+ +

Hereby the change log:

+ +

2016-08-17: SPAWN:InitCleanUp( SpawnCleanUpInterval ) replaces SPAWN:CleanUp( SpawnCleanUpInterval )

+ +
    +
  • Want to ensure that the methods starting with Init are the first called methods before any Spawn method is called!
  • +
  • This notation makes it now more clear which methods are initialization methods and which methods are Spawn enablement methods.
  • +
+ +
+ +

AUTHORS and CONTRIBUTIONS

+

Contributions:

    -
  • Dutch_Baron (James) Who you can search on the Eagle Dynamics Forums.
    +

  • Dutch_Baron (James): Who you can search on the Eagle Dynamics Forums.
    Working together with James has resulted in the creation of the AIBALANCER class.
    James has shared his ideas on balancing AI with air units, and together we made a first design which you can use now :-)

  • -
  • SNAFU +

  • SNAFU: Had a couple of mails with the guys to validate, if the same concept in the GCI/CAP script could be reworked within MOOSE. None of the script code has been used however within the new AIBALANCER moose class.

@@ -136,7 +150,7 @@ There will be as many AI GROUPS spawned as there at CLIENTS in SETCLIENT no

Authors:

    -
  • FlightControl - Framework Design & Programming
  • +
  • FlightControl: Framework Design & Programming
@@ -161,6 +175,12 @@ There will be as many AI GROUPS spawned as there at CLIENTS in SETCLIENT no AIBALANCER.ClassName + + + + AIBALANCER:GetPatrolZone() + +

Get the PatrolZone object assigned by the AIBalancer object.

@@ -212,7 +232,7 @@ There will be as many AI GROUPS spawned as there at CLIENTS in SETCLIENT no - AIBALANCER:SetPatrolZone(PatrolZone) + AIBALANCER:SetPatrolZone(PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed)

Let the AI patrol a Zone with a given Speed range and Altitude range.

@@ -291,6 +311,24 @@ There will be as many AI GROUPS spawned as there at CLIENTS in SETCLIENT no + + +
+
+ + +AIBALANCER:GetPatrolZone() + +
+
+ +

Get the PatrolZone object assigned by the AIBalancer object.

+ +

Return value

+ +

PatrolZone#PATROLZONE: +PatrolZone The PatrolZone where the AI needs to patrol.

+
@@ -450,20 +488,40 @@ The SET of Set#SET_AIRBASEs to evaluate wh
-AIBALANCER:SetPatrolZone(PatrolZone) +AIBALANCER:SetPatrolZone(PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed)

Let the AI patrol a Zone with a given Speed range and Altitude range.

-

Parameter

+

Parameters

  • PatrolZone#PATROLZONE PatrolZone : The PatrolZone where the AI needs to patrol.

    +
  • +
  • + +

    PatrolFloorAltitude :

    + +
  • +
  • + +

    PatrolCeilingAltitude :

    + +
  • +
  • + +

    PatrolMinSpeed :

    + +
  • +
  • + +

    PatrolMaxSpeed :

    +

Return value

diff --git a/Moose Training/Documentation/AI_Balancer.html b/Moose Training/Documentation/AI_Balancer.html new file mode 100644 index 000000000..95bdbfe1c --- /dev/null +++ b/Moose Training/Documentation/AI_Balancer.html @@ -0,0 +1,641 @@ + + + + + + +
+
+ +
+
+
+
+ +
+

Module AI_Balancer

+ +

This module contains the AI_BALANCER class.

+ + + +
+ +

1) AI.AIBalancer#AIBALANCER class, extends Core.Fsm#FSM_SET

+

The AI.AIBalancer#AIBALANCER class monitors and manages as many AI GROUPS as there are +CLIENTS in a SETCLIENT collection not occupied by players. +The AIBALANCER class manages internally a collection of AI management objects, which govern the behaviour +of the underlying AI GROUPS.

+ +

The parent class Core.Fsm#FSM_SET manages the functionality to control the Finite State Machine (FSM) +and calls for each event the state transition methods providing the internal Core.Fsm#FSM_SET.Set object containing the +SET_GROUP and additional event parameters provided during the event.

+ +

1.1) AI_BALANCER construction method

+

Create a new AI_BALANCER object with the AI_BALANCER.New method:

+ + + +

1.2)

+

* Add + * Remove

+ +

1.2) AI_BALANCER returns AI to Airbases

+

You can configure to have the AI to return to:

+ + + +

API CHANGE HISTORY

+ +

The underlying change log documents the API changes. Please read this carefully. The following notation is used:

+ +
    +
  • Added parts are expressed in bold type face.
  • +
  • Removed parts are expressed in italic type face.
  • +
+ +

Hereby the change log:

+ +

2016-08-17: SPAWN:InitCleanUp( SpawnCleanUpInterval ) replaces SPAWN:CleanUp( SpawnCleanUpInterval )

+ +
    +
  • Want to ensure that the methods starting with Init are the first called methods before any Spawn method is called!
  • +
  • This notation makes it now more clear which methods are initialization methods and which methods are Spawn enablement methods.
  • +
+ +
+ +

AUTHORS and CONTRIBUTIONS

+ +

Contributions:

+ +
    +
  • Dutch_Baron (James): Who you can search on the Eagle Dynamics Forums.
    + Working together with James has resulted in the creation of the AI_BALANCER class.
    + James has shared his ideas on balancing AI with air units, and together we made a first design which you can use now :-)

  • +
  • SNAFU: + Had a couple of mails with the guys to validate, if the same concept in the GCI/CAP script could be reworked within MOOSE. + None of the script code has been used however within the new AI_BALANCER moose class.

  • +
+ +

Authors:

+ +
    +
  • FlightControl: Framework Design & Programming
  • +
+ + +

Global(s)

+ + + + + +
AI_BALANCER + +
+

Type AI_BALANCER

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AI_BALANCER.AIGroups + +
AI_BALANCER.ClassName + +
AI_BALANCER:New(SetClient, SpawnAI) +

Creates a new AI_BALANCER object

+
AI_BALANCER.PatrolZones + +
AI_BALANCER.ReturnAirbaseSet + +
AI_BALANCER:ReturnToHomeAirbase(ReturnTresholdRange) +

Returns the AI to the home Wrapper.Airbase#AIRBASE.

+
AI_BALANCER:ReturnToNearestAirbases(ReturnTresholdRange, ReturnAirbaseSet) +

Returns the AI to the nearest friendly Wrapper.Airbase#AIRBASE.

+
AI_BALANCER.ReturnTresholdRange + +
AI_BALANCER.SetClient + +
AI_BALANCER.ToHomeAirbase + +
AI_BALANCER.ToNearestAirbase + +
AI_BALANCER:onenterDestroying(SetGroup, AIGroup, Event, From, To) + +
AI_BALANCER:onenterMonitoring(SetGroup) + +
AI_BALANCER:onenterReturning(SetGroup, AIGroup, Event, From, To) + +
AI_BALANCER:onenterSpawning(SetGroup, ClientName, AIGroup, Event, From, To) + +
+ +

Global(s)

+
+
+ + #AI_BALANCER + +AI_BALANCER + +
+
+ + + +
+
+

Type AI_Balancer

+ +

Type AI_BALANCER

+ +

AI_BALANCER class

+ +

Field(s)

+
+
+ + + +AI_BALANCER.AIGroups + +
+
+ + + +
+
+
+
+ + #string + +AI_BALANCER.ClassName + +
+
+ + + +
+
+
+
+ + +AI_BALANCER:New(SetClient, SpawnAI) + +
+
+ +

Creates a new AI_BALANCER object

+ +

Parameters

+
    +
  • + +

    Core.Set#SET_CLIENT SetClient : +A SET_CLIENT object that will contain the CLIENT objects to be monitored if they are alive or not (joined by a player).

    + +
  • +
  • + +

    Functional.Spawn#SPAWN SpawnAI : +The default Spawn object to spawn new AI Groups when needed.

    + +
  • +
+

Return value

+ +

#AI_BALANCER:

+ + +

Usage:

+
-- Define a new AI_BALANCER Object.
+ +
+
+
+
+ + + +AI_BALANCER.PatrolZones + +
+
+ + + +
+
+
+
+ + + +AI_BALANCER.ReturnAirbaseSet + +
+
+ + + +
+
+
+
+ + +AI_BALANCER:ReturnToHomeAirbase(ReturnTresholdRange) + +
+
+ +

Returns the AI to the home Wrapper.Airbase#AIRBASE.

+ +

Parameter

+ +
+
+
+
+ + +AI_BALANCER:ReturnToNearestAirbases(ReturnTresholdRange, ReturnAirbaseSet) + +
+
+ +

Returns the AI to the nearest friendly Wrapper.Airbase#AIRBASE.

+ +

Parameters

+ +
+
+
+
+ + + +AI_BALANCER.ReturnTresholdRange + +
+
+ + + +
+
+
+
+ + Core.Set#SET_CLIENT + +AI_BALANCER.SetClient + +
+
+ + + +
+
+
+
+ + #boolean + +AI_BALANCER.ToHomeAirbase + +
+
+ + + +
+
+
+
+ + #boolean + +AI_BALANCER.ToNearestAirbase + +
+
+ + + +
+
+
+
+ + +AI_BALANCER:onenterDestroying(SetGroup, AIGroup, Event, From, To) + +
+
+ + + +

Parameters

+ +
+
+
+
+ + +AI_BALANCER:onenterMonitoring(SetGroup) + +
+
+ + + +

Parameter

+
    +
  • + +

    SetGroup :

    + +
  • +
+
+
+
+
+ + +AI_BALANCER:onenterReturning(SetGroup, AIGroup, Event, From, To) + +
+
+ + + +

Parameters

+ +
+
+
+
+ + +AI_BALANCER:onenterSpawning(SetGroup, ClientName, AIGroup, Event, From, To) + +
+
+ + + +

Parameters

+ +
+
+ +
+ +
+ + diff --git a/Moose Training/Documentation/AI_PatrolZone.html b/Moose Training/Documentation/AI_PatrolZone.html new file mode 100644 index 000000000..f42d1ae48 --- /dev/null +++ b/Moose Training/Documentation/AI_PatrolZone.html @@ -0,0 +1,732 @@ + + + + + + +
+
+ +
+
+
+
+ +
+

Module AI_PatrolZone

+ +

This module contains the AI_PATROLZONE class.

+ + + +
+ +

1) #AI_PATROLZONE class, extends StateMachine#STATEMACHINE

+

The #AI_PATROLZONE class implements the core functions to patrol a Zone by an AIR Controllable. +The patrol algorithm works that for each airplane patrolling, upon arrival at the patrol zone, +a random point is selected as the route point within the 3D space, within the given boundary limits. +The airplane will fly towards the random 3D point within the patrol zone, using a random speed within the given altitude and speed limits. +Upon arrival at the random 3D point, a new 3D random point will be selected within the patrol zone using the given limits. +This cycle will continue until a fuel treshold has been reached by the airplane. +When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.

+ +

1.1) AI_PATROLZONE constructor:

+ + + +

1.2) AI_PATROLZONE state machine:

+

The AI_PATROLZONE is a state machine: it manages the different events and states of the AIControllable it is controlling.

+ +

1.2.1) AI_PATROLZONE Events:

+ +
    +
  • AI_PATROLZONE.Route( AIControllable ): A new 3D route point is selected and the AIControllable will fly towards that point with the given speed.
  • +
  • AI_PATROLZONE.Patrol( AIControllable ): The AIControllable reports it is patrolling. This event is called every 30 seconds.
  • +
  • AI_PATROLZONE.RTB( AIControllable ): The AIControllable will report return to base.
  • +
  • AI_PATROLZONE.End( AIControllable ): The end of the AI_PATROLZONE process.
  • +
  • AI_PATROLZONE.Dead( AIControllable ): The AIControllable is dead. The AI_PATROLZONE process will be ended.
  • +
+ +

1.2.2) AI_PATROLZONE States:

+ +
    +
  • Route: A new 3D route point is selected and the AIControllable will fly towards that point with the given speed.
  • +
  • Patrol: The AIControllable is patrolling. This state is set every 30 seconds, so every 30 seconds, a state transition function can be used.
  • +
  • RTB: The AIControllable reports it wants to return to the base.
  • +
  • Dead: The AIControllable is dead ...
  • +
  • End: The process has come to an end.
  • +
+ +

1.2.3) AI_PATROLZONE state transition functions:

+ +

State transition functions can be set by the mission designer customizing or improving the behaviour of the state. +There are 2 moments when state transition functions will be called by the state machine:

+ +
    +
  • Before the state transition. + The state transition function needs to start with the name OnBefore + the name of the state. + If the state transition function returns false, then the processing of the state transition will not be done! + If you want to change the behaviour of the AIControllable at this event, return false, + but then you'll need to specify your own logic using the AIControllable!

  • +
  • After the state transition. + The state transition function needs to start with the name OnAfter + the name of the state. + These state transition functions need to provide a return value, which is specified at the function description.

  • +
+ +

An example how to manage a state transition for an AI_PATROLZONE object Patrol for the state RTB:

+ +
 local PatrolZoneGroup = GROUP:FindByName( "Patrol Zone" )
+ local PatrolZone = ZONE_POLYGON:New( "PatrolZone", PatrolZoneGroup )
+
+ local PatrolSpawn = SPAWN:New( "Patrol Group" )
+ local PatrolGroup = PatrolSpawn:Spawn()
+
+ local Patrol = AI_PATROLZONE:New( PatrolZone, 3000, 6000, 300, 600 )
+ Patrol:SetControllable( PatrolGroup )
+ Patrol:ManageFuel( 0.2, 60 )
+
+ +

OnBeforeRTB( AIGroup ) will be called by the AI_PATROLZONE object when the AIGroup reports RTB, but before the RTB default action is processed by the AI_PATROLZONE object.

+ +
 --- State transition function for the AI\_PATROLZONE **Patrol** object
+ -- @param #AI_PATROLZONE self 
+ -- @param Controllable#CONTROLLABLE AIGroup
+ -- @return #boolean If false is returned, then the OnAfter state transition function will not be called.
+ function Patrol:OnBeforeRTB( AIGroup )
+   AIGroup:MessageToRed( "Returning to base", 20 )
+ end
+
+ +

OnAfterRTB( AIGroup ) will be called by the AI_PATROLZONE object when the AIGroup reports RTB, but after the RTB default action was processed by the AI_PATROLZONE object.

+ +
 --- State transition function for the AI\_PATROLZONE **Patrol** object
+ -- @param #AI_PATROLZONE self 
+ -- @param Controllable#CONTROLLABLE AIGroup
+ -- @return #Controllable#CONTROLLABLE The new AIGroup object that is set to be patrolling the zone.
+ function Patrol:OnAfterRTB( AIGroup )
+   return PatrolSpawn:Spawn()
+ end 
+
+ +

1.3) Manage the AI_PATROLZONE parameters:

+

The following methods are available to modify the parameters of a AI_PATROLZONE object:

+ + + +

1.3) Manage the out of fuel in the AI_PATROLZONE:

+

When the AIControllable is out of fuel, it is required that a new AIControllable is started, before the old AIControllable can return to the home base. +Therefore, with a parameter and a calculation of the distance to the home base, the fuel treshold is calculated. +When the fuel treshold is reached, the AIControllable will continue for a given time its patrol task in orbit, while a new AIControllable is targetted to the AI_PATROLZONE. +Once the time is finished, the old AIControllable will return to the base. +Use the method AI_PATROLZONE.ManageFuel() to have this proces in place.

+ +
+ +

API CHANGE HISTORY

+ +

The underlying change log documents the API changes. Please read this carefully. The following notation is used:

+ +
    +
  • Added parts are expressed in bold type face.
  • +
  • Removed parts are expressed in italic type face.
  • +
+ +

Hereby the change log:

+ +

2016-08-17: AI_PATROLZONE:New( PatrolSpawn, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed ) replaces AI_PATROLZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed )

+ +

2016-07-01: Initial class and API.

+ +
+ +

AUTHORS and CONTRIBUTIONS

+ +

Contributions:

+ +
    +
  • DutchBaron: Testing.
  • +
  • Pikey: Testing and API concept review.
  • +
+ +

Authors:

+ +
    +
  • FlightControl: Design & Programming.
  • +
+ + + +

Global(s)

+ + + + + + + + + +
AI_PATROLZONE + +
_NewPatrolRoute(AIControllable) + +
+

Type AI_PATROLZONE

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AI_PATROLZONE.AIControllable +

The Controllable patrolling.

+
AI_PATROLZONE.ClassName + +
AI_PATROLZONE:ManageFuel(PatrolFuelTresholdPercentage, PatrolOutOfFuelOrbitTime) +

When the AIControllable is out of fuel, it is required that a new AIControllable is started, before the old AIControllable can return to the home base.

+
AI_PATROLZONE:New(PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed) +

Creates a new AI_PATROLZONE object

+
AI_PATROLZONE.PatrolCeilingAltitude +

The highest altitude in meters where to execute the patrol.

+
AI_PATROLZONE.PatrolFloorAltitude +

The lowest altitude in meters where to execute the patrol.

+
AI_PATROLZONE.PatrolFuelTresholdPercentage + +
AI_PATROLZONE.PatrolManageFuel + +
AI_PATROLZONE.PatrolMaxSpeed +

The maximum speed of the Controllable in km/h.

+
AI_PATROLZONE.PatrolMinSpeed +

The minimum speed of the Controllable in km/h.

+
AI_PATROLZONE.PatrolOutOfFuelOrbitTime + +
AI_PATROLZONE.PatrolZone +

The Zone where the patrol needs to be executed.

+
AI_PATROLZONE:SetAltitude(PatrolFloorAltitude, PatrolCeilingAltitude) +

Sets the floor and ceiling altitude of the patrol.

+
AI_PATROLZONE:SetSpeed(PatrolMinSpeed, PatrolMaxSpeed) +

Sets (modifies) the minimum and maximum speed of the patrol.

+
AI_PATROLZONE:onenterPatrol() + +
AI_PATROLZONE:onenterRoute() +

Defines a new patrol route using the AI_PatrolZone parameters and settings.

+
+ +

Global(s)

+
+
+ + #AI_PATROLZONE + +AI_PATROLZONE + +
+
+ + + +
+
+
+
+ + +_NewPatrolRoute(AIControllable) + +
+
+ + + +

Parameter

+ +
+
+

Type AI_PatrolZone

+ +

Type AI_PATROLZONE

+ +

AI_PATROLZONE class

+ +

Field(s)

+
+
+ + Controllable#CONTROLLABLE + +AI_PATROLZONE.AIControllable + +
+
+ +

The Controllable patrolling.

+ +
+
+
+
+ + #string + +AI_PATROLZONE.ClassName + +
+
+ + + +
+
+
+
+ + +AI_PATROLZONE:ManageFuel(PatrolFuelTresholdPercentage, PatrolOutOfFuelOrbitTime) + +
+
+ +

When the AIControllable is out of fuel, it is required that a new AIControllable is started, before the old AIControllable can return to the home base.

+ + +

Therefore, with a parameter and a calculation of the distance to the home base, the fuel treshold is calculated. +When the fuel treshold is reached, the AIControllable will continue for a given time its patrol task in orbit, while a new AIControllable is targetted to the AI_PATROLZONE. +Once the time is finished, the old AIControllable will return to the base.

+ +

Parameters

+
    +
  • + +

    #number PatrolFuelTresholdPercentage : +The treshold in percentage (between 0 and 1) when the AIControllable is considered to get out of fuel.

    + +
  • +
  • + +

    #number PatrolOutOfFuelOrbitTime : +The amount of seconds the out of fuel AIControllable will orbit before returning to the base.

    + +
  • +
+

Return value

+ +

#AI_PATROLZONE: +self

+ +
+
+
+
+ + +AI_PATROLZONE:New(PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed) + +
+
+ +

Creates a new AI_PATROLZONE object

+ +

Parameters

+ +

Return value

+ +

#AI_PATROLZONE: +self

+ +

Usage:

+
-- Define a new AI_PATROLZONE Object. This PatrolArea will patrol an AIControllable within PatrolZone between 3000 and 6000 meters, with a variying speed between 600 and 900 km/h.
+PatrolZone = ZONE:New( 'PatrolZone' )
+PatrolSpawn = SPAWN:New( 'Patrol Group' )
+PatrolArea = AI_PATROLZONE:New( PatrolZone, 3000, 6000, 600, 900 )
+ +
+
+
+
+ + DCSTypes#Altitude + +AI_PATROLZONE.PatrolCeilingAltitude + +
+
+ +

The highest altitude in meters where to execute the patrol.

+ +
+
+
+
+ + DCSTypes#Altitude + +AI_PATROLZONE.PatrolFloorAltitude + +
+
+ +

The lowest altitude in meters where to execute the patrol.

+ +
+
+
+
+ + + +AI_PATROLZONE.PatrolFuelTresholdPercentage + +
+
+ + + +
+
+
+
+ + #boolean + +AI_PATROLZONE.PatrolManageFuel + +
+
+ + + +
+
+
+
+ + DCSTypes#Speed + +AI_PATROLZONE.PatrolMaxSpeed + +
+
+ +

The maximum speed of the Controllable in km/h.

+ +
+
+
+
+ + DCSTypes#Speed + +AI_PATROLZONE.PatrolMinSpeed + +
+
+ +

The minimum speed of the Controllable in km/h.

+ +
+
+
+
+ + + +AI_PATROLZONE.PatrolOutOfFuelOrbitTime + +
+
+ + + +
+
+
+
+ + Zone#ZONE_BASE + +AI_PATROLZONE.PatrolZone + +
+
+ +

The Zone where the patrol needs to be executed.

+ +
+
+
+
+ + +AI_PATROLZONE:SetAltitude(PatrolFloorAltitude, PatrolCeilingAltitude) + +
+
+ +

Sets the floor and ceiling altitude of the patrol.

+ +

Parameters

+
    +
  • + +

    DCSTypes#Altitude PatrolFloorAltitude : +The lowest altitude in meters where to execute the patrol.

    + +
  • +
  • + +

    DCSTypes#Altitude PatrolCeilingAltitude : +The highest altitude in meters where to execute the patrol.

    + +
  • +
+

Return value

+ +

#AI_PATROLZONE: +self

+ +
+
+
+
+ + +AI_PATROLZONE:SetSpeed(PatrolMinSpeed, PatrolMaxSpeed) + +
+
+ +

Sets (modifies) the minimum and maximum speed of the patrol.

+ +

Parameters

+ +

Return value

+ +

#AI_PATROLZONE: +self

+ +
+
+
+
+ + +AI_PATROLZONE:onenterPatrol() + +
+
+ + + +
+
+
+
+ + +AI_PATROLZONE:onenterRoute() + +
+
+ +

Defines a new patrol route using the AI_PatrolZone parameters and settings.

+ +

Return value

+ +

#AI_PATROLZONE: +self

+ +
+
+ +
+ +
+ + diff --git a/Moose Training/Documentation/Account.html b/Moose Training/Documentation/Account.html new file mode 100644 index 000000000..c7fd789ae --- /dev/null +++ b/Moose Training/Documentation/Account.html @@ -0,0 +1,858 @@ + + + + + + +
+
+ +
+
+
+
+ +
+

Module Account

+ +

(SP) (MP) (FSM) Account for (Detect, count and report) DCS events occuring on DCS objects (units).

+ + + +
+ +

#ACT_ACCOUNT FSM class, extends Core.Fsm#FSM_PROCESS

+ +

ACT_ACCOUNT state machine:

+ +

This class is a state machine: it manages a process that is triggered by events causing state transitions to occur. +All derived classes from this class will start with the class name, followed by a _. See the relevant derived class descriptions below. +Each derived class follows exactly the same process, using the same events and following the same state transitions, +but will have different implementation behaviour upon each event or state transition.

+ +

ACT_ACCOUNT Events:

+ +

These are the events defined in this class:

+ +
    +
  • Start: The process is started. The process will go into the Report state.
  • +
  • Event: A relevant event has occured that needs to be accounted for. The process will go into the Account state.
  • +
  • Report: The process is reporting to the player the accounting status of the DCS events.
  • +
  • More: There are more DCS events that need to be accounted for. The process will go back into the Report state.
  • +
  • NoMore: There are no more DCS events that need to be accounted for. The process will go into the Success state.
  • +
+ +

ACT_ACCOUNT Event methods:

+ +

Event methods are available (dynamically allocated by the state machine), that accomodate for state transitions occurring in the process. +There are two types of event methods, which you can use to influence the normal mechanisms in the state machine:

+ +
    +
  • Immediate: The event method has exactly the name of the event.
  • +
  • Delayed: The event method starts with a __ + the name of the event. The first parameter of the event method is a number value, expressing the delay in seconds when the event will be executed.
  • +
+ +

ACT_ACCOUNT States:

+ +
    +
  • Assigned: The player is assigned to the task. This is the initialization state for the process.
  • +
  • Waiting: the process is waiting for a DCS event to occur within the simulator. This state is set automatically.
  • +
  • Report: The process is Reporting to the players in the group of the unit. This state is set automatically every 30 seconds.
  • +
  • Account: The relevant DCS event has occurred, and is accounted for.
  • +
  • Success (*): All DCS events were accounted for.
  • +
  • Failed (*): The process has failed.
  • +
+ +

(*) End states of the process.

+ +

ACT_ACCOUNT state transition methods:

+ +

State transition functions can be set by the mission designer customizing or improving the behaviour of the state. +There are 2 moments when state transition methods will be called by the state machine:

+ +
    +
  • Before the state transition. + The state transition method needs to start with the name OnBefore + the name of the state. + If the state transition method returns false, then the processing of the state transition will not be done! + If you want to change the behaviour of the AIControllable at this event, return false, + but then you'll need to specify your own logic using the AIControllable!

  • +
  • After the state transition. + The state transition method needs to start with the name OnAfter + the name of the state. + These state transition methods need to provide a return value, which is specified at the function description.

  • +
+ +

1) #ACTACCOUNTDEADS FSM class, extends Fsm.Account#ACT_ACCOUNT

+ +

The ACTACCOUNTDEADS class accounts (detects, counts and reports) successful kills of DCS units. +The process is given a Set of units that will be tracked upon successful destruction. +The process will end after each target has been successfully destroyed. +Each successful dead will trigger an Account state transition that can be scored, modified or administered.

+ + +

ACTACCOUNTDEADS constructor:

+ + + +
+ + +

Global(s)

+ + + + + + + + + +
ACT_ACCOUNT + +
ACT_ACCOUNT_DEADS + +
+

Type ACT_ACCOUNT

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ACT_ACCOUNT.ClassName + +
ACT_ACCOUNT.DisplayCount + +
ACT_ACCOUNT:New() +

Creates a new DESTROY process.

+
ACT_ACCOUNT.TargetSetUnit + +
ACT_ACCOUNT:onafterEvent(ProcessUnit, Event, From, To) +

StateMachine callback function

+
ACT_ACCOUNT:onafterStart(ProcessUnit, Event, From, To) +

StateMachine callback function

+
ACT_ACCOUNT:onenterWaiting(ProcessUnit, Event, From, To) +

StateMachine callback function

+
+ +

Type ACT_ACCOUNT_DEADS

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ACT_ACCOUNT_DEADS.ClassName + +
ACT_ACCOUNT_DEADS.DisplayCategory + +
ACT_ACCOUNT_DEADS.DisplayCount + +
ACT_ACCOUNT_DEADS.DisplayInterval + +
ACT_ACCOUNT_DEADS.DisplayMessage + +
ACT_ACCOUNT_DEADS.DisplayTime + +
ACT_ACCOUNT_DEADS:Init(FsmAccount) + +
ACT_ACCOUNT_DEADS:New(TargetSetUnit, TaskName) +

Creates a new DESTROY process.

+
ACT_ACCOUNT_DEADS.TargetSetUnit + +
ACT_ACCOUNT_DEADS.TaskName + +
ACT_ACCOUNT_DEADS:_Destructor() + +
ACT_ACCOUNT_DEADS:onafterEvent(ProcessUnit, Event, From, To, EventData) +

StateMachine callback function

+
ACT_ACCOUNT_DEADS:onenterAccount(ProcessUnit, Event, From, To, EventData) +

StateMachine callback function

+
ACT_ACCOUNT_DEADS:onenterReport(ProcessUnit, Event, From, To) +

StateMachine callback function

+
ACT_ACCOUNT_DEADS:onfuncEventDead(EventData) + +
+ +

Global(s)

+
+
+ + #ACT_ACCOUNT + +ACT_ACCOUNT + +
+
+ + + +
+
+
+
+ + #ACT_ACCOUNT_DEADS + +ACT_ACCOUNT_DEADS + +
+
+ + + +
+
+

Type Account

+ +

Type ACT_ACCOUNT

+ +

ACT_ACCOUNT class

+ +

Field(s)

+
+
+ + #string + +ACT_ACCOUNT.ClassName + +
+
+ + + +
+
+
+
+ + #number + +ACT_ACCOUNT.DisplayCount + +
+
+ + + +
+
+
+
+ + +ACT_ACCOUNT:New() + +
+
+ +

Creates a new DESTROY process.

+ +

Return value

+ +

#ACT_ACCOUNT:

+ + +
+
+
+
+ + Set#SET_UNIT + +ACT_ACCOUNT.TargetSetUnit + +
+
+ + + +
+
+
+
+ + +ACT_ACCOUNT:onafterEvent(ProcessUnit, Event, From, To) + +
+
+ +

StateMachine callback function

+ +

Parameters

+ +
+
+
+
+ + +ACT_ACCOUNT:onafterStart(ProcessUnit, Event, From, To) + +
+
+ +

StateMachine callback function

+ +

Parameters

+ +
+
+
+
+ + +ACT_ACCOUNT:onenterWaiting(ProcessUnit, Event, From, To) + +
+
+ +

StateMachine callback function

+ +

Parameters

+ +
+
+ +

Type ACT_ACCOUNT_DEADS

+ +

ACTACCOUNTDEADS class

+ +

Field(s)

+
+
+ + #string + +ACT_ACCOUNT_DEADS.ClassName + +
+
+ + + +
+
+
+
+ + #string + +ACT_ACCOUNT_DEADS.DisplayCategory + +
+
+ + + + +

Targets is the default display category

+ +
+
+
+
+ + #number + +ACT_ACCOUNT_DEADS.DisplayCount + +
+
+ + + +
+
+
+
+ + #number + +ACT_ACCOUNT_DEADS.DisplayInterval + +
+
+ + + +
+
+
+
+ + #boolean + +ACT_ACCOUNT_DEADS.DisplayMessage + +
+
+ + + +
+
+
+
+ + #number + +ACT_ACCOUNT_DEADS.DisplayTime + +
+
+ + + + +

10 seconds is the default

+ +
+
+
+
+ + +ACT_ACCOUNT_DEADS:Init(FsmAccount) + +
+
+ + + +

Parameter

+
    +
  • + +

    FsmAccount :

    + +
  • +
+
+
+
+
+ + +ACT_ACCOUNT_DEADS:New(TargetSetUnit, TaskName) + +
+
+ +

Creates a new DESTROY process.

+ +

Parameters

+
    +
  • + +

    Set#SET_UNIT TargetSetUnit :

    + +
  • +
  • + +

    #string TaskName :

    + +
  • +
+
+
+
+
+ + Set#SET_UNIT + +ACT_ACCOUNT_DEADS.TargetSetUnit + +
+
+ + + +
+
+
+
+ + + +ACT_ACCOUNT_DEADS.TaskName + +
+
+ + + +
+
+
+
+ + +ACT_ACCOUNT_DEADS:_Destructor() + +
+
+ + + +
+
+
+
+ + +ACT_ACCOUNT_DEADS:onafterEvent(ProcessUnit, Event, From, To, EventData) + +
+
+ +

StateMachine callback function

+ +

Parameters

+ +
+
+
+
+ + +ACT_ACCOUNT_DEADS:onenterAccount(ProcessUnit, Event, From, To, EventData) + +
+
+ +

StateMachine callback function

+ +

Parameters

+ +
+
+
+
+ + +ACT_ACCOUNT_DEADS:onenterReport(ProcessUnit, Event, From, To) + +
+
+ +

StateMachine callback function

+ +

Parameters

+ +
+
+
+
+ + +ACT_ACCOUNT_DEADS:onfuncEventDead(EventData) + +
+
+ + + +

Parameter

+ +
+
+ +
+ +
+ + diff --git a/Moose Training/Documentation/Airbase.html b/Moose Training/Documentation/Airbase.html index d9f76684b..466ac9524 100644 --- a/Moose Training/Documentation/Airbase.html +++ b/Moose Training/Documentation/Airbase.html @@ -17,13 +17,16 @@ index
@@ -285,7 +278,7 @@ The Airbase Name.

Return value

-

Airbase#AIRBASE: +

Wrapper.Airbase#AIRBASE: self

@@ -325,7 +318,7 @@ The name of the airbase.

Return value

-

Airbase#AIRBASE:

+

Wrapper.Airbase#AIRBASE:

diff --git a/Moose Training/Documentation/AirbasePolice.html b/Moose Training/Documentation/AirbasePolice.html index 605a5ca32..5a8018037 100644 --- a/Moose Training/Documentation/AirbasePolice.html +++ b/Moose Training/Documentation/AirbasePolice.html @@ -17,13 +17,16 @@ index
+
+
+ + +BASE:EventOnBaseCaptured(EventFunction) + +
+
+ +

Subscribe to a SEVENTBASE_CAPTURED event.

+ +

Parameter

+
    +
  • + +

    #function EventFunction : +The function to be called when the event occurs for the unit.

    + +
  • +
+

Return value

+ +

#BASE:

+ + +
+
+
+
+ + +BASE:EventOnBirth(EventFunction) + +
+
+ +

Subscribe to a SEVENTBIRTH event.

+ +

Parameter

+
    +
  • + +

    #function EventFunction : +The function to be called when the event occurs for the unit.

    + +
  • +
+

Return value

+ +

#BASE:

+ + +
+
+
+
+ + +BASE:EventOnCrash(EventFunction) + +
+
+ +

Subscribe to a SEVENTCRASH event.

+ +

Parameter

+
    +
  • + +

    #function EventFunction : +The function to be called when the event occurs for the unit.

    + +
  • +
+

Return value

+ +

#BASE:

+ + +
+
+
+
+ + +BASE:EventOnDead(EventFunction) + +
+
+ +

Subscribe to a SEVENTDEAD event.

+ +

Parameter

+
    +
  • + +

    #function EventFunction : +The function to be called when the event occurs for the unit.

    + +
  • +
+

Return value

+ +

#BASE:

+ + +
+
+
+
+ + +BASE:EventOnEjection(EventFunction) + +
+
+ +

Subscribe to a SEVENTEJECTION event.

+ +

Parameter

+
    +
  • + +

    #function EventFunction : +The function to be called when the event occurs for the unit.

    + +
  • +
+

Return value

+ +

#BASE:

+ + +
+
+
+
+ + +BASE:EventOnEngineShutdown(EventFunction) + +
+
+ +

Subscribe to a SEVENTENGINE_SHUTDOWN event.

+ +

Parameter

+
    +
  • + +

    #function EventFunction : +The function to be called when the event occurs for the unit.

    + +
  • +
+

Return value

+ +

#BASE:

+ + +
+
+
+
+ + +BASE:EventOnEngineStartup(EventFunction) + +
+
+ +

Subscribe to a SEVENTENGINE_STARTUP event.

+ +

Parameter

+
    +
  • + +

    #function EventFunction : +The function to be called when the event occurs for the unit.

    + +
  • +
+

Return value

+ +

#BASE:

+ + +
+
+
+
+ + +BASE:EventOnHit(EventFunction) + +
+
+ +

Subscribe to a SEVENTHIT event.

+ +

Parameter

+
    +
  • + +

    #function EventFunction : +The function to be called when the event occurs for the unit.

    + +
  • +
+

Return value

+ +

#BASE:

+ + +
+
+
+
+ + +BASE:EventOnHumanFailure(EventFunction) + +
+
+ +

Subscribe to a SEVENTHUMAN_FAILURE event.

+ +

Parameter

+
    +
  • + +

    #function EventFunction : +The function to be called when the event occurs for the unit.

    + +
  • +
+

Return value

+ +

#BASE:

+ + +
+
+
+
+ + +BASE:EventOnLand(EventFunction) + +
+
+ +

Subscribe to a SEVENTLAND event.

+ +

Parameter

+
    +
  • + +

    #function EventFunction : +The function to be called when the event occurs for the unit.

    + +
  • +
+

Return value

+ +

#BASE:

+ + +
+
+
+
+ + +BASE:EventOnMissionStart(EventFunction) + +
+
+ +

Subscribe to a SEVENTMISSION_START event.

+ +

Parameter

+
    +
  • + +

    #function EventFunction : +The function to be called when the event occurs for the unit.

    + +
  • +
+

Return value

+ +

#BASE:

+ + +
+
+
+
+ + +BASE:EventOnPilotDead(EventFunction) + +
+
+ +

Subscribe to a SEVENTPILOT_DEAD event.

+ +

Parameter

+
    +
  • + +

    #function EventFunction : +The function to be called when the event occurs for the unit.

    + +
  • +
+

Return value

+ +

#BASE:

+ + +
+
+
+
+ + +BASE:EventOnPlayerComment(EventFunction) + +
+
+ +

Subscribe to a SEVENTPLAYER_COMMENT event.

+ +

Parameter

+
    +
  • + +

    #function EventFunction : +The function to be called when the event occurs for the unit.

    + +
  • +
+

Return value

+ +

#BASE:

+ + +
+
+
+
+ + +BASE:EventOnPlayerEnterUnit(EventFunction) + +
+
+ +

Subscribe to a SEVENTPLAYERENTERUNIT event.

+ +

Parameter

+
    +
  • + +

    #function EventFunction : +The function to be called when the event occurs for the unit.

    + +
  • +
+

Return value

+ +

#BASE:

+ + +
+
+
+
+ + +BASE:EventOnPlayerLeaveUnit(EventFunction) + +
+
+ +

Subscribe to a SEVENTPLAYERLEAVEUNIT event.

+ +

Parameter

+
    +
  • + +

    #function EventFunction : +The function to be called when the event occurs for the unit.

    + +
  • +
+

Return value

+ +

#BASE:

+ + +
+
+
+
+ + +BASE:EventOnPlayerMissionEnd(EventFunction) + +
+
+ +

Subscribe to a SEVENTMISSION_END event.

+ +

Parameter

+
    +
  • + +

    #function EventFunction : +The function to be called when the event occurs for the unit.

    + +
  • +
+

Return value

+ +

#BASE:

+ + +
+
+
+
+ + +BASE:EventOnRefueling(EventFunction) + +
+
+ +

Subscribe to a SEVENTREFUELING event.

+ +

Parameter

+
    +
  • + +

    #function EventFunction : +The function to be called when the event occurs for the unit.

    + +
  • +
+

Return value

+ +

#BASE:

+ + +
+
+
+
+ + +BASE:EventOnRefuelingStop(EventFunction) + +
+
+ +

Subscribe to a SEVENTREFUELING_STOP event.

+ +

Parameter

+
    +
  • + +

    #function EventFunction : +The function to be called when the event occurs for the unit.

    + +
  • +
+

Return value

+ +

#BASE:

+ + +
+
+
+
+ + +BASE:EventOnShootingEnd(EventFunction) + +
+
+ +

Subscribe to a SEVENTSHOOTING_END event.

+ +

Parameter

+
    +
  • + +

    #function EventFunction : +The function to be called when the event occurs for the unit.

    + +
  • +
+

Return value

+ +

#BASE:

+ + +
+
+
+
+ + +BASE:EventOnShootingStart(EventFunction) + +
+
+ +

Subscribe to a SEVENTSHOOTING_START event.

+ +

Parameter

+
    +
  • + +

    #function EventFunction : +The function to be called when the event occurs for the unit.

    + +
  • +
+

Return value

+ +

#BASE:

+ + +
+
+
+
+ + +BASE:EventOnShot(EventFunction) + +
+
+ +

Subscribe to a SEVENTSHOT event.

+ +

Parameter

+
    +
  • + +

    #function EventFunction : +The function to be called when the event occurs for the unit.

    + +
  • +
+

Return value

+ +

#BASE:

+ + +
+
+
+
+ + +BASE:EventOnTakeOff(EventFunction) + +
+
+ +

Subscribe to a SEVENTTAKEOFF event.

+ +

Parameter

+
    +
  • + +

    #function EventFunction : +The function to be called when the event occurs for the unit.

    + +
  • +
+

Return value

+ +

#BASE:

+ + +
+
+
+
+ + +BASE:EventOnTookControl(EventFunction) + +
+
+ +

Subscribe to a SEVENTTOOK_CONTROL event.

+ +

Parameter

+
    +
  • + +

    #function EventFunction : +The function to be called when the event occurs for the unit.

    + +
  • +
+

Return value

+ +

#BASE:

+ + +
+
+
+
+ + +BASE:EventRemoveAll() + +
+
+ +

Remove all subscribed events

+ +

Return value

+ +

#BASE:

@@ -943,31 +1731,10 @@ Child

-

The base constructor.

+ -

This is the top top class of all classed defined within the MOOSE. -Any new class needs to be derived from this class for proper inheritance.

- -

Return value

- -

#BASE: -The new instance of the BASE class.

- -

Usage:

-
-- This declares the constructor of the class TASK, inheriting from BASE.
---- TASK constructor
--- @param #TASK self
--- @param Parameter The parameter of the New constructor.
--- @return #TASK self
-function TASK:New( Parameter )
-
-    local self = BASE:Inherit( self, BASE:New() )
-    
-    self.Variable = Parameter 
-
-    return self
-end
+

@todo need to investigate if the deepCopy is really needed... Don't think so.

@@ -1214,6 +1981,19 @@ BASE:TraceOn( true ) -- Switch the tracing Off BASE:TraceOn( false ) + + +
+
+ + +BASE:_Destructor() + +
+
+ + +
@@ -1249,6 +2029,19 @@ A #table or any field.

+ +
+
+
+ + +BASE:_SetDestructor() + +
+
+ + +
@@ -1295,10 +2088,10 @@ A #table or any field.

-

TODO: Complete DCSTypes#Event structure.
+

TODO: Complete Dcs.DCSTypes#Event structure.
- The main event handling function... This function captures all events generated for the class. @param #BASE self - @param DCSTypes#Event event

+ @param Dcs.DCSTypes#Event event

Parameter

    diff --git a/Moose Training/Documentation/CARGO.html b/Moose Training/Documentation/CARGO.html index 1d3d81148..7aa7c873b 100644 --- a/Moose Training/Documentation/CARGO.html +++ b/Moose Training/Documentation/CARGO.html @@ -17,13 +17,16 @@ index
-

Module CARGO

+

Module Cargo

-

CARGO Classes

+

Management of logical cargo objects, that can be transported from and to transportation carriers.

+ + + +
+ +

Cargo can be of various forms, always are composed out of ONE object ( one unit or one static or one slingload crate ):

+ +
    +
  • AICARGOUNIT, represented by a Unit in a Group: Cargo can be represented by a Unit in a Group. Destruction of the Unit will mean that the cargo is lost.

  • +
  • CARGO_STATIC, represented by a Static: Cargo can be represented by a Static. Destruction of the Static will mean that the cargo is lost.

  • +
  • AICARGOPACKAGE, contained in a Unit of a Group: Cargo can be contained within a Unit of a Group. The cargo can be delivered by the Unit. If the Unit is destroyed, the cargo will be destroyed also.

  • +
  • AICARGOPACKAGE, Contained in a Static: Cargo can be contained within a Static. The cargo can be collected from the @Static. If the Static is destroyed, the cargo will be destroyed.

  • +
  • CARGO_SLINGLOAD, represented by a Cargo that is transportable: Cargo can be represented by a Cargo object that is transportable. Destruction of the Cargo will mean that the cargo is lost.

  • +
  • AICARGOGROUPED, represented by a Group of CARGO_UNITs.

  • +
+ +

1) AI.AICargo#AICARGO class, extends Core.Fsm#FSM_PROCESS

+

The #AI_CARGO class defines the core functions that defines a cargo object within MOOSE. +A cargo is a logical object defined that is available for transport, and has a life status within a simulation.

+ +

The AICARGO is a state machine: it manages the different events and states of the cargo. +All derived classes from AICARGO follow the same state machine, expose the same cargo event functions, and provide the same cargo states.

+ +

1.2.1) AI_CARGO Events:

+ +
    +
  • AI_CARGO.Board( ToCarrier ): Boards the cargo to a carrier.
  • +
  • AI_CARGO.Load( ToCarrier ): Loads the cargo into a carrier, regardless of its position.
  • +
  • AI_CARGO.UnBoard( ToPointVec2 ): UnBoard the cargo from a carrier. This will trigger a movement of the cargo to the option ToPointVec2.
  • +
  • AI_CARGO.UnLoad( ToPointVec2 ): UnLoads the cargo from a carrier.
  • +
  • AI_CARGO.Dead( Controllable ): The cargo is dead. The cargo process will be ended.
  • +
+ +

1.2.2) AI_CARGO States:

+ +
    +
  • UnLoaded: The cargo is unloaded from a carrier.
  • +
  • Boarding: The cargo is currently boarding (= running) into a carrier.
  • +
  • Loaded: The cargo is loaded into a carrier.
  • +
  • UnBoarding: The cargo is currently unboarding (=running) from a carrier.
  • +
  • Dead: The cargo is dead ...
  • +
  • End: The process has come to an end.
  • +
+ +

1.2.3) AI_CARGO state transition methods:

+ +

State transition functions can be set by the mission designer customizing or improving the behaviour of the state. +There are 2 moments when state transition methods will be called by the state machine:

+ +
    +
  • Before the state transition. + The state transition method needs to start with the name OnBefore + the name of the state. + If the state transition method returns false, then the processing of the state transition will not be done! + If you want to change the behaviour of the AIControllable at this event, return false, + but then you'll need to specify your own logic using the AIControllable!

  • +
  • After the state transition. + The state transition method needs to start with the name OnAfter + the name of the state. + These state transition methods need to provide a return value, which is specified at the function description.

  • +
+ +

2) #AICARGOUNIT class

+

The AICARGOUNIT class defines a cargo that is represented by a UNIT object within the simulator, and can be transported by a carrier. +Use the event functions as described above to Load, UnLoad, Board, UnBoard the AICARGOUNIT objects to and from carriers.

+ +

5) #AICARGOGROUPED class

+

The AICARGOGROUPED class defines a cargo that is represented by a group of UNIT objects within the simulator, and can be transported by a carrier. +Use the event functions as described above to Load, UnLoad, Board, UnBoard the AICARGOUNIT objects to and from carriers.

+ +

This module is still under construction, but is described above works already, and will keep working ...

+

Global(s)

- + + + + + + + + + + + + + + + + + + + + + @@ -108,44 +201,540 @@ +
CARGOAI_CARGO + +
AI_CARGO_GROUP + +
AI_CARGO_GROUPED + +
AI_CARGO_PACKAGE + +
AI_CARGO_REPRESENTABLE + +
AI_CARGO_UNIT
+

Type AI_CARGO

+ - + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CARGO_GROUPAI_CARGO:Board(ToCarrier) +

Boards the cargo to a Carrier.

+
AI_CARGO.CargoCarrier +

The alive DCS object carrying the cargo. This value can be nil, meaning, that the cargo is not contained anywhere...

+
AI_CARGO.CargoObject +

The alive DCS object representing the cargo. This value can be nil, meaning, that the cargo is not represented anywhere...

+
AI_CARGO.ClassName
CARGO_PACKAGEAI_CARGO.Containable +

This flag defines if the cargo can be contained within a DCS Unit.

+
AI_CARGO:IsNear(PointVec2) +

Check if CargoCarrier is near the Cargo to be Loaded.

+
AI_CARGO:Load(ToCarrier) +

Loads the cargo to a Carrier.

+
AI_CARGO.Moveable +

This flag defines if the cargo is moveable.

+
AI_CARGO.Name +

A string defining the name of the cargo. The name is the unique identifier of the cargo.

+
AI_CARGO.NearRadius +

(optional) A number defining the radius in meters when the cargo is near to a Carrier, so that it can be loaded.

+
AI_CARGO:New(Type, Name, Weight, ReportRadius, NearRadius) +

AI_CARGO Constructor.

+
AI_CARGO:OnAfterBoarding(Controllable)
CARGO_SLINGLOADAI_CARGO:OnAfterLoaded(Controllable)
CARGO_ZONEAI_CARGO:OnAfterUnBoarding(Controllable)
CargoStaticAI_CARGO:OnAfterUnLoaded(Controllable) +
AI_CARGO:OnBeforeBoarding(Controllable) + +
AI_CARGO:OnBeforeLoaded(Controllable) + +
AI_CARGO:OnBeforeUnBoarding(Controllable) + +
AI_CARGO:OnBeforeUnLoaded(Controllable) + +
AI_CARGO.ReportRadius +

(optional) A number defining the radius in meters when the cargo is signalling or reporting to a Carrier.

+
AI_CARGO.Representable +

This flag defines if the cargo can be represented by a DCS Unit.

+
AI_CARGO.Slingloadable +

This flag defines if the cargo can be slingloaded.

+
AI_CARGO:Spawn(PointVec2) +

Template method to spawn a new representation of the AI_CARGO in the simulator.

+
AI_CARGO.Type +

A string defining the type of the cargo. eg. Engineers, Equipment, Screwdrivers.

+
AI_CARGO:UnBoard(ToPointVec2) +

UnBoards the cargo to a Carrier.

+
AI_CARGO:UnLoad(ToPointVec2) +

UnLoads the cargo to a Carrier.

+
AI_CARGO.Weight +

A number defining the weight of the cargo. The weight is expressed in kg.

+
AI_CARGO:__Board(DelaySeconds, ToCarrier) +

Boards the cargo to a Carrier.

+
AI_CARGO:__Load(DelaySeconds, ToCarrier) +

Loads the cargo to a Carrier.

+
AI_CARGO:__UnBoard(DelaySeconds, ToPointVec2) +

UnBoards the cargo to a Carrier.

+
AI_CARGO:__UnLoad(DelaySeconds, ToPointVec2) +

UnLoads the cargo to a Carrier.

+ +

Type AI_CARGO_GROUP

+ + + + + + + + + + + + + + + + + +
AI_CARGO_GROUP.CargoSet +

A set of cargo objects.

+
AI_CARGO_GROUP.ClassName + +
AI_CARGO_GROUP.Name +

A string defining the name of the cargo group. The name is the unique identifier of the cargo.

+
AI_CARGO_GROUP:New(CargoSet, Type, Name, Weight, ReportRadius, NearRadius) +

AICARGOGROUP constructor.

+
+ +

Type AI_CARGO_GROUPED

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AI_CARGO_GROUPED.ClassName + +
AI_CARGO_GROUPED:New(CargoSet, Type, Name, Weight, ReportRadius, NearRadius) +

AICARGOGROUPED constructor.

+
AI_CARGO_GROUPED:onafterUnBoarding(ToPointVec2, Event, From, To) +

UnBoard Event.

+
AI_CARGO_GROUPED:onenterBoarding(CargoCarrier, Event, From, To) +

Enter Boarding State.

+
AI_CARGO_GROUPED:onenterLoaded(CargoCarrier, Event, From, To) +

Enter Loaded State.

+
AI_CARGO_GROUPED:onenterUnBoarding(ToPointVec2, Event, From, To) +

Enter UnBoarding State.

+
AI_CARGO_GROUPED:onenterUnLoaded(Core, Event, From, To, ToPointVec2) +

Enter UnLoaded State.

+
AI_CARGO_GROUPED:onleaveBoarding(CargoCarrier, Event, From, To) +

Leave Boarding State.

+
AI_CARGO_GROUPED:onleaveUnBoarding(ToPointVec2, Event, From, To) +

Leave UnBoarding State.

+
+ +

Type AI_CARGO_PACKAGE

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AI_CARGO_PACKAGE.CargoCarrier + +
AI_CARGO_PACKAGE.CargoInAir + +
AI_CARGO_PACKAGE.ClassName + +
AI_CARGO_PACKAGE:IsNear(CargoCarrier) +

Check if CargoCarrier is near the Cargo to be Loaded.

+
AI_CARGO_PACKAGE:New(CargoCarrier, Type, Name, Weight, ReportRadius, NearRadius) +

AICARGOPACKAGE Constructor.

+
AI_CARGO_PACKAGE:onafterLoad(Event, From, To, CargoCarrier, Speed, LoadDistance, Angle) +

Load Event.

+
AI_CARGO_PACKAGE:onafterOnBoard(Event, From, To, CargoCarrier, Speed, BoardDistance, Angle, LoadDistance) +

Board Event.

+
AI_CARGO_PACKAGE:onafterOnBoarded(Event, From, To, CargoCarrier, Speed, BoardDistance, LoadDistance, Angle) +

Boarded Event.

+
AI_CARGO_PACKAGE:onafterUnBoard(Event, From, To, Speed, UnLoadDistance, UnBoardDistance, Radius, Angle, CargoCarrier) +

UnBoard Event.

+
AI_CARGO_PACKAGE:onafterUnBoarded(Event, From, To, CargoCarrier, Speed) +

UnBoarded Event.

+
AI_CARGO_PACKAGE:onafterUnLoad(Event, From, To, Distance, Angle, CargoCarrier, Speed) +

UnLoad Event.

+
+ +

Type AI_CARGO_REPRESENTABLE

+ + + + + + + + + + + + + +
AI_CARGO_REPRESENTABLE.ClassName + +
AI_CARGO_REPRESENTABLE:New(CargoObject, Type, Name, Weight, ReportRadius, NearRadius) +

AICARGOREPRESENTABLE Constructor.

+
AI_CARGO_REPRESENTABLE:RouteTo(ToPointVec2, Speed) +

Route a cargo unit to a PointVec2.

+
+ +

Type AI_CARGO_UNIT

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AI_CARGO_UNIT.CargoCarrier + +
AI_CARGO_UNIT.CargoInAir + +
AI_CARGO_UNIT.CargoObject + +
AI_CARGO_UNIT.ClassName + +
AI_CARGO_UNIT:New(CargoUnit, Type, Name, Weight, ReportRadius, NearRadius) +

AICARGOUNIT Constructor.

+
AI_CARGO_UNIT.OnUnLoadedCallBack + +
AI_CARGO_UNIT:onafterBoard(Event, From, To, CargoCarrier) +

Board Event.

+
AI_CARGO_UNIT:onafterUnBoarding(Event, From, To, ToPointVec2) +

UnBoard Event.

+
AI_CARGO_UNIT:onenterBoarding(Event, From, To, CargoCarrier) +

Enter Boarding State.

+
AI_CARGO_UNIT:onenterLoaded(Event, From, To, CargoCarrier) +

Loaded State.

+
AI_CARGO_UNIT:onenterUnBoarding(Event, From, To, ToPointVec2) +

Enter UnBoarding State.

+
AI_CARGO_UNIT:onenterUnLoaded(Event, From, To, Core, ToPointVec2) +

Enter UnLoaded State.

+
AI_CARGO_UNIT:onleaveBoarding(Event, From, To, CargoCarrier) +

Leave Boarding State.

+
AI_CARGO_UNIT:onleaveUnBoarding(Event, From, To, ToPointVec2) +

Leave UnBoarding State.

+
+

Global(s)

- - -CARGO + #AI_CARGO + +AI_CARGO + +
+
+ + + +
+
+
+
+ + #AI_CARGO_GROUP + +AI_CARGO_GROUP + +
+
+ + + +
+
+
+
+ + #AI_CARGO_GROUPED + +AI_CARGO_GROUPED + +
+
+ + + +
+
+
+
+ + #AI_CARGO_PACKAGE + +AI_CARGO_PACKAGE + +
+
+ + + +
+
+
+
+ + #AI_CARGO_REPRESENTABLE + +AI_CARGO_REPRESENTABLE + +
+
+ + + +
+
+
+
+ + #AI_CARGO_UNIT + +AI_CARGO_UNIT
@@ -168,12 +757,70 @@
+

Type Cargo

+ +

Type AI_CARGO

+

Field(s)

- - -CARGO_GROUP + +AI_CARGO:Board(ToCarrier) + +
+
+ +

Boards the cargo to a Carrier.

+ + +

The event will create a movement (= running or driving) of the cargo to the Carrier. +The cargo must be in the UnLoaded state.

+ +

Parameter

+ +
+
+
+
+ + Wrapper.Controllable#CONTROLLABLE + +AI_CARGO.CargoCarrier + +
+
+ +

The alive DCS object carrying the cargo. This value can be nil, meaning, that the cargo is not contained anywhere...

+ +
+
+
+
+ + Wrapper.Controllable#CONTROLLABLE + +AI_CARGO.CargoObject + +
+
+ +

The alive DCS object representing the cargo. This value can be nil, meaning, that the cargo is not represented anywhere...

+ +
+
+
+
+ + #string + +AI_CARGO.ClassName
@@ -185,13 +832,39 @@
- - -CARGO_PACKAGE + #boolean + +AI_CARGO.Containable
+

This flag defines if the cargo can be contained within a DCS Unit.

+ +
+
+
+
+ + +AI_CARGO:IsNear(PointVec2) + +
+
+ +

Check if CargoCarrier is near the Cargo to be Loaded.

+ +

Parameter

+ +

Return value

+ +

#boolean:

@@ -199,13 +872,118 @@
- - -CARGO_SLINGLOAD + +AI_CARGO:Load(ToCarrier)
+

Loads the cargo to a Carrier.

+ + +

The event will load the cargo into the Carrier regardless of its position. There will be no movement simulated of the cargo loading. +The cargo must be in the UnLoaded state.

+ +

Parameter

+ +
+
+
+
+ + #boolean + +AI_CARGO.Moveable + +
+
+ +

This flag defines if the cargo is moveable.

+ +
+
+
+
+ + #string + +AI_CARGO.Name + +
+
+ +

A string defining the name of the cargo. The name is the unique identifier of the cargo.

+ +
+
+
+
+ + #number + +AI_CARGO.NearRadius + +
+
+ +

(optional) A number defining the radius in meters when the cargo is near to a Carrier, so that it can be loaded.

+ +
+
+
+
+ + +AI_CARGO:New(Type, Name, Weight, ReportRadius, NearRadius) + +
+
+ +

AI_CARGO Constructor.

+ + +

This class is an abstract class and should not be instantiated.

+ +

Parameters

+
    +
  • + +

    #string Type :

    + +
  • +
  • + +

    #string Name :

    + +
  • +
  • + +

    #number Weight :

    + +
  • +
  • + +

    #number ReportRadius : +(optional)

    + +
  • +
  • + +

    #number NearRadius : +(optional)

    + +
  • +
+

Return value

+ +

#AI_CARGO:

@@ -213,35 +991,1857 @@
- - -CARGO_ZONE + +AI_CARGO:OnAfterBoarding(Controllable)
+

Parameter

+ +
+
+
+
+ + +AI_CARGO:OnAfterLoaded(Controllable) + +
+
+ + + +

Parameter

+ +
+
+
+
+ + +AI_CARGO:OnAfterUnBoarding(Controllable) + +
+
+ + + +

Parameter

+ +
+
+
+
+ + +AI_CARGO:OnAfterUnLoaded(Controllable) + +
+
+ + + +

Parameter

+ +
+
+
+
+ + +AI_CARGO:OnBeforeBoarding(Controllable) + +
+
+ + + +

Parameter

+ +

Return value

+ +

#boolean:

+ +
- - -CargoStatic + +AI_CARGO:OnBeforeLoaded(Controllable)
+

Parameter

+ +

Return value

+ +

#boolean:

+ +
-

Type CARGO

+
+
+ + +AI_CARGO:OnBeforeUnBoarding(Controllable) + +
+
-

Type CARGO_ZONE

+ + +

Parameter

+ +

Return value

+ +

#boolean:

+ + +
+
+
+
+ + +AI_CARGO:OnBeforeUnLoaded(Controllable) + +
+
+ + + +

Parameter

+ +

Return value

+ +

#boolean:

+ + +
+
+
+
+ + #number + +AI_CARGO.ReportRadius + +
+
+ +

(optional) A number defining the radius in meters when the cargo is signalling or reporting to a Carrier.

+ +
+
+
+
+ + #boolean + +AI_CARGO.Representable + +
+
+ +

This flag defines if the cargo can be represented by a DCS Unit.

+ +
+
+
+
+ + #boolean + +AI_CARGO.Slingloadable + +
+
+ +

This flag defines if the cargo can be slingloaded.

+ +
+
+
+
+ + +AI_CARGO:Spawn(PointVec2) + +
+
+ +

Template method to spawn a new representation of the AI_CARGO in the simulator.

+ +

Parameter

+
    +
  • + +

    PointVec2 :

    + +
  • +
+

Return value

+ +

#AI_CARGO:

+ + +
+
+
+
+ + #string + +AI_CARGO.Type + +
+
+ +

A string defining the type of the cargo. eg. Engineers, Equipment, Screwdrivers.

+ +
+
+
+
+ + +AI_CARGO:UnBoard(ToPointVec2) + +
+
+ +

UnBoards the cargo to a Carrier.

+ + +

The event will create a movement (= running or driving) of the cargo from the Carrier. +The cargo must be in the Loaded state.

+ +

Parameter

+
    +
  • + +

    Core.Point#POINT_VEC2 ToPointVec2 : +(optional) @{Core.Point#POINT_VEC2) to where the cargo should run after onboarding. If not provided, the cargo will run to 60 meters behind the Carrier location.

    + +
  • +
+
+
+
+
+ + +AI_CARGO:UnLoad(ToPointVec2) + +
+
+ +

UnLoads the cargo to a Carrier.

+ + +

The event will unload the cargo from the Carrier. There will be no movement simulated of the cargo loading. +The cargo must be in the Loaded state.

+ +

Parameter

+
    +
  • + +

    Core.Point#POINT_VEC2 ToPointVec2 : +(optional) @{Core.Point#POINT_VEC2) to where the cargo will be placed after unloading. If not provided, the cargo will be placed 60 meters behind the Carrier location.

    + +
  • +
+
+
+
+
+ + #number + +AI_CARGO.Weight + +
+
+ +

A number defining the weight of the cargo. The weight is expressed in kg.

+ +
+
+
+
+ + +AI_CARGO:__Board(DelaySeconds, ToCarrier) + +
+
+ +

Boards the cargo to a Carrier.

+ + +

The event will create a movement (= running or driving) of the cargo to the Carrier. +The cargo must be in the UnLoaded state.

+ +

Parameters

+
    +
  • + +

    #number DelaySeconds : +The amount of seconds to delay the action.

    + +
  • +
  • + +

    Wrapper.Controllable#CONTROLLABLE ToCarrier : +The Carrier that will hold the cargo.

    + +
  • +
+
+
+
+
+ + +AI_CARGO:__Load(DelaySeconds, ToCarrier) + +
+
+ +

Loads the cargo to a Carrier.

+ + +

The event will load the cargo into the Carrier regardless of its position. There will be no movement simulated of the cargo loading. +The cargo must be in the UnLoaded state.

+ +

Parameters

+
    +
  • + +

    #number DelaySeconds : +The amount of seconds to delay the action.

    + +
  • +
  • + +

    Wrapper.Controllable#CONTROLLABLE ToCarrier : +The Carrier that will hold the cargo.

    + +
  • +
+
+
+
+
+ + +AI_CARGO:__UnBoard(DelaySeconds, ToPointVec2) + +
+
+ +

UnBoards the cargo to a Carrier.

+ + +

The event will create a movement (= running or driving) of the cargo from the Carrier. +The cargo must be in the Loaded state.

+ +

Parameters

+
    +
  • + +

    #number DelaySeconds : +The amount of seconds to delay the action.

    + +
  • +
  • + +

    Core.Point#POINT_VEC2 ToPointVec2 : +(optional) @{Core.Point#POINT_VEC2) to where the cargo should run after onboarding. If not provided, the cargo will run to 60 meters behind the Carrier location.

    + +
  • +
+
+
+
+
+ + +AI_CARGO:__UnLoad(DelaySeconds, ToPointVec2) + +
+
+ +

UnLoads the cargo to a Carrier.

+ + +

The event will unload the cargo from the Carrier. There will be no movement simulated of the cargo loading. +The cargo must be in the Loaded state.

+ +

Parameters

+
    +
  • + +

    #number DelaySeconds : +The amount of seconds to delay the action.

    + +
  • +
  • + +

    Core.Point#POINT_VEC2 ToPointVec2 : +(optional) @{Core.Point#POINT_VEC2) to where the cargo will be placed after unloading. If not provided, the cargo will be placed 60 meters behind the Carrier location.

    + +
  • +
+
+
+ +

Type AI_CARGO.CargoObjects

+

Type AI_CARGO_GROUP

+

Field(s)

+
+
+ + Set#SET_BASE + +AI_CARGO_GROUP.CargoSet + +
+
+ +

A set of cargo objects.

+ +
+
+
+
+ + #string + +AI_CARGO_GROUP.ClassName + +
+
+ + + +
+
+
+
+ + #string + +AI_CARGO_GROUP.Name + +
+
+ +

A string defining the name of the cargo group. The name is the unique identifier of the cargo.

+ +
+
+
+
+ + +AI_CARGO_GROUP:New(CargoSet, Type, Name, Weight, ReportRadius, NearRadius) + +
+
+ +

AICARGOGROUP constructor.

+ +

Parameters

+
    +
  • + +

    Core.Set#Set_BASE CargoSet :

    + +
  • +
  • + +

    #string Type :

    + +
  • +
  • + +

    #string Name :

    + +
  • +
  • + +

    #number Weight :

    + +
  • +
  • + +

    #number ReportRadius : +(optional)

    + +
  • +
  • + +

    #number NearRadius : +(optional)

    + +
  • +
+

Return value

+ +

#AICARGOGROUP:

+ + +
+
+ +

Type AI_CARGO_GROUPED

+

Field(s)

+
+
+ + #string + +AI_CARGO_GROUPED.ClassName + +
+
+ + + +
+
+
+
+ + +AI_CARGO_GROUPED:New(CargoSet, Type, Name, Weight, ReportRadius, NearRadius) + +
+
+ +

AICARGOGROUPED constructor.

+ +

Parameters

+
    +
  • + +

    Core.Set#Set_BASE CargoSet :

    + +
  • +
  • + +

    #string Type :

    + +
  • +
  • + +

    #string Name :

    + +
  • +
  • + +

    #number Weight :

    + +
  • +
  • + +

    #number ReportRadius : +(optional)

    + +
  • +
  • + +

    #number NearRadius : +(optional)

    + +
  • +
+

Return value

+ +

#AICARGOGROUPED:

+ + +
+
+
+
+ + +AI_CARGO_GROUPED:onafterUnBoarding(ToPointVec2, Event, From, To) + +
+
+ +

UnBoard Event.

+ +

Parameters

+
    +
  • + +

    Core.Point#POINT_VEC2 ToPointVec2 :

    + +
  • +
  • + +

    #string Event :

    + +
  • +
  • + +

    #string From :

    + +
  • +
  • + +

    #string To :

    + +
  • +
+
+
+
+
+ + +AI_CARGO_GROUPED:onenterBoarding(CargoCarrier, Event, From, To) + +
+
+ +

Enter Boarding State.

+ +

Parameters

+
    +
  • + +

    Wrapper.Unit#UNIT CargoCarrier :

    + +
  • +
  • + +

    #string Event :

    + +
  • +
  • + +

    #string From :

    + +
  • +
  • + +

    #string To :

    + +
  • +
+
+
+
+
+ + +AI_CARGO_GROUPED:onenterLoaded(CargoCarrier, Event, From, To) + +
+
+ +

Enter Loaded State.

+ +

Parameters

+
    +
  • + +

    Wrapper.Unit#UNIT CargoCarrier :

    + +
  • +
  • + +

    #string Event :

    + +
  • +
  • + +

    #string From :

    + +
  • +
  • + +

    #string To :

    + +
  • +
+
+
+
+
+ + +AI_CARGO_GROUPED:onenterUnBoarding(ToPointVec2, Event, From, To) + +
+
+ +

Enter UnBoarding State.

+ +

Parameters

+
    +
  • + +

    Core.Point#POINT_VEC2 ToPointVec2 :

    + +
  • +
  • + +

    #string Event :

    + +
  • +
  • + +

    #string From :

    + +
  • +
  • + +

    #string To :

    + +
  • +
+
+
+
+
+ + +AI_CARGO_GROUPED:onenterUnLoaded(Core, Event, From, To, ToPointVec2) + +
+
+ +

Enter UnLoaded State.

+ +

Parameters

+
    +
  • + +

    Core : +Point#POINT_VEC2

    + +
  • +
  • + +

    #string Event :

    + +
  • +
  • + +

    #string From :

    + +
  • +
  • + +

    #string To :

    + +
  • +
  • + +

    ToPointVec2 :

    + +
  • +
+
+
+
+
+ + +AI_CARGO_GROUPED:onleaveBoarding(CargoCarrier, Event, From, To) + +
+
+ +

Leave Boarding State.

+ +

Parameters

+
    +
  • + +

    Wrapper.Unit#UNIT CargoCarrier :

    + +
  • +
  • + +

    #string Event :

    + +
  • +
  • + +

    #string From :

    + +
  • +
  • + +

    #string To :

    + +
  • +
+
+
+
+
+ + +AI_CARGO_GROUPED:onleaveUnBoarding(ToPointVec2, Event, From, To) + +
+
+ +

Leave UnBoarding State.

+ +

Parameters

+
    +
  • + +

    Core.Point#POINT_VEC2 ToPointVec2 :

    + +
  • +
  • + +

    #string Event :

    + +
  • +
  • + +

    #string From :

    + +
  • +
  • + +

    #string To :

    + +
  • +
+
+
+ +

Type AI_CARGO_PACKAGE

+

Field(s)

+
+
+ + + +AI_CARGO_PACKAGE.CargoCarrier + +
+
+ + + +
+
+
+
+ + + +AI_CARGO_PACKAGE.CargoInAir + +
+
+ + + +
+
+
+
+ + #string + +AI_CARGO_PACKAGE.ClassName + +
+
+ + + +
+
+
+
+ + +AI_CARGO_PACKAGE:IsNear(CargoCarrier) + +
+
+ +

Check if CargoCarrier is near the Cargo to be Loaded.

+ +

Parameter

+ +

Return value

+ +

#boolean:

+ + +
+
+
+
+ + +AI_CARGO_PACKAGE:New(CargoCarrier, Type, Name, Weight, ReportRadius, NearRadius) + +
+
+ +

AICARGOPACKAGE Constructor.

+ +

Parameters

+
    +
  • + +

    Wrapper.Unit#UNIT CargoCarrier : +The UNIT carrying the package.

    + +
  • +
  • + +

    #string Type :

    + +
  • +
  • + +

    #string Name :

    + +
  • +
  • + +

    #number Weight :

    + +
  • +
  • + +

    #number ReportRadius : +(optional)

    + +
  • +
  • + +

    #number NearRadius : +(optional)

    + +
  • +
+

Return value

+ +

#AICARGOPACKAGE:

+ + +
+
+
+
+ + +AI_CARGO_PACKAGE:onafterLoad(Event, From, To, CargoCarrier, Speed, LoadDistance, Angle) + +
+
+ +

Load Event.

+ +

Parameters

+
    +
  • + +

    #string Event :

    + +
  • +
  • + +

    #string From :

    + +
  • +
  • + +

    #string To :

    + +
  • +
  • + +

    Wrapper.Unit#UNIT CargoCarrier :

    + +
  • +
  • + +

    #number Speed :

    + +
  • +
  • + +

    #number LoadDistance :

    + +
  • +
  • + +

    #number Angle :

    + +
  • +
+
+
+
+
+ + +AI_CARGO_PACKAGE:onafterOnBoard(Event, From, To, CargoCarrier, Speed, BoardDistance, Angle, LoadDistance) + +
+
+ +

Board Event.

+ +

Parameters

+
    +
  • + +

    #string Event :

    + +
  • +
  • + +

    #string From :

    + +
  • +
  • + +

    #string To :

    + +
  • +
  • + +

    Wrapper.Unit#UNIT CargoCarrier :

    + +
  • +
  • + +

    #number Speed :

    + +
  • +
  • + +

    #number BoardDistance :

    + +
  • +
  • + +

    #number Angle :

    + +
  • +
  • + +

    LoadDistance :

    + +
  • +
+
+
+
+
+ + +AI_CARGO_PACKAGE:onafterOnBoarded(Event, From, To, CargoCarrier, Speed, BoardDistance, LoadDistance, Angle) + +
+
+ +

Boarded Event.

+ +

Parameters

+
    +
  • + +

    #string Event :

    + +
  • +
  • + +

    #string From :

    + +
  • +
  • + +

    #string To :

    + +
  • +
  • + +

    Wrapper.Unit#UNIT CargoCarrier :

    + +
  • +
  • + +

    Speed :

    + +
  • +
  • + +

    BoardDistance :

    + +
  • +
  • + +

    LoadDistance :

    + +
  • +
  • + +

    Angle :

    + +
  • +
+
+
+
+
+ + +AI_CARGO_PACKAGE:onafterUnBoard(Event, From, To, Speed, UnLoadDistance, UnBoardDistance, Radius, Angle, CargoCarrier) + +
+
+ +

UnBoard Event.

+ +

Parameters

+
    +
  • + +

    #string Event :

    + +
  • +
  • + +

    #string From :

    + +
  • +
  • + +

    #string To :

    + +
  • +
  • + +

    #number Speed :

    + +
  • +
  • + +

    #number UnLoadDistance :

    + +
  • +
  • + +

    #number UnBoardDistance :

    + +
  • +
  • + +

    #number Radius :

    + +
  • +
  • + +

    #number Angle :

    + +
  • +
  • + +

    CargoCarrier :

    + +
  • +
+
+
+
+
+ + +AI_CARGO_PACKAGE:onafterUnBoarded(Event, From, To, CargoCarrier, Speed) + +
+
+ +

UnBoarded Event.

+ +

Parameters

+
    +
  • + +

    #string Event :

    + +
  • +
  • + +

    #string From :

    + +
  • +
  • + +

    #string To :

    + +
  • +
  • + +

    Wrapper.Unit#UNIT CargoCarrier :

    + +
  • +
  • + +

    Speed :

    + +
  • +
+
+
+
+
+ + +AI_CARGO_PACKAGE:onafterUnLoad(Event, From, To, Distance, Angle, CargoCarrier, Speed) + +
+
+ +

UnLoad Event.

+ +

Parameters

+
    +
  • + +

    #string Event :

    + +
  • +
  • + +

    #string From :

    + +
  • +
  • + +

    #string To :

    + +
  • +
  • + +

    #number Distance :

    + +
  • +
  • + +

    #number Angle :

    + +
  • +
  • + +

    CargoCarrier :

    + +
  • +
  • + +

    Speed :

    + +
  • +
+
+
+ +

Type AI_CARGO_REPRESENTABLE

+

Field(s)

+
+
+ + #string + +AI_CARGO_REPRESENTABLE.ClassName + +
+
+ + + +
+
+
+
+ + +AI_CARGO_REPRESENTABLE:New(CargoObject, Type, Name, Weight, ReportRadius, NearRadius) + +
+
+ +

AICARGOREPRESENTABLE Constructor.

+ +

Parameters

+
    +
  • + +

    Wrapper.Controllable#Controllable CargoObject :

    + +
  • +
  • + +

    #string Type :

    + +
  • +
  • + +

    #string Name :

    + +
  • +
  • + +

    #number Weight :

    + +
  • +
  • + +

    #number ReportRadius : +(optional)

    + +
  • +
  • + +

    #number NearRadius : +(optional)

    + +
  • +
+

Return value

+ +

#AICARGOREPRESENTABLE:

+ + +
+
+
+
+ + +AI_CARGO_REPRESENTABLE:RouteTo(ToPointVec2, Speed) + +
+
+ +

Route a cargo unit to a PointVec2.

+ +

Parameters

+ +

Return value

+ +

#AICARGOREPRESENTABLE:

+ + +
+
+ +

Type AI_CARGO_UNIT

+

Field(s)

+
+
+ + + +AI_CARGO_UNIT.CargoCarrier + +
+
+ + + +
+
+
+
+ + + +AI_CARGO_UNIT.CargoInAir + +
+
+ + + +
+
+
+
+ + + +AI_CARGO_UNIT.CargoObject + +
+
+ + + +
+
+
+
+ + #string + +AI_CARGO_UNIT.ClassName + +
+
+ + + +
+
+
+
+ + +AI_CARGO_UNIT:New(CargoUnit, Type, Name, Weight, ReportRadius, NearRadius) + +
+
+ +

AICARGOUNIT Constructor.

+ +

Parameters

+
    +
  • + +

    Wrapper.Unit#UNIT CargoUnit :

    + +
  • +
  • + +

    #string Type :

    + +
  • +
  • + +

    #string Name :

    + +
  • +
  • + +

    #number Weight :

    + +
  • +
  • + +

    #number ReportRadius : +(optional)

    + +
  • +
  • + +

    #number NearRadius : +(optional)

    + +
  • +
+

Return value

+ +

#AICARGOUNIT:

+ + +
+
+
+
+ + +AI_CARGO_UNIT.OnUnLoadedCallBack + +
+
+ + + +
+
+
+
+ + +AI_CARGO_UNIT:onafterBoard(Event, From, To, CargoCarrier) + +
+
+ +

Board Event.

+ +

Parameters

+
    +
  • + +

    #string Event :

    + +
  • +
  • + +

    #string From :

    + +
  • +
  • + +

    #string To :

    + +
  • +
  • + +

    CargoCarrier :

    + +
  • +
+
+
+
+
+ + +AI_CARGO_UNIT:onafterUnBoarding(Event, From, To, ToPointVec2) + +
+
+ +

UnBoard Event.

+ +

Parameters

+
    +
  • + +

    #string Event :

    + +
  • +
  • + +

    #string From :

    + +
  • +
  • + +

    #string To :

    + +
  • +
  • + +

    Core.Point#POINT_VEC2 ToPointVec2 :

    + +
  • +
+
+
+
+
+ + +AI_CARGO_UNIT:onenterBoarding(Event, From, To, CargoCarrier) + +
+
+ +

Enter Boarding State.

+ +

Parameters

+
    +
  • + +

    #string Event :

    + +
  • +
  • + +

    #string From :

    + +
  • +
  • + +

    #string To :

    + +
  • +
  • + +

    Wrapper.Unit#UNIT CargoCarrier :

    + +
  • +
+
+
+
+
+ + +AI_CARGO_UNIT:onenterLoaded(Event, From, To, CargoCarrier) + +
+
+ +

Loaded State.

+ +

Parameters

+
    +
  • + +

    #string Event :

    + +
  • +
  • + +

    #string From :

    + +
  • +
  • + +

    #string To :

    + +
  • +
  • + +

    Wrapper.Unit#UNIT CargoCarrier :

    + +
  • +
+
+
+
+
+ + +AI_CARGO_UNIT:onenterUnBoarding(Event, From, To, ToPointVec2) + +
+
+ +

Enter UnBoarding State.

+ +

Parameters

+
    +
  • + +

    #string Event :

    + +
  • +
  • + +

    #string From :

    + +
  • +
  • + +

    #string To :

    + +
  • +
  • + +

    Core.Point#POINT_VEC2 ToPointVec2 :

    + +
  • +
+
+
+
+
+ + +AI_CARGO_UNIT:onenterUnLoaded(Event, From, To, Core, ToPointVec2) + +
+
+ +

Enter UnLoaded State.

+ +

Parameters

+
    +
  • + +

    #string Event :

    + +
  • +
  • + +

    #string From :

    + +
  • +
  • + +

    #string To :

    + +
  • +
  • + +

    Core : +Point#POINT_VEC2

    + +
  • +
  • + +

    ToPointVec2 :

    + +
  • +
+
+
+
+
+ + +AI_CARGO_UNIT:onleaveBoarding(Event, From, To, CargoCarrier) + +
+
+ +

Leave Boarding State.

+ +

Parameters

+
    +
  • + +

    #string Event :

    + +
  • +
  • + +

    #string From :

    + +
  • +
  • + +

    #string To :

    + +
  • +
  • + +

    Wrapper.Unit#UNIT CargoCarrier :

    + +
  • +
+
+
+
+
+ + +AI_CARGO_UNIT:onleaveUnBoarding(Event, From, To, ToPointVec2) + +
+
+ +

Leave UnBoarding State.

+ +

Parameters

+
    +
  • + +

    #string Event :

    + +
  • +
  • + +

    #string From :

    + +
  • +
  • + +

    #string To :

    + +
  • +
  • + +

    Core.Point#POINT_VEC2 ToPointVec2 :

    + +
  • +
+
+
+
diff --git a/Moose Training/Documentation/CleanUp.html b/Moose Training/Documentation/CleanUp.html index e5f550022..ae98a5943 100644 --- a/Moose Training/Documentation/CleanUp.html +++ b/Moose Training/Documentation/CleanUp.html @@ -17,13 +17,16 @@ index

Return value

-

DCSTask#Task:

+

Dcs.DCSTasking.Task#Task:

Usage:

@@ -907,7 +858,7 @@ SCHEDULER:New( nil,
-

Return the route of a controllable by using the Database#DATABASE class.

+

Return the route of a controllable by using the Core.Database#DATABASE class.

Parameters

@@ -989,7 +940,7 @@ The DCS task structure.

Return value

-

DCSTask#Task: +

Dcs.DCSTasking.Task#Task: The DCS task structure.

@@ -1012,7 +963,7 @@ The DCS task structure.

  • -

    Controllable#CONTROLLABLE AttackGroup : +

    Wrapper.Controllable#CONTROLLABLE AttackGroup : The Controllable to be attacked.

  • @@ -1030,7 +981,7 @@ All en-route tasks have the priority parameter. This is a number (less value - h
  • -

    DCSTypes#AI.Task.WeaponExpend WeaponExpend : +

    Dcs.DCSTypes#AI.Task.WeaponExpend WeaponExpend : (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion.

  • @@ -1042,13 +993,13 @@ All en-route tasks have the priority parameter. This is a number (less value - h
  • -

    DCSTypes#Azimuth Direction : +

    Dcs.DCSTypes#Azimuth Direction : (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction.

  • -

    DCSTypes#Distance Altitude : +

    Dcs.DCSTypes#Distance Altitude : (optional) Desired attack start altitude. Controllable/aircraft will make its attacks from the altitude. If the altitude is too low or too high to use weapon aircraft/controllable will choose closest altitude to the desired attack start altitude. If the desired altitude is defined controllable/aircraft will not attack from safe altitude.

  • @@ -1061,7 +1012,7 @@ All en-route tasks have the priority parameter. This is a number (less value - h

Return value

-

DCSTask#Task: +

Dcs.DCSTasking.Task#Task: The DCS task structure.

@@ -1081,13 +1032,13 @@ The DCS task structure.

Return value

-

DCSTask#Task: +

Dcs.DCSTasking.Task#Task: The DCS task structure.

@@ -1120,7 +1071,7 @@ The DCS task structure.

  • -

    Unit#UNIT AttackUnit : +

    Wrapper.Unit#UNIT AttackUnit : The UNIT.

  • @@ -1138,7 +1089,7 @@ All en-route tasks have the priority parameter. This is a number (less value - h
  • -

    DCSTypes#AI.Task.WeaponExpend WeaponExpend : +

    Dcs.DCSTypes#AI.Task.WeaponExpend WeaponExpend : (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion.

  • @@ -1150,7 +1101,7 @@ All en-route tasks have the priority parameter. This is a number (less value - h
  • -

    DCSTypes#Azimuth Direction : +

    Dcs.DCSTypes#Azimuth Direction : (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction.

  • @@ -1169,7 +1120,7 @@ All en-route tasks have the priority parameter. This is a number (less value - h

Return value

-

DCSTask#Task: +

Dcs.DCSTasking.Task#Task: The DCS task structure.

@@ -1193,7 +1144,7 @@ If the task is assigned to the controllable lead unit will be a FAC.

  • -

    DCSTypes#Distance Radius : +

    Dcs.DCSTypes#Distance Radius : The maximal distance from the FAC to a target.

  • @@ -1206,7 +1157,7 @@ All en-route tasks have the priority parameter. This is a number (less value - h

Return value

-

DCSTask#Task: +

Dcs.DCSTasking.Task#Task: The DCS task structure.

@@ -1230,7 +1181,7 @@ If the task is assigned to the controllable lead unit will be a FAC.

Return value

-

DCSTask#Task: +

Dcs.DCSTasking.Task#Task: The DCS task structure.

@@ -1282,7 +1233,7 @@ The DCS task structure.

Return value

-

DCSTask#Task: +

Dcs.DCSTasking.Task#Task: The DCS task structure.

@@ -1346,39 +1297,6 @@ If no detection method is given, the detection will use all the available method

#table: DetectedTargets

- - -
-
- - -CONTROLLABLE:GetMessage(Message, Duration) - -
-
- -

Returns a message with the callsign embedded (if there is one).

- -

Parameters

-
    -
  • - -

    #string Message : -The message text

    - -
  • -
  • - -

    DCSTypes#Duration Duration : -The duration of the message.

    - -
  • -
-

Return value

- -

Message#MESSAGE:

- -
@@ -1442,204 +1360,6 @@ The mission route defined by points.

- -CONTROLLABLE:Message(Message, Duration) - -
-
- -

Send a message to the players in the Group.

- - -

The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message.

- -

Parameters

-
    -
  • - -

    #string Message : -The message text

    - -
  • -
  • - -

    DCSTypes#Duration Duration : -The duration of the message.

    - -
  • -
-
-
-
-
- - -CONTROLLABLE:MessageToAll(Message, Duration) - -
-
- -

Send a message to all coalitions.

- - -

The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message.

- -

Parameters

-
    -
  • - -

    #string Message : -The message text

    - -
  • -
  • - -

    DCSTypes#Duration Duration : -The duration of the message.

    - -
  • -
-
-
-
-
- - -CONTROLLABLE:MessageToBlue(Message, Duration) - -
-
- -

Send a message to the blue coalition.

- - -

The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message.

- -

Parameters

-
    -
  • - -

    #string Message : -The message text

    - -
  • -
  • - -

    DCSTypes#Duration Duration : -The duration of the message.

    - -
  • -
-
-
-
-
- - -CONTROLLABLE:MessageToClient(Message, Duration, Client) - -
-
- -

Send a message to a client.

- - -

The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message.

- -

Parameters

-
    -
  • - -

    #string Message : -The message text

    - -
  • -
  • - -

    DCSTypes#Duration Duration : -The duration of the message.

    - -
  • -
  • - -

    Client#CLIENT Client : -The client object receiving the message.

    - -
  • -
-
-
-
-
- - -CONTROLLABLE:MessageToGroup(Message, Duration, MessageGroup) - -
-
- -

Send a message to a Group.

- - -

The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message.

- -

Parameters

-
    -
  • - -

    #string Message : -The message text

    - -
  • -
  • - -

    DCSTypes#Duration Duration : -The duration of the message.

    - -
  • -
  • - -

    Group#GROUP MessageGroup : -The GROUP object receiving the message.

    - -
  • -
-
-
-
-
- - -CONTROLLABLE:MessageToRed(Message, Duration) - -
-
- -

Send a message to the red coalition.

- - -

The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message.

- -

Parameters

-
    -
  • - -

    #string Message : -The message text

    - -
  • -
  • - -

    DCSTYpes#Duration Duration : -The duration of the message.

    - -
  • -
-
-
-
-
- CONTROLLABLE:New(ControllableName) @@ -1652,7 +1372,7 @@ The duration of the message.

Return value

-

Controllable#CONTROLLABLE: +

Wrapper.Controllable#CONTROLLABLE: self

@@ -2037,7 +1757,7 @@ self

-

(AIR) Return the Controllable to an Airbase#AIRBASE +

(AIR) Return the Controllable to an Wrapper.Airbase#AIRBASE A speed can be given in km/h.

@@ -2047,8 +1767,8 @@ A speed can be given in km/h.

Return value

-

Controllable#CONTROLLABLE: +

Wrapper.Controllable#CONTROLLABLE: self

@@ -2137,7 +1857,7 @@ self

  • -

    Controllable#CONTROLLABLE AttackGroup : +

    Wrapper.Controllable#CONTROLLABLE AttackGroup : The Controllable to be attacked.

  • @@ -2149,7 +1869,7 @@ The Controllable to be attacked.

  • -

    DCSTypes#AI.Task.WeaponExpend WeaponExpend : +

    Dcs.DCSTypes#AI.Task.WeaponExpend WeaponExpend : (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion.

  • @@ -2161,13 +1881,13 @@ The Controllable to be attacked.

  • -

    DCSTypes#Azimuth Direction : +

    Dcs.DCSTypes#Azimuth Direction : (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction.

  • -

    DCSTypes#Distance Altitude : +

    Dcs.DCSTypes#Distance Altitude : (optional) Desired attack start altitude. Controllable/aircraft will make its attacks from the altitude. If the altitude is too low or too high to use weapon aircraft/controllable will choose closest altitude to the desired attack start altitude. If the desired altitude is defined controllable/aircraft will not attack from safe altitude.

  • @@ -2180,7 +1900,7 @@ The Controllable to be attacked.

Return value

-

DCSTask#Task: +

Dcs.DCSTasking.Task#Task: The DCS task structure.

@@ -2189,7 +1909,7 @@ The DCS task structure.

-CONTROLLABLE:TaskAttackMapObject(PointVec2, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack) +CONTROLLABLE:TaskAttackMapObject(Vec2, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack)
@@ -2200,7 +1920,7 @@ The DCS task structure.

  • -

    DCSTypes#Vec2 PointVec2 : +

    Dcs.DCSTypes#Vec2 Vec2 : 2D-coordinates of the point the map object is closest to. The distance between the point and the map object must not be greater than 2000 meters. Object id is not used here because Mission Editor doesn't support map object identificators.

  • @@ -2212,7 +1932,7 @@ The DCS task structure.

  • -

    DCSTypes#AI.Task.WeaponExpend WeaponExpend : +

    Dcs.DCSTypes#AI.Task.WeaponExpend WeaponExpend : (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion.

  • @@ -2224,7 +1944,7 @@ The DCS task structure.

  • -

    DCSTypes#Azimuth Direction : +

    Dcs.DCSTypes#Azimuth Direction : (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction.

  • @@ -2237,7 +1957,7 @@ The DCS task structure.

Return value

-

DCSTask#Task: +

Dcs.DCSTasking.Task#Task: The DCS task structure.

@@ -2257,7 +1977,7 @@ The DCS task structure.

  • -

    Unit#UNIT AttackUnit : +

    Wrapper.Unit#UNIT AttackUnit : The unit.

  • @@ -2269,7 +1989,7 @@ The unit.

  • -

    DCSTypes#AI.Task.WeaponExpend WeaponExpend : +

    Dcs.DCSTypes#AI.Task.WeaponExpend WeaponExpend : (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion.

  • @@ -2281,7 +2001,7 @@ The unit.

  • -

    DCSTypes#Azimuth Direction : +

    Dcs.DCSTypes#Azimuth Direction : (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction.

  • @@ -2300,7 +2020,7 @@ The unit.

Return value

-

DCSTask#Task: +

Dcs.DCSTasking.Task#Task: The DCS task structure.

@@ -2309,7 +2029,7 @@ The DCS task structure.

-CONTROLLABLE:TaskBombing(PointVec2, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack) +CONTROLLABLE:TaskBombing(Vec2, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack)
@@ -2320,7 +2040,7 @@ The DCS task structure.

  • -

    DCSTypes#Vec2 PointVec2 : +

    Dcs.DCSTypes#Vec2 Vec2 : 2D-coordinates of the point to deliver weapon at.

  • @@ -2332,7 +2052,7 @@ The DCS task structure.

  • -

    DCSTypes#AI.Task.WeaponExpend WeaponExpend : +

    Dcs.DCSTypes#AI.Task.WeaponExpend WeaponExpend : (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion.

  • @@ -2344,7 +2064,7 @@ The DCS task structure.

  • -

    DCSTypes#Azimuth Direction : +

    Dcs.DCSTypes#Azimuth Direction : (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction.

  • @@ -2357,7 +2077,7 @@ The DCS task structure.

Return value

-

DCSTask#Task: +

Dcs.DCSTasking.Task#Task: The DCS task structure.

@@ -2377,7 +2097,7 @@ The DCS task structure.

  • -

    Airbase#AIRBASE Airbase : +

    Wrapper.Airbase#AIRBASE Airbase : Airbase to attack.

  • @@ -2389,7 +2109,7 @@ Airbase to attack.

  • -

    DCSTypes#AI.Task.WeaponExpend WeaponExpend : +

    Dcs.DCSTypes#AI.Task.WeaponExpend WeaponExpend : (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion.

  • @@ -2401,7 +2121,7 @@ Airbase to attack.

  • -

    DCSTypes#Azimuth Direction : +

    Dcs.DCSTypes#Azimuth Direction : (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction.

  • @@ -2414,7 +2134,7 @@ Airbase to attack.

Return value

-

DCSTask#Task: +

Dcs.DCSTasking.Task#Task: The DCS task structure.

@@ -2434,14 +2154,14 @@ The DCS task structure.

Return value

-

DCSTask#Task:

+

Dcs.DCSTasking.Task#Task:

@@ -2461,7 +2181,7 @@ Array of DCSTask#Task

@@ -2508,7 +2228,7 @@ return DCSTask#Task

Return value

-

DCSTask#Task:

+

Dcs.DCSTasking.Task#Task:

@@ -2539,7 +2259,7 @@ return DCSTask#Task

Return value

-

DCSTask#Task: +

Dcs.DCSTasking.Task#Task: The DCS task structure.

@@ -2572,7 +2292,7 @@ The DCS task structure.

Return value

-

DCSTask#Task: +

Dcs.DCSTasking.Task#Task: The DCS task structure

@@ -2600,7 +2320,7 @@ The DCS task structure

-CONTROLLABLE:TaskEscort(EscortControllable, PointVec3, LastWaypointIndex, EngagementDistanceMax, TargetTypes, FollowControllable, EngagementDistance) +CONTROLLABLE:TaskEscort(EscortControllable, Vec3, LastWaypointIndex, EngagementDistanceMax, TargetTypes, FollowControllable, EngagementDistance)
@@ -2615,13 +2335,13 @@ The unit / controllable will also protect that controllable from threats of spec
@@ -2680,7 +2400,7 @@ If the task is assigned to the controllable lead unit will be a FAC.

Return value

-

DCSTask#Task: +

Dcs.DCSTasking.Task#Task: The DCS task structure.

@@ -2714,7 +2434,7 @@ The DCS task structure.

-CONTROLLABLE:TaskFireAtPoint(PointVec2, Radius) +CONTROLLABLE:TaskFireAtPoint(Vec2, Radius)
@@ -2725,20 +2445,20 @@ The DCS task structure.

Return value

-

DCSTask#Task: +

Dcs.DCSTasking.Task#Task: The DCS task structure.

@@ -2747,7 +2467,7 @@ The DCS task structure.

-CONTROLLABLE:TaskFollow(FollowControllable, PointVec3, LastWaypointIndex) +CONTROLLABLE:TaskFollow(FollowControllable, Vec3, LastWaypointIndex)
@@ -2762,13 +2482,13 @@ If another controllable is on land the unit / controllable will orbit around.
  • -

    Controllable#CONTROLLABLE FollowControllable : +

    Wrapper.Controllable#CONTROLLABLE FollowControllable : The controllable to be followed.

  • -

    DCSTypes#Vec3 PointVec3 : +

    Dcs.DCSTypes#Vec3 Vec3 : Position of the unit / lead unit of the controllable relative lead unit of another controllable in frame reference oriented by course of lead unit of another controllable. If another controllable is on land the unit / controllable will orbit around.

  • @@ -2781,7 +2501,7 @@ Detach waypoint of another controllable. Once reached the unit / controllable Fo

    Return value

    -

    DCSTask#Task: +

    Dcs.DCSTasking.Task#Task: The DCS task structure.

    @@ -2835,7 +2555,7 @@ The DCS task structure.

    Return value

    -

    DCSTask#Task: +

    Dcs.DCSTasking.Task#Task: The DCS task structure.

    @@ -2885,7 +2605,7 @@ self

    diff --git a/Moose Training/Documentation/DCSObject.html b/Moose Training/Documentation/DCSObject.html index 4f5ee1296..72238f7e0 100644 --- a/Moose Training/Documentation/DCSObject.html +++ b/Moose Training/Documentation/DCSObject.html @@ -17,13 +17,16 @@ index

    Return value

    -

    Client#CLIENT: +

    Wrapper.Client#CLIENT: The found CLIENT.

    @@ -809,7 +814,7 @@ The found CLIENT.

    Return value

    -

    Group#GROUP: +

    Wrapper.Group#GROUP: The found GROUP.

    @@ -835,7 +840,7 @@ The found GROUP.

    Return value

    -

    Static#STATIC: +

    Wrapper.Static#STATIC: The found STATIC.

    @@ -861,7 +866,7 @@ The found STATIC.

    Return value

    -

    Unit#UNIT: +

    Wrapper.Unit#UNIT: The found Unit.

    @@ -1195,6 +1200,27 @@ self

    + +DATABASE:GetGroupNameFromUnitName(UnitName) + +
    +
    + + + +

    Parameter

    +
      +
    • + +

      UnitName :

      + +
    • +
    +
    +
    +
    +
    + DATABASE:GetGroupTemplate(GroupName) @@ -1216,6 +1242,27 @@ self

    + +DATABASE:GetGroupTemplateFromUnitName(UnitName) + +
    +
    + + + +

    Parameter

    +
      +
    • + +

      UnitName :

      + +
    • +
    +
    +
    +
    +
    + DATABASE:GetStatusGroup(GroupName) @@ -1412,7 +1459,7 @@ self

    @@ -1433,7 +1480,7 @@ self

    @@ -1454,7 +1501,7 @@ self

    @@ -1475,7 +1522,7 @@ self

    diff --git a/Moose Training/Documentation/Detection.html b/Moose Training/Documentation/Detection.html index 46b899586..43c5617c1 100644 --- a/Moose Training/Documentation/Detection.html +++ b/Moose Training/Documentation/Detection.html @@ -17,13 +17,16 @@ index
    -

    Get the Zone#ZONE_UNIT of a detection area using a given numeric index.

    +

    Get the Core.Zone#ZONE_UNIT of a detection area using a given numeric index.

    Parameter

      @@ -1047,7 +1040,7 @@ DetectedSet

    Return value

    -

    Zone#ZONE_UNIT: +

    Core.Zone#ZONE_UNIT: DetectedZone

    @@ -1125,7 +1118,7 @@ trhe if there are friendlies nearby

    Return value

    -

    Unit#UNIT: +

    Wrapper.Unit#UNIT: The nearest FAC unit

    @@ -1145,26 +1138,26 @@ The nearest FAC unit

    Return value

    -

    Detection#DETECTION_AREAS: +

    Functional.Detection#DETECTION_AREAS: self

    @@ -1211,7 +1204,7 @@ The Index of the detection are to be removed.

    Return value

    -

    Set#SET_BASE:

    +

    Core.Set#SET_BASE:

    @@ -1719,7 +1712,7 @@ Count

    -

    Get the detected Set#SET_BASEs.

    +

    Get the detected Core.Set#SET_BASEs.

    Return value

    @@ -1741,7 +1734,7 @@ DetectedSets

    Return value

    -

    Group#GROUP:

    +

    Wrapper.Group#GROUP:

    @@ -1964,13 +1957,13 @@ true if already identified.

    Return value

    -

    DCSUnit#Unit: +

    Dcs.DCSWrapper.Unit#Unit: The DCS Unit.

    @@ -968,24 +961,6 @@ Minimum height found.

    - -GROUP:GetPointVec3() - -
    -
    - -

    Returns the current point (Vec3 vector) of the first DCS Unit in the DCS Group.

    - -

    Return value

    - -

    DCSTypes#Vec3: -Current Vec3 point of the first DCS Unit of the DCS Group.

    - -
    -
    -
    -
    - GROUP:GetSize() @@ -1101,7 +1076,7 @@ The number of the UNIT wrapper class to be returned.

    Return value

    -

    Unit#UNIT: +

    Wrapper.Unit#UNIT: The UNIT wrapper class.

    @@ -1137,9 +1112,27 @@ The UNITs wrappers.

    Return value

    -

    DCSTypes#Vec2: +

    Dcs.DCSTypes#Vec2: Current Vec2 point of the first DCS Unit of the DCS Group.

    + +
    +
    +
    + + +GROUP:GetVec3() + +
    +
    + +

    Returns the current Vec3 vector of the first DCS Unit in the GROUP.

    + +

    Return value

    + +

    Dcs.DCSTypes#Vec3: +Current Vec3 of the first DCS Unit of the GROUP.

    +
    @@ -1231,7 +1224,7 @@ true if the DCS Group is alive.

    @@ -1294,7 +1287,7 @@ true if DCS Group contains Helicopters.

    @@ -1321,7 +1314,7 @@ Returns true if the Group is completely within the Zone#ZONE_BASE Zone : +

    Core.Zone#ZONE_BASE Zone : The zone to test.

    @@ -1329,7 +1322,7 @@ The zone to test.

    Return value

    #boolean: -Returns true if the Group is completely within the Zone#ZONE_BASE

    +Returns true if the Group is completely within the Core.Zone#ZONE_BASE

    @@ -1366,7 +1359,7 @@ true if DCS Group contains Ships.

    -SCHEDULER:Stop() +SCHEDULER:Stop(ScheduleID)
    -

    Stops the scheduler.

    +

    Stops the schedules or a specific schedule if a valid ScheduleID is provided.

    -

    Return value

    +

    Parameter

    +
    -
    -
    -
    - - - -SCHEDULER.StopSeconds - -
    -
    - - - -
    -
    -
    -
    - - - -SCHEDULER.TimeEventFunction - -
    -
    - - - -
    -
    -
    -
    - - - -SCHEDULER.TimeEventFunctionArguments - -
    -
    - - - -
    -
    -
    -
    - - - -SCHEDULER.TimeEventObject - -
    -
    - - - -
    -
    -
    -
    - - -SCHEDULER:_Scheduler() - -
    -
    - - +

    #number ScheduleID : +(optional) The ScheduleID of the planned (repeating) schedule.

    + +
    diff --git a/Moose Training/Documentation/Scoring.html b/Moose Training/Documentation/Scoring.html index f3c992ba6..5cdd4d7a0 100644 --- a/Moose Training/Documentation/Scoring.html +++ b/Moose Training/Documentation/Scoring.html @@ -17,13 +17,16 @@ index

    Return value

    -

    #TASK_BASE: +

    #TASK: self

    @@ -1694,30 +1862,9 @@ self

    - -TASK_BASE:SetCategory(TaskCategory) - -
    -
    - -

    Sets the Category of the Task

    - -

    Parameter

    -
      -
    • - -

      #string TaskCategory :

      - -
    • -
    -
    -
    -
    -
    - - Set#SET_GROUP - -TASK_BASE.SetGroup + Core.Set#SET_GROUP + +TASK.SetGroup
    @@ -1729,8 +1876,8 @@ self

    - -TASK_BASE:SetID(TaskID) + +TASK:SetID(TaskID)
    @@ -1750,8 +1897,42 @@ self

    - -TASK_BASE:SetName(TaskName) + +TASK:SetMenu() + +
    +
    + +

    Set the menu options of the Task to all the groups in the SetGroup.

    + +
    +
    +
    +
    + + +TASK:SetMenuForGroup(TaskGroup) + +
    +
    + +

    Set the Menu for a Group

    + +

    Parameter

    +
      +
    • + +

      TaskGroup :

      + +
    • +
    +
    +
    +
    +
    + + +TASK:SetName(TaskName)
    @@ -1771,26 +1952,8 @@ self

    - -TASK_BASE:SetPlannedMenu() - -
    -
    - -

    Set the menu options of the Task to all the groups in the SetGroup.

    - -

    Return value

    - -

    #TASK_BASE: -self

    - -
    -
    -
    -
    - - -TASK_BASE:SetPlannedMenuForGroup(TaskGroup, MenuText) + +TASK:SetPlannedMenuForGroup(TaskGroup, MenuText)
    @@ -1801,7 +1964,7 @@ self

    Return value

    -

    #TASK_BASE: +

    #TASK: self

    @@ -1821,8 +1984,39 @@ self

    - -TASK_BASE:SetType(TaskType) + +TASK:SetStateMachine(TaskUnit, Fsm) + +
    +
    + +

    Add a FiniteStateMachine to Task with key TaskUnit

    + +

    Parameters

    + +

    Return value

    + +

    #TASK: +self

    + +
    +
    +
    +
    + + +TASK:SetType(TaskType)
    @@ -1842,8 +2036,35 @@ self

    - -TASK_BASE:StateAssigned() + +TASK:SetUnitProcess(Core, FsmTemplate) + +
    +
    + +

    Sets the Task FSM Process Template

    + +

    Parameters

    +
      +
    • + +

      Core : +Fsm#FSM_PROCESS

      + +
    • +
    • + +

      FsmTemplate :

      + +
    • +
    +
    +
    +
    +
    + + +TASK:StateAssigned()
    @@ -1855,8 +2076,8 @@ self

    - -TASK_BASE:StateFailed() + +TASK:StateFailed()
    @@ -1868,8 +2089,8 @@ self

    - -TASK_BASE:StateHold() + +TASK:StateHold()
    @@ -1881,8 +2102,8 @@ self

    - -TASK_BASE:StatePlanned() + +TASK:StatePlanned()
    @@ -1894,8 +2115,8 @@ self

    - -TASK_BASE:StateReplanned() + +TASK:StateReplanned()
    @@ -1907,22 +2128,38 @@ self

    - -TASK_BASE:StateSuccess() + +TASK:StateSuccess()

    Sets a Task to status Success.

    +
    +
    +
    +
    + + +TASK:Success() + +
    +
    + +

    FSM Success synchronous event function for TASK.

    + + +

    Use this event to make the Task a Success.

    +
    - -TASK_BASE.TaskBriefing + +TASK.TaskBriefing
    @@ -1935,8 +2172,8 @@ self

    - -TASK_BASE.TaskCategory + +TASK.TaskID
    @@ -1949,8 +2186,22 @@ self

    - -TASK_BASE.TaskID + +TASK.TaskName + +
    +
    + + + +
    +
    +
    +
    + + Core.Scheduler#SCHEDULER + +TASK.TaskScheduler
    @@ -1963,8 +2214,8 @@ self

    - -TASK_BASE.TaskName + +TASK.TaskType
    @@ -1976,36 +2227,8 @@ self

    - Scheduler#SCHEDULER - -TASK_BASE.TaskScheduler - -
    -
    - - - -
    -
    -
    -
    - - - -TASK_BASE.TaskType - -
    -
    - - - -
    -
    -
    -
    - - -TASK_BASE:UnAssignFromGroups() + +TASK:UnAssignFromGroups()
    @@ -2017,151 +2240,262 @@ self

    - -TASK_BASE:UnAssignFromUnit(TaskUnit, TaskUnitName) + +TASK:UnAssignFromUnit(TaskUnit)

    UnAssign the Task from an alive Unit.

    +

    Parameter

    + +

    Return value

    + +

    #TASK: +self

    + +
    +
    +
    +
    + + +TASK:__Abort() + +
    +
    + +

    FSM Abort asynchronous event function for TASK.

    + + +

    Use this event to Abort the Task.

    + +
    +
    +
    +
    + + +TASK:__Cancel() + +
    +
    + +

    FSM Cancel asynchronous event function for TASK.

    + + +

    Use this event to Cancel the Task.

    + +
    +
    +
    +
    + + +TASK:__Fail() + +
    +
    + +

    FSM Fail asynchronous event function for TASK.

    + + +

    Use this event to Fail the Task.

    + +
    +
    +
    +
    + + +TASK:__Replan() + +
    +
    + +

    FSM Replan asynchronous event function for TASK.

    + + +

    Use this event to Replan the Task.

    + +
    +
    +
    +
    + + +TASK:__Success() + +
    +
    + +

    FSM Success asynchronous event function for TASK.

    + + +

    Use this event to make the Task a Success.

    + +
    +
    +
    +
    + + +TASK:onenterAborted(Event, From, To) + +
    +
    + +

    FSM function for a TASK

    +

    Parameters

    • -

      Unit#UNIT TaskUnit :

      +

      #string Event :

    • -

      TaskUnitName :

      +

      #string From :

      + +
    • +
    • + +

      #string To :

    -

    Return value

    - -

    #TASK_BASE: -self

    -
    - -TASK_BASE:_EventAssignUnit(Event) + +TASK:onenterAssigned(Event, From, To)
    -

    Register a potential new assignment for a new spawned Unit.

    +

    FSM function for a TASK

    - -

    Tasks only get assigned if there are players in it.

    - -

    Parameter

    +

    Parameters

    • -

      Event#EVENTDATA Event :

      +

      #string Event :

      + +
    • +
    • + +

      #string From :

      + +
    • +
    • + +

      #string To :

    -

    Return value

    - -

    #TASK_BASE: -self

    -
    - -TASK_BASE:_EventDead(Event) + +TASK:onenterFailed(Event, From, To)
    -

    UnAssigns a Unit that is left by a player, crashed, dead, ....

    +

    FSM function for a TASK

    - -

    There are only assignments if there are players in it.

    - -

    Parameter

    +

    Parameters

    • -

      Event#EVENTDATA Event :

      +

      #string Event :

      + +
    • +
    • + +

      #string From :

      + +
    • +
    • + +

      #string To :

    -

    Return value

    - -

    #TASK_BASE: -self

    -
    - -TASK_BASE:_EventPlayerLeaveUnit(Event) + +TASK:onenterSuccess(Event, From, To)
    -

    Catches the "player leave unit" event for a Unit ....

    +

    FSM function for a TASK

    - -

    When a player is an air unit, and leaves the unit:

    - -
      -
    • and he is not at an airbase runway on the ground, he will fail its task.
    • -
    • and he is on an airbase and on the ground, the process for him will just continue to work, he can switch airplanes, and take-off again. - This is important to model the change from plane types for a player during mission assignment.
    • -
    - -

    Parameter

    +

    Parameters

    • -

      Event#EVENTDATA Event :

      +

      #string Event :

      + +
    • +
    • + +

      #string From :

      + +
    • +
    • + +

      #string To :

    -

    Return value

    +
    +
    +
    +
    + + +TASK:onstatechange(Event, From, To) + +
    +
    + +

    FSM function for a TASK

    + +

    Parameters

    +
    -
    -
    -
    - - -TASK_BASE:_Schedule() - -
    -
    - - - -
    -
    -
    -
    - - -TASK_BASE:_Scheduler() - -
    -
    - + +
  • + +

    #string From :

    +
  • +
  • + +

    #string To :

    +
  • +
    diff --git a/Moose Training/Documentation/Task_A2G.html b/Moose Training/Documentation/Task_A2G.html index 0e37d2258..9737bad70 100644 --- a/Moose Training/Documentation/Task_A2G.html +++ b/Moose Training/Documentation/Task_A2G.html @@ -17,13 +17,16 @@ index
    -
    -
    - #string TASK_A2G.ClassName @@ -241,24 +172,6 @@ self

    -
    -
    -
    -
    - - -TASK_A2G:CleanUp() - -
    -
    - -

    Removes a TASK_A2G.

    - -

    Return value

    - -

    #nil:

    - -
    @@ -289,7 +202,7 @@ self

    -
    -
    -
    - - -TASK_A2G:OnNext(Fsm, Event, From, To, Event) - -
    -
    - -

    StateMachine callback function for a TASK

    - -

    Parameters

    - -
    -
    -
    -
    - - - -TASK_A2G.TaskScheduler - -
    -
    - - - -
    -
    -
    -
    - - -TASK_A2G:_Schedule() - -
    -
    - - - -
    -
    -
    -
    - - -TASK_A2G:_Scheduler() - -
    -
    - - -
    diff --git a/Moose Training/Documentation/Task_Assign.html b/Moose Training/Documentation/Task_Assign.html index 8b817f643..122e20e49 100644 --- a/Moose Training/Documentation/Task_Assign.html +++ b/Moose Training/Documentation/Task_Assign.html @@ -21,7 +21,7 @@
  • Airbase
  • AirbasePolice
  • Base
  • -
  • CARGO
  • +
  • Cargo
  • CleanUp
  • Client
  • Controllable
  • @@ -38,17 +38,11 @@
  • DCScountry
  • DCStimer
  • DCStrigger
  • -
  • DEPLOYTASK
  • -
  • DESTROYBASETASK
  • -
  • DESTROYGROUPSTASK
  • -
  • DESTROYRADARSTASK
  • -
  • DESTROYUNITTYPESTASK
  • Database
  • Detection
  • DetectionManager
  • Escort
  • Event
  • -
  • GOHOMETASK
  • Group
  • Identifiable
  • MOVEMENT
  • @@ -56,18 +50,15 @@
  • Message
  • MissileTrainer
  • Mission
  • -
  • NOTASK
  • Object
  • -
  • PICKUPTASK
  • -
  • PatrolZone
  • Point
  • Positionable
  • Process
  • Process_Destroy
  • Process_JTAC
  • +
  • Process_PatrolZone
  • +
  • Process_Pickup
  • Process_Smoke
  • -
  • ROUTETASK
  • -
  • STAGE
  • Scheduler
  • Scoring
  • Sead
  • @@ -80,13 +71,13 @@
  • Task_A2G
  • Task_Assign
  • Task_Client_Menu
  • +
  • Task_PICKUP
  • Task_Route
  • Task_SEAD
  • Unit
  • Zone
  • env
  • land
  • -
  • routines
  • diff --git a/Moose Training/Documentation/Task_Client_Menu.html b/Moose Training/Documentation/Task_Client_Menu.html index fc35db8ed..5fd5fc3d4 100644 --- a/Moose Training/Documentation/Task_Client_Menu.html +++ b/Moose Training/Documentation/Task_Client_Menu.html @@ -17,13 +17,16 @@ index
    -

    Module Task_CAS

    +

    Module Task_PICKUP

    -

    This module contains the TASK_A2G classes.

    +

    This module contains the TASK_PICKUP classes.

    -

    1) #TASK_A2G class, extends Task#TASK_BASE

    -

    The #TASK_A2G class defines a new CAS task of a Set of Target Units, located at a Target Zone, based on the tasking capabilities defined in Task#TASK_BASE. -The TASK_A2G is processed through a Statemachine#STATEMACHINE_TASK, and has the following statuses:

    +

    1) #TASK_PICKUP class, extends Tasking.Task#TASK

    +

    The #TASK_PICKUP class defines a pickup task of a Set of CARGO objects defined within the mission. +based on the tasking capabilities defined in Tasking.Task#TASK. +The TASK_PICKUP is implemented using a Statemachine#FSM_TASK, and has the following statuses:

    • None: Start of the process
    • -
    • Planned: The SEAD task is planned. Upon Planned, the sub-process ProcessAssign#PROCESSASSIGN_ACCEPT is started to accept the task.
    • -
    • Assigned: The SEAD task is assigned to a Group#GROUP. Upon Assigned, the sub-process ProcessRoute#PROCESSROUTE is started to route the active Units in the Group to the attack zone.
    • +
    • Planned: The SEAD task is planned. Upon Planned, the sub-process ProcessFsm.Assign#ACTASSIGN_ACCEPT is started to accept the task.
    • +
    • Assigned: The SEAD task is assigned to a Wrapper.Group#GROUP. Upon Assigned, the sub-process ProcessFsm.Route#ACTROUTE is started to route the active Units in the Group to the attack zone.
    • Success: The SEAD task is successfully completed. Upon Success, the sub-process ProcessSEAD#PROCESSSEAD is started to follow-up successful SEADing of the targets assigned in the task.
    • Failed: The SEAD task has failed. This will happen if the player exists the task early, without communicating a possible cancellation to HQ.
    @@ -116,64 +110,64 @@ The TASK_A2G is processed through a - TASK_A2G + TASK_PICKUP -

    Type TASK_A2G

    +

    Type TASK_PICKUP

    - + - + - + - + - + - + - + - + - + @@ -184,9 +178,9 @@ The TASK_A2G is processed through a
    - #TASK_A2G - -TASK_A2G + #TASK_PICKUP + +TASK_PICKUP
    @@ -195,18 +189,18 @@ The TASK_A2G is processed through a Type Task_CAS +

    Type Task_PICKUP

    -

    Type TASK_A2G

    +

    Type TASK_PICKUP

    -

    The TASK_A2G class

    +

    The TASK_PICKUP class

    Field(s)

    - -TASK_A2G:AssignToUnit(TaskUnit) + +TASK_PICKUP:AssignToUnit(TaskUnit)
    @@ -217,13 +211,13 @@ The TASK_A2G is processed through a Unit#UNIT TaskUnit :

    +

    Wrapper.Unit#UNIT TaskUnit :

    Return value

    -

    #TASK_A2G: +

    #TASK_PICKUP: self

    @@ -232,8 +226,8 @@ self

    #string - -TASK_A2G.ClassName + +TASK_PICKUP.ClassName
    @@ -245,13 +239,13 @@ self

    - -TASK_A2G:CleanUp() + +TASK_PICKUP:CleanUp()
    -

    Removes a TASK_A2G.

    +

    Removes a TASK_PICKUP.

    Return value

    @@ -263,8 +257,8 @@ self

    - -TASK_A2G:GetPlannedMenuText() + +TASK_PICKUP:GetPlannedMenuText()
    @@ -276,24 +270,24 @@ self

    - -TASK_A2G:New(Mission, SetGroup, TaskName, TaskType, UnitSetTargets, TargetZone, TargetSetUnit, FACUnit) + +TASK_PICKUP:New(Mission, AssignedSetGroup, TaskName, TaskType, UnitSetTargets, TargetZone)
    -

    Instantiates a new TASK_A2G.

    +

    Instantiates a new TASK_PICKUP.

    Parameters

    Return value

    -

    #TASK_A2G: +

    #TASK_PICKUP: self

    @@ -340,8 +324,8 @@ self

    - -TASK_A2G:OnNext(Fsm, Event, From, To, Event) + +TASK_PICKUP:OnNext(Fsm, Event, From, To, Event)
    @@ -352,7 +336,7 @@ self

    diff --git a/Moose Training/Documentation/Task_SEAD.html b/Moose Training/Documentation/Task_SEAD.html index a68747971..a645f9be7 100644 --- a/Moose Training/Documentation/Task_SEAD.html +++ b/Moose Training/Documentation/Task_SEAD.html @@ -17,13 +17,16 @@ index
    TASK_A2G:AssignToUnit(TaskUnit)TASK_PICKUP:AssignToUnit(TaskUnit)

    Assign the Task to a Unit.

    TASK_A2G.ClassNameTASK_PICKUP.ClassName
    TASK_A2G:CleanUp()TASK_PICKUP:CleanUp() -

    Removes a TASK_A2G.

    +

    Removes a TASK_PICKUP.

    TASK_A2G:GetPlannedMenuText()TASK_PICKUP:GetPlannedMenuText()
    TASK_A2G:New(Mission, SetGroup, TaskName, TaskType, UnitSetTargets, TargetZone, TargetSetUnit, FACUnit)TASK_PICKUP:New(Mission, AssignedSetGroup, TaskName, TaskType, UnitSetTargets, TargetZone) -

    Instantiates a new TASK_A2G.

    +

    Instantiates a new TASK_PICKUP.

    TASK_A2G:OnNext(Fsm, Event, From, To, Event)TASK_PICKUP:OnNext(Fsm, Event, From, To, Event)

    StateMachine callback function for a TASK

    TASK_A2G.TaskSchedulerTASK_PICKUP.TaskScheduler
    TASK_A2G:_Schedule()TASK_PICKUP:_Schedule()
    TASK_A2G:_Scheduler()TASK_PICKUP:_Scheduler()
    - - - - - - - - @@ -153,36 +134,12 @@ The TASK_SEAD is implemented using a TASK_SEAD:New(Mission, SetGroup, TaskName, UnitSetTargets, TargetZone, TargetSetUnit) - - - - - - - - - - - - - - - -
    TASK_SEAD:AssignToUnit(TaskUnit) -

    Assign the Task to a Unit.

    -
    TASK_SEAD.ClassName -
    TASK_SEAD:CleanUp() -

    Removes a TASK_SEAD.

    Instantiates a new TASK_SEAD.

    -
    TASK_SEAD:OnNext(Fsm, Event, From, To, Event) -

    StateMachine callback function for a TASK

    TASK_SEAD.TargetSetUnit -
    TASK_SEAD.TaskScheduler - -
    TASK_SEAD:_Schedule() - -
    TASK_SEAD:_Scheduler() -
    @@ -212,32 +169,6 @@ The TASK_SEAD is implemented using a
    - -TASK_SEAD:AssignToUnit(TaskUnit) - -
    -
    - -

    Assign the Task to a Unit.

    - -

    Parameter

    - -

    Return value

    - -

    #TASK_SEAD: -self

    - -
    -
    -
    -
    - #string TASK_SEAD.ClassName @@ -247,24 +178,6 @@ self

    -
    -
    -
    -
    - - -TASK_SEAD:CleanUp() - -
    -
    - -

    Removes a TASK_SEAD.

    - -

    Return value

    - -

    #nil:

    - -
    @@ -295,7 +208,7 @@ self

    -
    -
    -
    - - -TASK_SEAD:OnNext(Fsm, Event, From, To, Event) - -
    -
    - -

    StateMachine callback function for a TASK

    - -

    Parameters

    -
    @@ -386,46 +258,6 @@ self

    -
    -
    -
    -
    - - - -TASK_SEAD.TaskScheduler - -
    -
    - - - -
    -
    -
    -
    - - -TASK_SEAD:_Schedule() - -
    -
    - - - -
    -
    -
    -
    - - -TASK_SEAD:_Scheduler() - -
    -
    - - -
    diff --git a/Moose Training/Documentation/TrackHits.html b/Moose Training/Documentation/TrackHits.html new file mode 100644 index 000000000..806370aa5 --- /dev/null +++ b/Moose Training/Documentation/TrackHits.html @@ -0,0 +1,776 @@ + + + + + + +
    +
    + +
    +
    +
    +
    + +
    +

    Module TrackHits

    + +

    (SP) (MP) (FSM) Detect, count and report successful hits to DCS objects.

    + + + +
    + +

    #ACCOUNT FSM class, extends Process#PROCESS

    +

    The #ACCOUNT_DEADS class detects, counts and reports successful hits to DCS objects. +The process is given a Set of units that will be tracked upon successful destruction. +The process will end after each target has been successfully destroyed. +Each successful destruction will trigger a status change.

    + +

    1.1) ACCOUNT_DEADS constructor:

    + + + +

    1.2) ACCOUNT state machine:

    +

    The ACCOUNT is a state machine: it manages the different events and states of the Controllable it is controlling.

    + +

    1.2.1) ACCOUNT Events:

    + +

    Event methods are available (dynamically allocated by the state machine), that accomodate for state transitions to occur in the process. +There are two types of event methods:

    + +
      +
    • Immediate: The event method has exactly the name of the event.
    • +
    • Delayed: The event method starts with a __ + the name of the event. The first parameter of the event method is a number value, expressing the delay in seconds when the event will be executed.
    • +
    + +

    These are the events defined in this class:

    + +
      +
    • Start: The process is started. The process will go into the Waiting state.
    • +
    • Event: A relevant event has occured that needs to be accounted for. The process will go into the Account state.
    • +
    • More: There are more DCS events that need to be accounted for. The process will go back into the Waiting state.
    • +
    • NoMore: There are no more DCS events that need to be accounted for. The process will go into the Success state.
    • +
    + +

    1.2.2) ACCOUNT States:

    + +
      +
    • Assigned: The player is assigned to the task. This is the initialization state for the process.
    • +
    • Waiting: The process is awaiting an DCS event to occur within the simulator. This event is called automatically every 1 second.
    • +
    • Account: The relevant DCS event has occurred, and is accounted for.
    • +
    • Success (*): All DCS events were accounted for.
    • +
    • Failed (*): The process has failed.
    • +
    + +

    (*) End states of the process.

    + +

    1.2.3) ACCOUNT_DEADS state transition functions:

    + +

    State transition functions can be set by the mission designer customizing or improving the behaviour of the state. +There are 2 moments when state transition functions will be called by the state machine:

    + +
      +
    • Before the state transition. + The state transition function needs to start with the name OnBefore + the name of the state. + If the state transition function returns false, then the processing of the state transition will not be done! + If you want to change the behaviour of the AIControllable at this event, return false, + but then you'll need to specify your own logic using the AIControllable!

    • +
    • After the state transition. + The state transition function needs to start with the name OnAfter + the name of the state. + These state transition functions need to provide a return value, which is specified at the function description.

    • +
    + +

    1.3) Manage the ACCOUNT_DEADS parameters:

    +

    The following methods are available to modify the parameters of a ACCOUNT_DEADS object:

    + + + + +

    Global(s)

    + + + + + +
    ACCOUNT_DEADS + +
    +

    Type ACCOUNT_DEADS

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    ACCOUNT_DEADS.ClassName + +
    ACCOUNT_DEADS.DisplayCategory + +
    ACCOUNT_DEADS.DisplayCount + +
    ACCOUNT_DEADS.DisplayInterval + +
    ACCOUNT_DEADS.DisplayMessage + +
    ACCOUNT_DEADS.DisplayTime + +
    ACCOUNT_DEADS:EventDead(Event) + +
    ACCOUNT_DEADS.Fsm + +
    ACCOUNT_DEADS:New(ProcessUnit, TargetSetUnit, TaskName) +

    Creates a new DESTROY process.

    +
    ACCOUNT_DEADS:OnDestroyed(ProcessUnit, Event, From, To) +

    StateMachine callback function

    +
    ACCOUNT_DEADS:OnHitTarget(ProcessUnit, Event, From, To) +

    StateMachine callback function

    +
    ACCOUNT_DEADS:OnKilled(ProcessUnit, Event, From, To) +

    StateMachine callback function

    +
    ACCOUNT_DEADS:OnMoreTargets(ProcessUnit, Event, From, To) +

    StateMachine callback function

    +
    ACCOUNT_DEADS:OnRestart(ProcessUnit, Event, From, To) +

    StateMachine callback function

    +
    ACCOUNT_DEADS:OnStart(ProcessUnit, Event, From, To) +

    StateMachine callback function

    +
    ACCOUNT_DEADS:OnWaiting(ProcessUnit, Event, From, To) +

    StateMachine callback function

    +
    ACCOUNT_DEADS.TargetSetUnit + +
    ACCOUNT_DEADS.TaskName + +
    + +

    Type ASSIGN_MENU_ACCEPT

    + + + + + +
    ASSIGN_MENU_ACCEPT.DisplayCount + +
    + +

    Global(s)

    +
    +
    + + #ACCOUNT_DEADS + +ACCOUNT_DEADS + +
    +
    + + + +
    +
    +

    Type TrackHits

    + +

    Type ACCOUNT_DEADS

    + +

    ACCOUNT_DEADS class

    + +

    Field(s)

    +
    +
    + + #string + +ACCOUNT_DEADS.ClassName + +
    +
    + + + +
    +
    +
    +
    + + #string + +ACCOUNT_DEADS.DisplayCategory + +
    +
    + + + + +

    Targets is the default display category

    + +
    +
    +
    +
    + + #number + +ACCOUNT_DEADS.DisplayCount + +
    +
    + + + +
    +
    +
    +
    + + #number + +ACCOUNT_DEADS.DisplayInterval + +
    +
    + + + +
    +
    +
    +
    + + #boolean + +ACCOUNT_DEADS.DisplayMessage + +
    +
    + + + +
    +
    +
    +
    + + #number + +ACCOUNT_DEADS.DisplayTime + +
    +
    + + + + +

    10 seconds is the default

    + +
    +
    +
    +
    + + +ACCOUNT_DEADS:EventDead(Event) + +
    +
    + + + +

    Parameter

    + +
    +
    +
    +
    + + + +ACCOUNT_DEADS.Fsm + +
    +
    + + + +
    +
    +
    +
    + + +ACCOUNT_DEADS:New(ProcessUnit, TargetSetUnit, TaskName) + +
    +
    + +

    Creates a new DESTROY process.

    + +

    Parameters

    + +

    Return value

    + +

    #ACCOUNT_DEADS: +self

    + +
    +
    +
    +
    + + +ACCOUNT_DEADS:OnDestroyed(ProcessUnit, Event, From, To) + +
    +
    + +

    StateMachine callback function

    + +

    Parameters

    + +
    +
    +
    +
    + + +ACCOUNT_DEADS:OnHitTarget(ProcessUnit, Event, From, To) + +
    +
    + +

    StateMachine callback function

    + +

    Parameters

    + +
    +
    +
    +
    + + +ACCOUNT_DEADS:OnKilled(ProcessUnit, Event, From, To) + +
    +
    + +

    StateMachine callback function

    + +

    Parameters

    + +
    +
    +
    +
    + + +ACCOUNT_DEADS:OnMoreTargets(ProcessUnit, Event, From, To) + +
    +
    + +

    StateMachine callback function

    + +

    Parameters

    + +
    +
    +
    +
    + + +ACCOUNT_DEADS:OnRestart(ProcessUnit, Event, From, To) + +
    +
    + +

    StateMachine callback function

    + +

    Parameters

    + +
    +
    +
    +
    + + +ACCOUNT_DEADS:OnStart(ProcessUnit, Event, From, To) + +
    +
    + +

    StateMachine callback function

    + +

    Parameters

    + +
    +
    +
    +
    + + +ACCOUNT_DEADS:OnWaiting(ProcessUnit, Event, From, To) + +
    +
    + +

    StateMachine callback function

    + +

    Parameters

    + +
    +
    +
    +
    + + Set#SET_UNIT + +ACCOUNT_DEADS.TargetSetUnit + +
    +
    + + + +
    +
    +
    +
    + + + +ACCOUNT_DEADS.TaskName + +
    +
    + + + +
    +
    + +

    Type ASSIGN_MENU_ACCEPT

    +

    Field(s)

    +
    +
    + + #number + +ASSIGN_MENU_ACCEPT.DisplayCount + +
    +
    + + + +
    +
    + +
    + +
    + + diff --git a/Moose Training/Documentation/Unit.html b/Moose Training/Documentation/Unit.html index 6d20bdf4e..4f609ac2b 100644 --- a/Moose Training/Documentation/Unit.html +++ b/Moose Training/Documentation/Unit.html @@ -17,13 +17,16 @@ index
    +
    +
    +
    + + #number + +i + +
    +
    + + + + +

    Remove obscolete units from the group structure

    +

    Type Unit

    @@ -546,24 +494,6 @@ If you want to obtain the complete 3D position including ori� -
    -
    -
    -
    - - -UNIT:Destroy() - -
    -
    - -

    Destroys the Unit.

    - -

    Return value

    - -

    #nil: -The DCS Unit is not existing or alive.

    -
    @@ -581,14 +511,14 @@ The DCS Unit is not existing or alive.

    Return value

    -

    Unit#UNIT: +

    #UNIT: self

    @@ -615,7 +545,7 @@ The Unit Name.

    Return value

    -

    Unit#UNIT: +

    #UNIT: self

    @@ -635,24 +565,10 @@ self

    - -
    -
    -
    - - #UNIT.FlareColor - -UNIT.FlareColor - -
    -
    - - -
    @@ -722,7 +638,7 @@ self

    1. -

      DCSUnit#Unit.Ammo:

      +

      Dcs.DCSWrapper.Unit#Unit.Ammo:

    2. @@ -776,7 +692,7 @@ The DCS Unit is not existing or alive.

      Return value

      -

      DCSUnit#Unit:

      +

      Dcs.DCSWrapper.Unit#Unit:

      @@ -827,7 +743,7 @@ The DCS Unit is not existing or alive.

      1. -

        Group#GROUP: +

        Wrapper.Group#GROUP: The Group of the Unit.

      2. @@ -1023,7 +939,7 @@ The DCS Unit is not existing or alive.

      3. -

        DCSObject#Object: +

        Dcs.DCSWrapper.Object#Object: The object of the radar's interest. Not nil only if at least one radar of the unit is tracking a target.

      4. @@ -1051,7 +967,7 @@ The DCS Unit is not existing or alive.

        1. -

          DCSUnit#Unit.Sensors:

          +

          Dcs.DCSWrapper.Unit#Unit.Sensors:

        2. @@ -1269,7 +1185,7 @@ Ground category evaluation result.

    @@ -1296,7 +1212,7 @@ Returns true if the unit is within the Zone#ZON
      -
    • AIBalancer
    • +
    • AI_Balancer
    • +
    • Account
    • Airbase
    • AirbasePolice
    • +
    • Assign
    • Base
    • -
    • CARGO
    • +
    • Cargo
    • CleanUp
    • Client
    • +
    • CommandCenter
    • Controllable
    • DCSAirbase
    • DCSCoalitionObject
    • @@ -38,17 +41,11 @@
    • DCScountry
    • DCStimer
    • DCStrigger
    • -
    • DEPLOYTASK
    • -
    • DESTROYBASETASK
    • -
    • DESTROYGROUPSTASK
    • -
    • DESTROYRADARSTASK
    • -
    • DESTROYUNITTYPESTASK
    • Database
    • Detection
    • -
    • DetectionManager
    • Escort
    • Event
    • -
    • GOHOMETASK
    • +
    • Fsm
    • Group
    • Identifiable
    • MOVEMENT
    • @@ -56,33 +53,29 @@
    • Message
    • MissileTrainer
    • Mission
    • -
    • NOTASK
    • Object
    • -
    • PICKUPTASK
    • -
    • PatrolZone
    • +
    • Patrol
    • Point
    • Positionable
    • -
    • Process
    • -
    • Process_Destroy
    • Process_JTAC
    • -
    • Process_Smoke
    • -
    • ROUTETASK
    • -
    • STAGE
    • +
    • Process_Pickup
    • +
    • Route
    • +
    • ScheduleDispatcher
    • Scheduler
    • Scoring
    • Sead
    • Set
    • +
    • Smoke
    • Spawn
    • -
    • StateMachine
    • Static
    • StaticObject
    • Task
    • Task_A2G
    • -
    • Task_Assign
    • Task_Client_Menu
    • -
    • Task_Route
    • +
    • Task_PICKUP
    • Task_SEAD
    • Unit
    • +
    • Utils
    • Zone
    • env
    • land
    • @@ -92,7 +85,7 @@

      Module Zone

      -

      This module contains the ZONE classes, inherited from Zone#ZONE_BASE.

      +

      This module contains the ZONE classes, inherited from Core.Zone#ZONE_BASE.

      There are essentially two core functions that zones accomodate:

      @@ -106,7 +99,7 @@
      • Test if completely within the zone.
      • -
      • Test if partly within the zone (for Group#GROUP objects).
      • +
      • Test if partly within the zone (for Wrapper.Group#GROUP objects).
      • Test if not in the zone.
      • Distance to the nearest intersecting point of the zone.
      • Distance to the center of the zone.
      • @@ -116,50 +109,138 @@

        Each of these ZONE classes have a zone name, and specific parameters defining the zone type:

        -

        Each zone implements two polymorphic functions defined in Zone#ZONE_BASE:

        +
        + +

        1) Core.Zone#ZONE_BASE class, extends Core.Base#BASE

        +

        This class is an abstract BASE class for derived classes, and is not meant to be instantiated.

        + +

        1.1) Each zone has a name:

        + +

        1.2) Each zone implements two polymorphic functions defined in Core.Zone#ZONE_BASE:

        + + + +

        1.3) A zone has a probability factor that can be set to randomize a selection between zones:

        + +
          +
        • ZONE_BASE.SetRandomizeProbability(): Set the randomization probability of a zone to be selected, taking a value between 0 and 1 ( 0 = 0%, 1 = 100% )
        • +
        • ZONE_BASE.GetRandomizeProbability(): Get the randomization probability of a zone to be selected, passing a value between 0 and 1 ( 0 = 0%, 1 = 100% )
        • +
        • ZONE_BASE.GetZoneMaybe(): Get the zone taking into account the randomization probability. nil is returned if this zone is not a candidate.
        • +
        + +

        1.4) A zone manages Vectors:

        + + + +

        1.5) A zone has a bounding square:

        + + + +

        1.6) A zone can be marked:

        + +
        -

        1) Zone#ZONE_BASE class, extends Base#BASE

        -

        The ZONE_BASE class defining the base for all other zone classes.

        +

        2) Core.Zone#ZONE_RADIUS class, extends Core.Zone#ZONE_BASE

        +

        The ZONERADIUS class defined by a zone name, a location and a radius. +This class implements the inherited functions from Core.Zone#ZONEBASE taking into account the own zone format and properties.

        + +

        2.1) Core.Zone#ZONE_RADIUS constructor:

        + + + +

        2.2) Manage the radius of the zone:

        + + + +

        2.3) Manage the location of the zone:

        + +
        -

        2) Zone#ZONE_RADIUS class, extends Zone#ZONE_BASE

        -

        The ZONE_RADIUS class defined by a zone name, a location and a radius.

        +

        3) Core.Zone#ZONE class, extends Core.Zone#ZONE_RADIUS

        +

        The ZONE class, defined by the zone name as defined within the Mission Editor. +This class implements the inherited functions from {Core.Zone#ZONE_RADIUS} taking into account the own zone format and properties.


        -

        3) Zone#ZONE class, extends Zone#ZONE_RADIUS

        -

        The ZONE class, defined by the zone name as defined within the Mission Editor.

        +

        4) Core.Zone#ZONE_UNIT class, extends Core.Zone#ZONE_RADIUS

        +

        The ZONE_UNIT class defined by a zone around a Wrapper.Unit#UNIT with a radius. +This class implements the inherited functions from Core.Zone#ZONE_RADIUS taking into account the own zone format and properties.


        -

        4) Zone#ZONE_UNIT class, extends Zone#ZONE_RADIUS

        -

        The ZONE_UNIT class defined by a zone around a Unit#UNIT with a radius.

        +

        5) Core.Zone#ZONE_GROUP class, extends Core.Zone#ZONE_RADIUS

        +

        The ZONE_GROUP class defines by a zone around a Wrapper.Group#GROUP with a radius. The current leader of the group defines the center of the zone. +This class implements the inherited functions from Core.Zone#ZONE_RADIUS taking into account the own zone format and properties.


        -

        5) Zone#ZONE_GROUP class, extends Zone#ZONE_RADIUS

        -

        The ZONE_GROUP class defines by a zone around a Group#GROUP with a radius. The current leader of the group defines the center of the zone.

        +

        6) Core.Zone#ZONEPOLYGONBASE class, extends Core.Zone#ZONE_BASE

        +

        The ZONEPOLYGONBASE class defined by a sequence of Wrapper.Group#GROUP waypoints within the Mission Editor, forming a polygon. +This class implements the inherited functions from Core.Zone#ZONE_RADIUS taking into account the own zone format and properties. +This class is an abstract BASE class for derived classes, and is not meant to be instantiated.


        -

        6) Zone#ZONE_POLYGON class, extends Zone#ZONE_BASE

        -

        The ZONE_POLYGON class defined by a sequence of Group#GROUP waypoints within the Mission Editor, forming a polygon.

        +

        7) Core.Zone#ZONE_POLYGON class, extends Core.Zone#ZONEPOLYGONBASE

        +

        The ZONE_POLYGON class defined by a sequence of Wrapper.Group#GROUP waypoints within the Mission Editor, forming a polygon. +This class implements the inherited functions from Core.Zone#ZONE_RADIUS taking into account the own zone format and properties.

        + +
        + +

        API CHANGE HISTORY

        + +

        The underlying change log documents the API changes. Please read this carefully. The following notation is used:

        + +
          +
        • Added parts are expressed in bold type face.
        • +
        • Removed parts are expressed in italic type face.
        • +
        + +

        Hereby the change log:

        + +

        2016-08-15: ZONE_BASE:GetName() added.

        + +

        2016-08-15: ZONE_BASE:SetZoneProbability( ZoneProbability ) added.

        + +

        2016-08-15: ZONE_BASE:GetZoneProbability() added.

        + +

        2016-08-15: ZONE_BASE:GetZoneMaybe() added.


        @@ -237,18 +318,36 @@ ZONE_BASE:GetBoundingSquare()

        Get the bounding square the zone.

        + + + + ZONE_BASE:GetName() + +

        Returns the name of the zone.

        ZONE_BASE:GetRandomVec2() -

        Define a random DCSTypes#Vec2 within the zone.

        +

        Define a random Dcs.DCSTypes#Vec2 within the zone.

        ZONE_BASE:GetVec2() -

        Returns the Vec2 coordinate of the zone.

        +

        Returns the Dcs.DCSTypes#Vec2 coordinate of the zone.

        + + + + ZONE_BASE:GetZoneMaybe() + +

        Get the zone taking into account the randomization probability of a zone to be selected.

        + + + + ZONE_BASE:GetZoneProbability() + +

        Get the randomization probability of a zone to be selected.

        @@ -258,7 +357,7 @@ - ZONE_BASE:IsPointVec3InZone(PointVec3) + ZONE_BASE:IsPointVec3InZone(Vec3)

        Returns if a point is within the zone.

        @@ -267,6 +366,12 @@ ZONE_BASE:New(ZoneName)

        ZONE_BASE constructor

        + + + + ZONE_BASE:SetZoneProbability(ZoneProbability) + +

        Set the randomization probability of a zone to be selected.

        @@ -279,6 +384,12 @@ ZONE_BASE.ZoneName

        Name of the zone.

        + + + + ZONE_BASE.ZoneProbability + +

        A value between 0 and 1. 0 = 0% and 1 = 100% probability.

        @@ -334,7 +445,7 @@ ZONE_GROUP:New(ZoneName, ZoneGROUP, Radius) -

        Constructor to create a ZONE_GROUP instance, taking the zone name, a zone Group#GROUP and a radius.

        +

        Constructor to create a ZONE_GROUP instance, taking the zone name, a zone Wrapper.Group#GROUP and a radius.

        @@ -356,7 +467,7 @@ ZONE_POLYGON:New(ZoneName, ZoneGroup) -

        Constructor to create a ZONE_POLYGON instance, taking the zone name and the name of the Group#GROUP defined within the Mission Editor.

        +

        Constructor to create a ZONE_POLYGON instance, taking the zone name and the name of the Wrapper.Group#GROUP defined within the Mission Editor.

        @@ -384,7 +495,7 @@ ZONE_POLYGON_BASE:GetRandomVec2() -

        Define a random DCSTypes#Vec2 within the zone.

        +

        Define a random Dcs.DCSTypes#Vec2 within the zone.

        @@ -396,13 +507,13 @@ ZONE_POLYGON_BASE:New(ZoneName, PointsArray) -

        Constructor to create a ZONEPOLYGONBASE instance, taking the zone name and an array of DCSTypes#Vec2, forming a polygon.

        +

        Constructor to create a ZONEPOLYGONBASE instance, taking the zone name and an array of Dcs.DCSTypes#Vec2, forming a polygon.

        ZONE_POLYGON_BASE.Polygon -

        The polygon defined by an array of DCSTypes#Vec2.

        +

        The polygon defined by an array of Dcs.DCSTypes#Vec2.

        @@ -425,12 +536,6 @@ ZONE_RADIUS:FlareZone(FlareColor, Points, Azimuth)

        Flares the zone boundaries in a color.

        - - - - ZONE_RADIUS:GetPointVec3(Height) - -

        Returns the point of the zone.

        @@ -448,7 +553,13 @@ ZONE_RADIUS:GetVec2() -

        Returns the location of the zone.

        +

        Returns the Dcs.DCSTypes#Vec2 of the zone.

        + + + + ZONE_RADIUS:GetVec3(Height) + +

        Returns the Dcs.DCSTypes#Vec3 of the ZONE_RADIUS.

        @@ -458,7 +569,7 @@ - ZONE_RADIUS:IsPointVec3InZone(PointVec3, Vec3) + ZONE_RADIUS:IsPointVec3InZone(Vec3)

        Returns if a point is within the zone.

        @@ -466,25 +577,25 @@ ZONE_RADIUS:New(ZoneName, Vec2, Radius) -

        Constructor of ZONE_RADIUS, taking the zone name, the zone location and a radius.

        +

        Constructor of #ZONE_RADIUS, taking the zone name, the zone location and a radius.

        ZONE_RADIUS.Radius

        The radius of the zone.

        - - - - ZONE_RADIUS:SetPointVec2(Vec2) - -

        Sets the location of the zone.

        ZONE_RADIUS:SetRadius(Radius)

        Sets the radius of the zone.

        + + + + ZONE_RADIUS:SetVec2(Vec2) + +

        Sets the Dcs.DCSTypes#Vec2 of the zone.

        @@ -507,12 +618,6 @@ ZONE_UNIT.ClassName - - - - ZONE_UNIT:GetPointVec3(Height) - -

        Returns the point of the zone.

        @@ -524,7 +629,13 @@ ZONE_UNIT:GetVec2() -

        Returns the current location of the Unit#UNIT.

        +

        Returns the current location of the Wrapper.Unit#UNIT.

        + + + + ZONE_UNIT:GetVec3(Height) + +

        Returns the Dcs.DCSTypes#Vec3 of the ZONE_UNIT.

        @@ -648,10 +759,6 @@

        Type Zone

        -

        Type POINT_VEC3.FlareColor

        - -

        Type POINT_VEC3.SmokeColor

        -

        Type ZONE

        The ZONE class, defined by the zone name as defined within the Mission Editor.

        @@ -742,17 +849,35 @@ The bounding square.

        + +ZONE_BASE:GetName() + +
        +
        + +

        Returns the name of the zone.

        + +

        Return value

        + +

        #string: +The name of the zone.

        + +
        +
        +
        +
        + ZONE_BASE:GetRandomVec2()
        -

        Define a random DCSTypes#Vec2 within the zone.

        +

        Define a random Dcs.DCSTypes#Vec2 within the zone.

        Return value

        -

        #nil: +

        Dcs.DCSTypes#Vec2: The Vec2 coordinates.

        @@ -766,13 +891,59 @@ The Vec2 coordinates.

        -

        Returns the Vec2 coordinate of the zone.

        +

        Returns the Dcs.DCSTypes#Vec2 coordinate of the zone.

        Return value

        #nil:

        +
        +
        +
        +
        + + +ZONE_BASE:GetZoneMaybe() + +
        +
        + +

        Get the zone taking into account the randomization probability of a zone to be selected.

        + +

        Return values

        +
          +
        1. + +

          #ZONE_BASE: +The zone is selected taking into account the randomization probability factor.

          + +
        2. +
        3. + +

          #nil: +The zone is not selected taking into account the randomization probability factor.

          + +
        4. +
        +
        +
        +
        +
        + + +ZONE_BASE:GetZoneProbability() + +
        +
        + +

        Get the randomization probability of a zone to be selected.

        + +

        Return value

        + +

        #number: +A value between 0 and 1. 0 = 0% and 1 = 100% probability.

        +
        @@ -790,7 +961,7 @@ The Vec2 coordinates.

        +
        +
        + + #number + +ZONE_BASE.ZoneProbability + +
        +
        + +

        A value between 0 and 1. 0 = 0% and 1 = 100% probability.

        +
        @@ -901,7 +1108,7 @@ The smoke color.

        - DCSTypes#Distance + Dcs.DCSTypes#Distance ZONE_BASE.BoundingSquare.x1 @@ -915,7 +1122,7 @@ The smoke color.

        - DCSTypes#Distance + Dcs.DCSTypes#Distance ZONE_BASE.BoundingSquare.x2 @@ -929,7 +1136,7 @@ The smoke color.

        - DCSTypes#Distance + Dcs.DCSTypes#Distance ZONE_BASE.BoundingSquare.y1 @@ -943,7 +1150,7 @@ The smoke color.

        - DCSTypes#Distance + Dcs.DCSTypes#Distance ZONE_BASE.BoundingSquare.y2 @@ -987,7 +1194,7 @@ The smoke color.

        Return value

        -

        DCSTypes#Vec2: +

        Dcs.DCSTypes#Vec2: The random location of the zone based on the Group location.

        @@ -1005,7 +1212,7 @@ The random location of the zone based on the Group loca

        Return value

        -

        DCSTypes#Vec2: +

        Dcs.DCSTypes#Vec2: The location of the zone based on the Group location.

        @@ -1019,7 +1226,7 @@ The location of the zone based on the Group location.
        -

        Constructor to create a ZONE_GROUP instance, taking the zone name, a zone Group#GROUP and a radius.

        +

        Constructor to create a ZONE_GROUP instance, taking the zone name, a zone Wrapper.Group#GROUP and a radius.

        Parameters

          @@ -1031,13 +1238,13 @@ Name of the zone.

        • -

          Group#GROUP ZoneGROUP : +

          Wrapper.Group#GROUP ZoneGROUP : The Group as the center of the zone.

        • -

          DCSTypes#Distance Radius : +

          Dcs.DCSTypes#Distance Radius : The radius of the zone.

        • @@ -1052,7 +1259,7 @@ self

          - Group#GROUP + Wrapper.Group#GROUP ZONE_GROUP.ZoneGROUP @@ -1066,7 +1273,7 @@ self

          Type ZONE_POLYGON

          -

          The ZONE_POLYGON class defined by a sequence of Group#GROUP waypoints within the Mission Editor, forming a polygon.

          +

          The ZONE_POLYGON class defined by a sequence of Wrapper.Group#GROUP waypoints within the Mission Editor, forming a polygon.

          Field(s)

          @@ -1092,10 +1299,10 @@ self

          -

          Constructor to create a ZONE_POLYGON instance, taking the zone name and the name of the Group#GROUP defined within the Mission Editor.

          +

          Constructor to create a ZONE_POLYGON instance, taking the zone name and the name of the Wrapper.Group#GROUP defined within the Mission Editor.

          -

          The Group#GROUP waypoints define the polygon corners. The first and the last point are automatically connected by ZONE_POLYGON.

          +

          The Wrapper.Group#GROUP waypoints define the polygon corners. The first and the last point are automatically connected by ZONE_POLYGON.

          Parameters

            @@ -1107,7 +1314,7 @@ Name of the zone.

          • -

            Group#GROUP ZoneGroup : +

            Wrapper.Group#GROUP ZoneGroup : The GROUP waypoints as defined within the Mission Editor define the polygon shape.

          • @@ -1122,7 +1329,7 @@ self

            Type ZONE_POLYGON_BASE

            -

            The ZONEPOLYGONBASE class defined by an array of DCSTypes#Vec2, forming a polygon.

            +

            The ZONEPOLYGONBASE class defined by an array of Dcs.DCSTypes#Vec2, forming a polygon.

            Field(s)

            @@ -1184,11 +1391,11 @@ The bounding square.

        -

        Define a random DCSTypes#Vec2 within the zone.

        +

        Define a random Dcs.DCSTypes#Vec2 within the zone.

        Return value

        -

        DCSTypes#Vec2: +

        Dcs.DCSTypes#Vec2: The Vec2 coordinate.

        @@ -1211,7 +1418,7 @@ The Vec2 coordinate.

        -

        Constructor to create a ZONEPOLYGONBASE instance, taking the zone name and an array of DCSTypes#Vec2, forming a polygon.

        +

        Constructor to create a ZONEPOLYGONBASE instance, taking the zone name and an array of Dcs.DCSTypes#Vec2, forming a polygon.

        -

        The Group#GROUP waypoints define the polygon corners. The first and the last point are automatically connected.

        +

        The Wrapper.Group#GROUP waypoints define the polygon corners. The first and the last point are automatically connected.

        Parameters

        @@ -1269,7 +1476,7 @@ self

        -

        The polygon defined by an array of DCSTypes#Vec2.

        +

        The polygon defined by an array of Dcs.DCSTypes#Vec2.

        @@ -1288,7 +1495,7 @@ self

        • -

          #POINT_VEC3.SmokeColor SmokeColor : +

          Utilities.Utils#SMOKECOLOR SmokeColor : The smoke color.

        • @@ -1342,7 +1549,7 @@ self

          • -

            #POINT_VEC3.FlareColor FlareColor : +

            Utilities.Utils#FLARECOLOR FlareColor : The flare color.

          • @@ -1354,7 +1561,7 @@ The flare color.

          • -

            DCSTypes#Azimuth Azimuth : +

            Dcs.DCSTypes#Azimuth Azimuth : (optional) Azimuth The azimuth of the flare.

          • @@ -1369,33 +1576,6 @@ self

            - -ZONE_RADIUS:GetPointVec3(Height) - -
            -
            - -

            Returns the point of the zone.

            - -

            Parameter

            -
              -
            • - -

              DCSTypes#Distance Height : -The height to add to the land height where the center of the zone is located.

              - -
            • -
            -

            Return value

            - -

            DCSTypes#Vec3: -The point of the zone.

            - -
            -
            -
            -
            - ZONE_RADIUS:GetRadius() @@ -1406,7 +1586,7 @@ The point of the zone.

            Return value

            -

            DCSTypes#Distance: +

            Dcs.DCSTypes#Distance: The radius of the zone.

            @@ -1424,7 +1604,7 @@ The radius of the zone.

            Return value

            -

            DCSTypes#Vec2: +

            Dcs.DCSTypes#Vec2: The random location within the zone.

            @@ -1438,11 +1618,11 @@ The random location within the zone.

            -

            Returns the location of the zone.

            +

            Returns the Dcs.DCSTypes#Vec2 of the zone.

            Return value

            -

            DCSTypes#Vec2: +

            Dcs.DCSTypes#Vec2: The location of the zone.

            @@ -1450,6 +1630,33 @@ The location of the zone.

            + +ZONE_RADIUS:GetVec3(Height) + +
            +
            + +

            Returns the Dcs.DCSTypes#Vec3 of the ZONE_RADIUS.

            + +

            Parameter

            +
              +
            • + +

              Dcs.DCSTypes#Distance Height : +The height to add to the land height where the center of the zone is located.

              + +
            • +
            +

            Return value

            + +

            Dcs.DCSTypes#Vec3: +The point of the zone.

            + +
            +
            +
            +
            + ZONE_RADIUS:IsPointVec2InZone(Vec2) @@ -1462,7 +1669,7 @@ The location of the zone.

            -

            Constructor of ZONE_RADIUS, taking the zone name, the zone location and a radius.

            +

            Constructor of #ZONE_RADIUS, taking the zone name, the zone location and a radius.

            Parameters

            @@ -1616,6 +1791,33 @@ The radius of the zone.

            + +ZONE_RADIUS:SetVec2(Vec2) + +
            +
            + +

            Sets the Dcs.DCSTypes#Vec2 of the zone.

            + +

            Parameter

            + +

            Return value

            + +

            Dcs.DCSTypes#Vec2: +The new location of the zone.

            + +
            +
            +
            +
            + ZONE_RADIUS:SmokeZone(SmokeColor, Points) @@ -1628,7 +1830,7 @@ The radius of the zone.

              -
            • AIBalancer
            • +
            • AI_Balancer
            • +
            • Account
            • Airbase
            • AirbasePolice
            • +
            • Assign
            • Base
            • -
            • CARGO
            • +
            • Cargo
            • CleanUp
            • Client
            • +
            • CommandCenter
            • Controllable
            • DCSAirbase
            • DCSCoalitionObject
            • @@ -38,17 +41,11 @@
            • DCScountry
            • DCStimer
            • DCStrigger
            • -
            • DEPLOYTASK
            • -
            • DESTROYBASETASK
            • -
            • DESTROYGROUPSTASK
            • -
            • DESTROYRADARSTASK
            • -
            • DESTROYUNITTYPESTASK
            • Database
            • Detection
            • -
            • DetectionManager
            • Escort
            • Event
            • -
            • GOHOMETASK
            • +
            • Fsm
            • Group
            • Identifiable
            • MOVEMENT
            • @@ -56,33 +53,29 @@
            • Message
            • MissileTrainer
            • Mission
            • -
            • NOTASK
            • Object
            • -
            • PICKUPTASK
            • -
            • PatrolZone
            • +
            • Patrol
            • Point
            • Positionable
            • -
            • Process
            • -
            • Process_Destroy
            • Process_JTAC
            • -
            • Process_Smoke
            • -
            • ROUTETASK
            • -
            • STAGE
            • +
            • Process_Pickup
            • +
            • Route
            • +
            • ScheduleDispatcher
            • Scheduler
            • Scoring
            • Sead
            • Set
            • +
            • Smoke
            • Spawn
            • -
            • StateMachine
            • Static
            • StaticObject
            • Task
            • Task_A2G
            • -
            • Task_Assign
            • Task_Client_Menu
            • -
            • Task_Route
            • +
            • Task_PICKUP
            • Task_SEAD
            • Unit
            • +
            • Utils
            • Zone
            • env
            • land
            • @@ -93,9 +86,15 @@

              Module

              - + + + + + @@ -108,6 +107,12 @@ + + + + @@ -117,9 +122,9 @@ - + @@ -132,6 +137,12 @@ + + + + @@ -216,36 +227,6 @@ - - - - - - - - - - - - - - - - - - - - @@ -258,12 +239,6 @@ - - - - @@ -275,13 +250,13 @@ - + @@ -324,12 +299,6 @@ - - - - @@ -339,15 +308,9 @@ - + - - - - @@ -360,18 +323,6 @@ - - - - - - - - @@ -381,21 +332,21 @@ - + - + - + @@ -423,15 +374,15 @@ - + - + @@ -449,19 +400,13 @@ - - - - @@ -471,9 +416,9 @@ - + @@ -486,12 +431,19 @@ + + + + diff --git a/Moose Training/Documentation/land.html b/Moose Training/Documentation/land.html index 4f5d6dc0d..951505f35 100644 --- a/Moose Training/Documentation/land.html +++ b/Moose Training/Documentation/land.html @@ -17,13 +17,16 @@ index

              Return value

              -

              DCSTypes#Distance:

              +

              Dcs.DCSTypes#Distance:

              diff --git a/Moose Training/Documentation/routines.html b/Moose Training/Documentation/routines.html index f1ccd5f79..11e68a822 100644 --- a/Moose Training/Documentation/routines.html +++ b/Moose Training/Documentation/routines.html @@ -17,13 +17,16 @@ index
                -
              • AIBalancer
              • +
              • AI_Balancer
              • +
              • Account
              • Airbase
              • AirbasePolice
              • +
              • Assign
              • Base
              • -
              • CARGO
              • +
              • Cargo
              • CleanUp
              • Client
              • +
              • CommandCenter
              • Controllable
              • DCSAirbase
              • DCSCoalitionObject
              • @@ -38,17 +41,11 @@
              • DCScountry
              • DCStimer
              • DCStrigger
              • -
              • DEPLOYTASK
              • -
              • DESTROYBASETASK
              • -
              • DESTROYGROUPSTASK
              • -
              • DESTROYRADARSTASK
              • -
              • DESTROYUNITTYPESTASK
              • Database
              • Detection
              • -
              • DetectionManager
              • Escort
              • Event
              • -
              • GOHOMETASK
              • +
              • Fsm
              • Group
              • Identifiable
              • MOVEMENT
              • @@ -56,33 +53,29 @@
              • Message
              • MissileTrainer
              • Mission
              • -
              • NOTASK
              • Object
              • -
              • PICKUPTASK
              • -
              • PatrolZone
              • +
              • Patrol
              • Point
              • Positionable
              • -
              • Process
              • -
              • Process_Destroy
              • Process_JTAC
              • -
              • Process_Smoke
              • -
              • ROUTETASK
              • -
              • STAGE
              • +
              • Process_Pickup
              • +
              • Route
              • +
              • ScheduleDispatcher
              • Scheduler
              • Scoring
              • Sead
              • Set
              • +
              • Smoke
              • Spawn
              • -
              • StateMachine
              • Static
              • StaticObject
              • Task
              • Task_A2G
              • -
              • Task_Assign
              • Task_Client_Menu
              • -
              • Task_Route
              • +
              • Task_PICKUP
              • Task_SEAD
              • Unit
              • +
              • Utils
              • Zone
              • env
              • land
              • diff --git a/Moose Training/Presentations/DCS World - MOOSE - AI Balancer - Part 1 - Explanation.pptx b/Moose Training/Presentations/AI Balancer/DCS World - MOOSE - AI Balancer - Part 1 - Explanation.pptx similarity index 100% rename from Moose Training/Presentations/DCS World - MOOSE - AI Balancer - Part 1 - Explanation.pptx rename to Moose Training/Presentations/AI Balancer/DCS World - MOOSE - AI Balancer - Part 1 - Explanation.pptx diff --git a/Moose Training/Presentations/DCS World - MOOSE - Cargo.pptx b/Moose Training/Presentations/Cargo/DCS World - MOOSE - Cargo.pptx similarity index 100% rename from Moose Training/Presentations/DCS World - MOOSE - Cargo.pptx rename to Moose Training/Presentations/Cargo/DCS World - MOOSE - Cargo.pptx diff --git a/Moose Training/Presentations/DCS World - MOOSE - Detection - Part 1 - Teaser.pptx b/Moose Training/Presentations/Detection/DCS World - MOOSE - Detection - Part 1 - Teaser.pptx similarity index 100% rename from Moose Training/Presentations/DCS World - MOOSE - Detection - Part 1 - Teaser.pptx rename to Moose Training/Presentations/Detection/DCS World - MOOSE - Detection - Part 1 - Teaser.pptx diff --git a/Moose Training/Presentations/DCS World - MOOSE - Detection - Part 2 - Demo.pptx b/Moose Training/Presentations/Detection/DCS World - MOOSE - Detection - Part 2 - Demo.pptx similarity index 100% rename from Moose Training/Presentations/DCS World - MOOSE - Detection - Part 2 - Demo.pptx rename to Moose Training/Presentations/Detection/DCS World - MOOSE - Detection - Part 2 - Demo.pptx diff --git a/Moose Training/Presentations/DCS World - MOOSE - Detection and Fac.pptx b/Moose Training/Presentations/Detection/DCS World - MOOSE - Detection and Fac.pptx similarity index 100% rename from Moose Training/Presentations/DCS World - MOOSE - Detection and Fac.pptx rename to Moose Training/Presentations/Detection/DCS World - MOOSE - Detection and Fac.pptx diff --git a/Moose Training/Presentations/DCS World - MOOSE - Escorting - Part 2 - APIs.pptx b/Moose Training/Presentations/Escorting/DCS World - MOOSE - Escorting - Part 2 - APIs.pptx similarity index 100% rename from Moose Training/Presentations/DCS World - MOOSE - Escorting - Part 2 - APIs.pptx rename to Moose Training/Presentations/Escorting/DCS World - MOOSE - Escorting - Part 2 - APIs.pptx diff --git a/Moose Training/Presentations/DCS World - MOOSE - Development - Part 1 - Tools and Installation.pptx b/Moose Training/Presentations/Installation and Usage/DCS World - MOOSE - Development - Part 1 - Tools and Installation.pptx similarity index 100% rename from Moose Training/Presentations/DCS World - MOOSE - Development - Part 1 - Tools and Installation.pptx rename to Moose Training/Presentations/Installation and Usage/DCS World - MOOSE - Development - Part 1 - Tools and Installation.pptx diff --git a/Moose Training/Presentations/DCS World - MOOSE - Development - Part 2 - Using Eclipse and MOOSE - Copy.pptx b/Moose Training/Presentations/Installation and Usage/DCS World - MOOSE - Development - Part 2 - Using Eclipse and MOOSE - Copy.pptx similarity index 100% rename from Moose Training/Presentations/DCS World - MOOSE - Development - Part 2 - Using Eclipse and MOOSE - Copy.pptx rename to Moose Training/Presentations/Installation and Usage/DCS World - MOOSE - Development - Part 2 - Using Eclipse and MOOSE - Copy.pptx diff --git a/Moose Training/Presentations/DCS World - MOOSE - Development - Part 3 - The DATABASE - UNIT - CLIENT - GROUP - ZONE, .pptx b/Moose Training/Presentations/Installation and Usage/DCS World - MOOSE - Development - Part 3 - The DATABASE - UNIT - CLIENT - GROUP - ZONE, .pptx similarity index 100% rename from Moose Training/Presentations/DCS World - MOOSE - Development - Part 3 - The DATABASE - UNIT - CLIENT - GROUP - ZONE, .pptx rename to Moose Training/Presentations/Installation and Usage/DCS World - MOOSE - Development - Part 3 - The DATABASE - UNIT - CLIENT - GROUP - ZONE, .pptx diff --git a/Moose Training/Presentations/DCS World - MOOSE - Missile Trainer.pptx b/Moose Training/Presentations/Missile Trainer/DCS World - MOOSE - Missile Trainer.pptx similarity index 100% rename from Moose Training/Presentations/DCS World - MOOSE - Missile Trainer.pptx rename to Moose Training/Presentations/Missile Trainer/DCS World - MOOSE - Missile Trainer.pptx diff --git a/Moose Training/Presentations/DCS World - MOOSE - Sets - Part 1 - SET_GROUP.pptx b/Moose Training/Presentations/Sets/DCS World - MOOSE - Sets - Part 1 - SET_GROUP.pptx similarity index 100% rename from Moose Training/Presentations/DCS World - MOOSE - Sets - Part 1 - SET_GROUP.pptx rename to Moose Training/Presentations/Sets/DCS World - MOOSE - Sets - Part 1 - SET_GROUP.pptx diff --git a/Moose Training/Presentations/DCS World - MOOSE - Spawning - Part 2 - APIs.pptx b/Moose Training/Presentations/Spawning/DCS World - MOOSE - Spawning - Part 2 - APIs.pptx similarity index 100% rename from Moose Training/Presentations/DCS World - MOOSE - Spawning - Part 2 - APIs.pptx rename to Moose Training/Presentations/Spawning/DCS World - MOOSE - Spawning - Part 2 - APIs.pptx diff --git a/Moose Training/Presentations/DCS World - MOOSE - Tasking - SEAD.pptx b/Moose Training/Presentations/Tasking/DCS World - MOOSE - Tasking - SEAD - kopie.pptx similarity index 98% rename from Moose Training/Presentations/DCS World - MOOSE - Tasking - SEAD.pptx rename to Moose Training/Presentations/Tasking/DCS World - MOOSE - Tasking - SEAD - kopie.pptx index 191650f02..fd558d66b 100644 Binary files a/Moose Training/Presentations/DCS World - MOOSE - Tasking - SEAD.pptx and b/Moose Training/Presentations/Tasking/DCS World - MOOSE - Tasking - SEAD - kopie.pptx differ diff --git a/Moose Training/Presentations/Tasking/DCS World - MOOSE - Tasking - SEAD.pptx b/Moose Training/Presentations/Tasking/DCS World - MOOSE - Tasking - SEAD.pptx new file mode 100644 index 000000000..6ca6f477a Binary files /dev/null and b/Moose Training/Presentations/Tasking/DCS World - MOOSE - Tasking - SEAD.pptx differ diff --git a/Moose Training/Presentations/Tasking/DCS World - MOOSE - Tasking.pptx b/Moose Training/Presentations/Tasking/DCS World - MOOSE - Tasking.pptx new file mode 100644 index 000000000..5187a7432 Binary files /dev/null and b/Moose Training/Presentations/Tasking/DCS World - MOOSE - Tasking.pptx differ diff --git a/Moose Training/Presentations/DCS World - MOOSE - Development - Part 4 - Wrapper Classes.pptx b/Moose Training/Presentations/Wrapper/DCS World - MOOSE - Development - Part 4 - Wrapper Classes.pptx similarity index 100% rename from Moose Training/Presentations/DCS World - MOOSE - Development - Part 4 - Wrapper Classes.pptx rename to Moose Training/Presentations/Wrapper/DCS World - MOOSE - Development - Part 4 - Wrapper Classes.pptx diff --git a/Moose Training/Presentations/DCS World - MOOSE - Zones - Part 1 - Use zones with GROUP and UNIT.pptx b/Moose Training/Presentations/Zones/DCS World - MOOSE - Zones - Part 1 - Use zones with GROUP and UNIT.pptx similarity index 100% rename from Moose Training/Presentations/DCS World - MOOSE - Zones - Part 1 - Use zones with GROUP and UNIT.pptx rename to Moose Training/Presentations/Zones/DCS World - MOOSE - Zones - Part 1 - Use zones with GROUP and UNIT.pptx diff --git a/Moose Training/Release Announce/Release Announce.bbc b/Moose Training/Release Announce/Release Announce.bbc new file mode 100644 index 000000000..4dddcf4a2 --- /dev/null +++ b/Moose Training/Release Announce/Release Announce.bbc @@ -0,0 +1,224 @@ + +Hi everyone, + +It's FlightControl here ... It is been quite a ride ... But here it is, the release of the MOOSE framework for DCS World is a fact. This release is adding the following functionality to MOOSE, on top of the pre-release version: + +[list] +[*] Task orchestration: CommandCenters, Missions, Tasks, Task Actions ... +[*] AI balancing and AI bahaviour. AIBalancer, Patrol Zones, ... +[*] Cargo handling: Board, Unboard, Transfer cargo of different types. +[*] Dynamic detection and task assignment through FACs. +[*] Additional functions in existing classes. +[/list] + +Preparing this release was a large work ... I hope you like using it. + +There are various test missions created to demonstrate the framework. It is worth to have a close look to the lua files for each test mission. You can find in each directory to lua file that is embedded in the test mission. +The documentation is not yet up-to-date, but that will improve next year. On top, I plan to create training videos for you to demonstrate the capabilities of the introduced new concepts. These things take time and a good preparation, so pls be patient. + +[size=24][b][u]API Changes![/u][/b][/size] + +The [color=blue]good[/color] and [color=red]bad [/color]news is that there are a couple of API changes that I had to push through. The reason why these API changes appear in the release is, I made some bad design decisions in the past, and I've made the interface more consistent. I would like you to review and update your missions to adapt and incorporate these API changes. Implementing these API changes can be quickly done I believe ... The changes are minor. + +In a nuttshell: +- I changed the name of the SPAWN initialization functions, adding [color=blue]Init[/color] to the name. I wanted to make sure that the difference between Initialization and Spawning in the SPAWN methods were clear, because many people had questions and were confused! Please refer to the SPA-1xx for a demonstration on these new Init functions (they are the same, only the name changed). +- Spawning of new units can be done now from a Unit location, a Static location, a PointVec2 location, a PointVec3 location and a Zone location. In order to make the interface consistent over all these functions, I added a new function called [color=blue]InitRandomizeUnits( RandomizeUnits, OuterRadius, InnerRadius )[/color] and added, revised the APIs of the Spawning functions. Please refer to the test missions SPA-3xx for demo scripts to use these functions. +- Zones can now also be randomized during Spawning using the method [color=blue]InitRandomizeZones( SpawnZones )[/color]. Please refer to the test mission SPA-220 for a demonstration to use this function. + +Here are the changes: + +[list] +[*][b]BASE[/b] + +[list] +[*]DCS Event handling functions have been added to the base class. These functions start with the [color=blue]OnEvent...[/color] prefix. These functions now allow you to DCS events in a class like when a birth of a unit happens, or when a crash happens etc. +[/list] + +[*][b]SPAWN[/b] + +[list] +[*][color=blue]OnSpawnGroup[/color]( SpawnCallBackFunction, ... ) replaces [color=red]SpawnFunction[/color]( SpawnCallBackFunction, ... ) +[/list] + +[list] +[*][b]SpawnInZone[/b]( Zone, [color=blue]RandomizeGroup[/color], SpawnIndex ) replaces SpawnInZone( Zone, [color=red]RandomizeUnits, OuterRadius, InnerRadius,[/color] SpawnIndex ) +[*][b]SpawnFromVec3[/b]( Vec3, SpawnIndex ) replaces SpawnFromVec3( Vec3, [color=red]RandomizeUnits, OuterRadius, InnerRadius,[/color] SpawnIndex ) +[*][b]SpawnFromVec2[/b]( Vec2, SpawnIndex ) replaces SpawnFromVec2( Vec2, [color=red]RandomizeUnits, OuterRadius, InnerRadius,[/color] SpawnIndex ) +[*][b]SpawnFromUnit[/b]( SpawnUnit, SpawnIndex ) replaces SpawnFromUnit( SpawnUnit, [color=red]RandomizeUnits, OuterRadius, InnerRadius,[/color] SpawnIndex ) +[*][b]SpawnFromStatic[/b]( SpawnUnit, SpawnIndex ) replaces SpawnFromStatic( SpawnStatic, [color=red]RandomizeUnits, OuterRadius, InnerRadius,[/color] SpawnIndex ) +[/list] + + +[list] +[*][color=blue][b]InitRandomizeUnits[/b]( RandomizeUnits, OuterRadius, InnerRadius )[/color] added. +[*][color=blue][b]InitLimit[/b][/color]( SpawnMaxUnitsAlive, SpawnMaxGroups ) replaces [color=red]Limit[/color]( SpawnMaxUnitsAlive, SpawnMaxGroups ) +[*][color=blue][b]InitArray[/b][/color]( SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY ) replaces [color=red]Array[/color]( SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY ) +[*][color=blue][b]InitRandomizeRoute[/b][/color]( SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight ) replaces [color=red]RandomizeRoute[/color]( SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight ) +[*][color=blue][b]InitRandomizeTemplate[/b][/color]( SpawnTemplatePrefixTable ) replaces [color=red]RandomizeTemplate[/color]( SpawnTemplatePrefixTable ) +[*][color=blue][b]InitUnControlled[/b][/color]() replaces [color=red]UnControlled[/color]() +[*][color=blue][b]InitCleanUp[/b][/color]( SpawnCleanUpInterval ) replaces [color=red]CleanUp[/color]( SpawnCleanUpInterval ) +[*][color=blue][b]InitRandomizeZones[/b]( SpawnZones )[/color] added +[/list] + + +[*][b]AIBALANCER[/b] +[list] +[*]Has been completely reworked. I don't think anybody has been using this class beside hijack. +[/list] + + +[*][b]PATROLZONE[/b]: +[list] +[*]Has been completely reworked. I don't think anybody has been using this class beside hijack. +[/list] + + +[*][b]POINT_VEC3[/b] and references in other classes methods to POINT_VEC3 objects: +[list] +[*][color=blue]Translate( Distance, Angle )[/color]added. +[*]Replaced methods ending with [color=red]Point_Vec3[/color]() to [color=blue]Vec3[/color]() where the code manages a Vec3. Replaced all references to the method. +[*]Replaced method [color=red]Point_Vec2()[/color] to [color=blue]Vec2[/color]() where the code manages a Vec2. Replaced all references to the method. +[*]Replaced method [color=red]Random_Point_Vec3()[/color] to [color=blue]RandomVec3[/color]() where the code manages a Vec3. Replaced all references to the method. +[/list] + + +[*][b]SCHEDULER[/b] has been reworked, see below. +[/list] + +[size=24][b][u]What's new![/u][/b][/size] + +Find below a comprehensive summary of the MOOSE new features in this release: + +[size=22][b][u]1. Task Orchestration[/u][/b][/size] + +[size=18][b][u]1.1. Comand Centers[/u][/b][/size] + +[b]COMMANDCENTER[/b]: Governs the communication and existence of Missions, Tasks and Actions for a Coalition. +[list=1] +[*]Create a new CommandCenter. Multiple CommandCenters can be defined within one Mission. +[*]Maintain Missions. Add, Remove and CleanUp Missions, and undelying Tasks and Actions. +[*]Provide a navigation menu to orchestrate for different groups the Tasking.[*]Send Reports to players of the Tasks within all Missions of a CommandCenter. +[*]Send Messages to all players alive within all Missions of a CommandCenter. +[/list] + +[size=18][b][u]1.2. Missions[/u][/b][/size] + +[b]MISSION[/b]: Governs the process flow of the Mission, Tasks and Actions for a CommandCenter for a Coalition.[list=1] +[*]Create a new Mission for a CommandCenter. +[*]Provide Mission status flow, implemented through a Finite State Machine. +[*]Expose Mission event- and state functions to influence the Mission orchestration. +[*]Maintain Tasks: Add, Remove and CleanUp Tasks and underlying Actions. +[*]Send Reports to players of the Tasks within the Mission. +[*]Send Messages to all players within the Mission. +[*]Attach a Scoring and event log implemented through the SCORING class. +[/list] + +[size=18][b][u]1.3. Tasks[/u][/b][/size] + +[b]TASK[/b]: Governs the process flow of the Task state and the execution of the Taskprocess and hierarchical processes by players. +[list=1] +[*]Create a new Task for a Mission governed by a CommandCenter. +[*]Provide Task status flow, implemented through a Finite State Machine. +[*]Expose Task event- and state functions to influence the Task orchestration. +[*]Create a Task Actions, implemented through a Finite State Machine Process, that the players will need to follow when the Task is Assigned to a Player (Unit). +[*]Maintain Tasks: Add, Remove and CleanUp Task Actions and hierarchical Actions. +[*]Expose Mission event- and state functions to influence the Task state. + +- Flag a Task as Planned. +- Flag a Task as Assigned. +- Flag a Task as Successful. +- Flag a Task as Failed. +- Flag a Task as Aborted. +- Flag a Task as Cancelled. +[*]Send Reports of the Task to the players. +[*]Send Messages to the player executing and assigned to the Task. +[*]Create a Task Action workbook. +[*]Provide a mechanism to assign players to the Task. +[*]Provide a mechanism to abort players from the Task. +[*]Each assigned player to the Task, will have a Task Action flow executing, governed by the TASK object. +[*]Provide a mechanism to attach a Scoring scheme when certain states are reached in the Task and in the Task Action flow. +[/list] + +[size=18][b][u]1.4. Task Actions[/u][/b][/size] + +[b]ACT_ASSIGN[/b], [b]ACT_ROUTE[/b], [b]ACT_ACCOUNT[/b], [b]ACT_SMOKE[/b]: Governs Task Action Subroutines that can be embedded within a Task Action flow. These ACT_ classes will be further enhanced and expanded now the baseline of MOOSE is there. This will result in mission designed being able to quickly combine these actions to implement different Task flows. +[list=1] +[*]Create a new Task Action Subroutine for a Task Action flow, governed by a Task. +[*]ACT_ASSIGN: Base class to assign a player to a Task. If the player is in a Group that is already assigned to the Task, the Player will be assigned automatically. +[*]ACT_ASSIGN_ACCEPT: Assign a player to a Task, and automatically Accept the Task. +[*]ACT_ASSIGN_MENU_ACCEPT: Assign a player to a Task, and let the Player Accept or Reject the Task within 30 seconds. +[*]ACT_ROUTE: Base class to route a player. +[*]ACT_ROUTE_ZONE: Route a player to a zone. +[*]ACT_ACCOUNT: Base class to "account" events or things within a running mission. +[*]ACT_ACCOUNT_DEADS: Account if certain DCS Units are dead. +[*]ACT_ASSIST: Base class to assist players with certain actions through the menu. +[*]ACT_ASSIST_SMOKE: Assist players with smoking target areas while in flight through the menu. +[*]ACT_...: Expect in the future more ACT classes to be created and added to the MOOSE framework. It is upon our creativity to identify good functions to be added. +[/list] + +[size=22][b][u]2. Finite State Machines[/u][/b][/size] + +[b]FSM[/b], [b]FSM_CONTROLLABLE[/b], [b]FSM_ACTION[/b], [b]FSM_TASK[/b], [b]FSM_SET[/b]: Finite State Machine base classes that implement the necessary functionality to realise a workflow following various state transitions triggered through events being fired externally or internally within the FSM implementation. +[list=1] +[*]FSM: The Finitite State Machine base class. It provides functions to create a fsm workflow, adding state transitions schemes and adding sub processes. +[*]FSM_CONTROLLABLE: An fsm governing the workflow for a Controllable object within DCS, which can be a UNIT or a GROUP. +[*]FSM_PROCESS: An fsm governing a workflow for a Controllable object within DCS for a Task. Note that all ACT_... classes are derived classes from FSM_PROCESS, implementing an fsm to govern the Player unit for the Task given. +[*]FSM_TASK: An fsm governing a workflow for a Task. Note that the TASK class is derived from FSM_TASK. +[*]FSM_SET: An fsm governing a workflow for a Set. +[/list] + +[size=22][b][u]3. Balance and Control AI[/u][/b][/size] + +[b]AIBALANCER[/b]: Balances AI within a Mission. It is up to the mission designer to capture the different AIBalancer events, and attach different AI processes to accomodate AI behaviour. +[list=1] +[*]Spawn new AI as there aren't players in the Mission. In other words, spawn as many AI required to simulate player behaviour. +[*]Attach various AI processes to let the AI execute certain tasks. +[*]Implements an fsm to accomodate a workflow to let the mission designer attach various AI workflows for AI implementation behaviour. +[*]PATROLZONE: +[/list] + +[b]PATROLZONE[/b]: Is an AI behaviour class, governing AI to patrol a zone, used by the AI balancer. +[list=1] +[*]Provide an fsm process, implementing AI patrol behaviour so that AI is patrolling a zone for a defined time. +[*]Expose various event functions to influence the AI patrol behaviour. +[*]The PATROLZONE class can be used in AIBALANCER to simulate players. +[/list] + +[size=22][b][u]4. Cargo Handling[/u][/b][/size] + +[b]CARGO[/b]: Cargo Handling of CONTROLLABLE object, thus UNITs and GROUPs. CARGO provides a dynamic way to Load, Unload, Board, UnBoard and Transfer Cargo between Carriers. [u]The cargo handling of units is animated, that means, you'll see the units moving towards or from the carriers ... +[/u][list=1] +[*]Board Cargo to a Carrier. +[*]Unboard Cargo from a Carrier to a point. +[*]Transfer Cargo from a Carrier to another Carrier. +[*]CARGO is the base class implementing the cargo workflow. +[*]CARGO_GROUP: Implements the Cargo handling for a GROUP. +[*]CARGO_UNIT: Implements the Cargo handling for one UNIT. +[*]CARGO_GROUPED: Implements the Cargo handling for multiple GROUPs. +[*]CARGO_PACKAGE: Under construction. This would simulate a package being carried by a carrier. +[/list] +Note: There are various Task Actions (ACT_) classes planned that would allow to deploy and pickup cargo in a battle field. +Note: There are various AI_ classes planned that would allow to deploy and pickup cargo in a battlefield.[/td] + +[size=22][b][u]5. Scheduling[/u][/b][/size] + +[b]SCHEDULER[/b]: The scheduling of methods has been intensively reworked. In the previous version of MOOSE, each SCHEDULER object controlled the scheduling of the method provided. However, with the new SCHEDULER implementation, the actually scheduling is now done by a SCHEDULEDISPATCHER class, which has one instance within the MOOSE framework, under _SCHEDULEDISPATCHER... SCHEDULER has some minor changes to the API. One of them is that SCHEDULER now allows to schedule more than one repeated schedule. When the method SCHEDULE.Schedule() is called, a ScheduleID is returned. This ScheduleID can then be used to Stop or (Re-)Start the schedule using the SCHEDULER object and the ScheduleID. The actual object controlling all the schedules is _SCHEDULEDISPATCHER. + + +[size=24][b][u]Other comments ...[/u][/b][/size] + +There are many more changes done within the framework, but these are very technical and hidden from the API set that the users will use. Some of the work that was done includes: rework scheduling, adding a scheduler dispatcher. + +There is still some work to be done on the [b]TASK_DISPATCHER[/b], but that will be done the coming weeks, so don't spend too much time on that for the moment ... + +Also, this release builds the foundation of many many other classes to come. Now that we have state machines and the object model is now more or less stable, other functions can be built upon this framework. It would be great that a community would see the benefits of this development and endorse it, like many have already done. + +The documentation is not completely up-to-date, but that will come and flatten out the next weeks. +Also, demonstration videos will be published on my youtube channel next year to demo some of the new functions, and I'll rework a few of the older versions. + +If you have problems using this release and somehow feel blocked, you can use the previous commit on the master branch. Just click in GITHUB on the previous commit, sync and you'll be fine. + +I hope you will have the same pleasure using this framework as the creators had making it. + +Thanks all; +FC
              AIBalancerAI_Balancer -

              This module contains the AIBALANCER class.

              +

              This module contains the AI_BALANCER class.

              +
              Account +

              (SP) (MP) (FSM) Account for (Detect, count and report) DCS events occuring on DCS objects (units).

              AirbasePolice

              This module contains the AIRBASEPOLICE classes.

              +
              Assign +

              (SP) (MP) (FSM) Accept or reject process for player (task) assignments.

              CARGOCargo -

              CARGO Classes

              +

              Management of logical cargo objects, that can be transported from and to transportation carriers.

              Client

              This module contains the CLIENT class.

              +
              CommandCenter +

              A COMMANDCENTER is the owner of multiple missions within MOOSE.

              DCStrigger -
              DEPLOYTASK -

              A DEPLOYTASK orchestrates the deployment of CARGO within a specific landing zone.

              -
              DESTROYBASETASK -

              A DESTROYBASETASK will monitor the destruction of Groups and Units.

              -
              DESTROYGROUPSTASK -

              DESTROYGROUPSTASK

              -
              DESTROYRADARSTASK -

              Task class to destroy radar installations.

              -
              DESTROYUNITTYPESTASK -

              Set TASK to destroy certain unit types.

              Detection

              This module contains the DETECTION classes.

              -
              DetectionManager -

              This module contains the DETECTION_MANAGER class and derived classes.

              Event -

              The EVENT class models an efficient event handling process between other classes and its units, weapons.

              +

              This module contains the EVENT class.

              GOHOMETASKFsm -

              A GOHOMETASK orchestrates the travel back to the home base, which is a specific zone defined within the ME.

              +

              This module contains the FSM class.

              Mission

              A MISSION is the main owner of a Mission orchestration within MOOSE .

              -
              NOTASK -

              A NOTASK is a dummy activity...

              PICKUPTASKPatrol -

              A PICKUPTASK orchestrates the loading of CARGO at a specific landing zone.

              -
              PatrolZone -

              This module contains the PATROLZONE class.

              +

              (AI) (FSM) Make AI patrol routes or zones.

              Positionable

              This module contains the POSITIONABLE class.

              -
              Process - -
              Process_Destroy -
              Process_SmokeProcess_Pickup
              ROUTETASKRoute -

              A ROUTETASK orchestrates the travel to a specific zone defined within the ME.

              +

              (SP) (MP) (FSM) Route AI or players through waypoints or to zones.

              STAGEScheduleDispatcher -

              Stages within a TASK within a MISSION.

              +

              This module defines the SCHEDULEDISPATCHER class, which is used by a central object called _SCHEDULEDISPATCHER.

              SpawnSmoke -

              This module contains the SPAWN class.

              +

              (SP) (MP) (FSM) Route AI or players through waypoints or to zones.

              StateMachineSpawn -

              This module contains the STATEMACHINE class.

              +

              This module contains the SPAWN class.

              Task -

              This module contains the TASK_BASE class.

              +

              This module contains the TASK class.

              Task_A2G -

              This module contains the TASK_A2G classes.

              -
              Task_Assign -

              This module contains the PROCESS_ASSIGN classes.

              +

              (AI) (SP) (MP) Tasking for Air to Ground Processes.

              Task_RouteTask_PICKUP - +

              This module contains the TASK_PICKUP classes.

              Unit

              This module contains the UNIT class.

              +
              Utils +

              This module contains derived utilities taken from the MIST framework, +which are excellent tools to be reused in an OO environment!.

              Zone -

              This module contains the ZONE classes, inherited from Zone#ZONE_BASE.

              +

              This module contains the ZONE classes, inherited from Core.Zone#ZONE_BASE.