From 2d178252681ee8f9073f82179166d8c03a729164 Mon Sep 17 00:00:00 2001 From: Sven Van de Velde Date: Wed, 3 Aug 2016 11:41:52 +0200 Subject: [PATCH 01/16] Progress --- Moose Development/Moose/Cargo.lua | 401 +++++++++++---------- Moose Development/Moose/Process_Pickup.lua | 173 +++++++++ Moose Development/Moose/Task_Pickup.lua | 137 +++++++ 3 files changed, 529 insertions(+), 182 deletions(-) create mode 100644 Moose Development/Moose/Process_Pickup.lua create mode 100644 Moose Development/Moose/Task_Pickup.lua diff --git a/Moose Development/Moose/Cargo.lua b/Moose Development/Moose/Cargo.lua index f4372934e..f02e61406 100644 --- a/Moose Development/Moose/Cargo.lua +++ b/Moose Development/Moose/Cargo.lua @@ -1,17 +1,227 @@ ---- CARGO Classes --- @module CARGO +--- 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 - - - - ---- 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 = {} +--- @type CARGO +-- @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 Positionable#POSITIONABLE 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", + STATUS = { + NONE = 0, + LOADED = 1, + UNLOADED = 2, + LOADING = 3 + }, + Type = nil, + Name = nil, + Weight = nil, + CargoObject = nil, + CargoCarrier = nil, + Representable = false, + Slingloadable = false, + Moveable = false, + Containable = false, +} + + +--- Add Cargo to the mission... +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:StatusNone() + + return self +end + +function CARGO:Spawn( PointVec2 ) + self:F() + + return self + +end + +function CARGO:Load( CargoObject, CargoCarrier ) + self:F() + + self:StatusLoaded( CargoCarrier ) + + return self +end + + +function CARGO:UnLoad( CargoCarrier, CargoObject ) + self:F() + + self:StatusUnLoaded( CargoObject ) + + return self +end + +function CARGO:OnBoard( CargoObject, CargoCarrier ) + self:F() + +end + +function CARGO:OnBoarded( CargoCarrier ) + self:F() + + local OnBoarded = false + + return OnBoarded +end + + +function CARGO:IsNear( CargoCarrier, CargoObject, Radius ) + self:F() + + local Near = true + + return Near + +end + + +function CARGO:IsLoading() + self:F() + + if self:IsStatusLoading() then + return self.Object + end + + return nil + +end + + +function CARGO:IsContained() + self:F() + + if self:IsStatusContained() then + return self.Object + end + + return nil + +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( Carrier ) + self:F() + + self.CargoClient = Carrier + 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_ZONE = { ClassName="CARGO_ZONE", @@ -326,180 +536,7 @@ function CARGO_ZONE:GetCargoZoneName() 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 = { diff --git a/Moose Development/Moose/Process_Pickup.lua b/Moose Development/Moose/Process_Pickup.lua new file mode 100644 index 000000000..968d1235d --- /dev/null +++ b/Moose Development/Moose/Process_Pickup.lua @@ -0,0 +1,173 @@ +--- @module Process_Pickup + +--- PROCESS_PICKUP class +-- @type PROCESS_PICKUP +-- @field Unit#UNIT ProcessUnit +-- @field Set#SET_UNIT TargetSetUnit +-- @extends Process#PROCESS +PROCESS_PICKUP = { + ClassName = "PROCESS_PICKUP", + Fsm = {}, + TargetSetUnit = nil, +} + + +--- Creates a new DESTROY process. +-- @param #PROCESS_PICKUP self +-- @param Task#TASK Task +-- @param Unit#UNIT ProcessUnit +-- @param Set#SET_UNIT TargetSetUnit +-- @return #PROCESS_PICKUP self +function PROCESS_PICKUP:New( Task, ProcessName, ProcessUnit ) + + -- Inherits from BASE + local self = BASE:Inherit( self, PROCESS:New( ProcessName, Task, ProcessUnit ) ) -- #PROCESS_PICKUP + + 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 = 'Navigating' }, + { name = 'Start', from = 'Navigating', to = 'Navigating' }, + { name = 'Nearby', from = 'Navigating', to = 'Preparing' }, + { name = 'Pickup', from = 'Preparing', to = 'Loading' }, + { name = 'Load', from = 'Loading', to = 'Success' }, + { name = 'Fail', from = 'Assigned', to = 'Failed' }, + { name = 'Fail', from = 'Navigating', to = 'Failed' }, + { name = 'Fail', from = 'Preparing', to = 'Failed' }, + }, + callbacks = { + onStart = self.OnStart, + onNearby = self.OnNearby, + onPickup = self.OnPickup, + onLoad = self.OnLoad, + }, + endstates = { 'Success', 'Failed' } + } ) + + return self +end + +--- Process Events + +--- StateMachine callback function for a PROCESS +-- @param #PROCESS_PICKUP self +-- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +function PROCESS_PICKUP:OnStart( Fsm, Event, From, To ) + + self:NextEvent( Fsm.Start ) +end + +--- StateMachine callback function for a PROCESS +-- @param #PROCESS_PICKUP self +-- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +function PROCESS_PICKUP:OnNavigating( 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_PICKUP self +-- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Event#EVENTDATA Event +function PROCESS_PICKUP: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_PICKUP self +-- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +function PROCESS_PICKUP:OnMoreTargets( Fsm, Event, From, To ) + + +end + +--- StateMachine callback function for a PROCESS +-- @param #PROCESS_PICKUP self +-- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Event#EVENTDATA DCSEvent +function PROCESS_PICKUP:OnKilled( Fsm, Event, From, To ) + + self:NextEvent( Fsm.Restart ) + +end + +--- StateMachine callback function for a PROCESS +-- @param #PROCESS_PICKUP self +-- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +function PROCESS_PICKUP:OnRestart( Fsm, Event, From, To ) + + self:NextEvent( Fsm.Menu ) + +end + +--- StateMachine callback function for a PROCESS +-- @param #PROCESS_PICKUP self +-- @param StateMachine#STATEMACHINE_PROCESS Fsm +-- @param #string Event +-- @param #string From +-- @param #string To +function PROCESS_PICKUP:OnDestroyed( Fsm, Event, From, To ) + +end + +--- DCS Events + +--- @param #PROCESS_PICKUP self +-- @param Event#EVENTDATA Event +function PROCESS_PICKUP:EventDead( Event ) + + if Event.IniDCSUnit then + self:NextEvent( self.Fsm.HitTarget, Event ) + end +end + + diff --git a/Moose Development/Moose/Task_Pickup.lua b/Moose Development/Moose/Task_Pickup.lua new file mode 100644 index 000000000..cb73cb894 --- /dev/null +++ b/Moose Development/Moose/Task_Pickup.lua @@ -0,0 +1,137 @@ +--- This module contains the TASK_PICKUP classes. +-- +-- 1) @{#TASK_PICKUP} class, extends @{Task#TASK_BASE} +-- =================================================== +-- 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: +-- +-- * **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_PICKUP + + +do -- TASK_PICKUP + + --- The TASK_PICKUP class + -- @type TASK_PICKUP + -- @extends Task#TASK_BASE + TASK_PICKUP = { + ClassName = "TASK_PICKUP", + } + + --- Instantiates a new TASK_PICKUP. + -- @param #TASK_PICKUP self + -- @param 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 + -- @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" ) ) + self:F() + + _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_PICKUP. + -- @param #TASK_PICKUP self + -- @return #nil + function TASK_PICKUP:CleanUp() + + self:GetParent( self ):CleanUp() + + return nil + end + + + --- Assign the @{Task} to a @{Unit}. + -- @param #TASK_PICKUP self + -- @param 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 ProcessPickup = self:AddProcess( TaskUnit, PROCESS_PICKUP:New( self, self.TaskType, TaskUnit ) ) + + 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 = 'Next', from = 'Assigned', to = 'Success' }, + { name = 'Fail', from = 'Assigned', to = 'Failed' }, + }, + callbacks = { + onNext = self.OnNext, + }, + subs = { + Assign = { onstateparent = 'Planned', oneventparent = 'Next', fsm = ProcessAssign.Fsm, event = 'Start', returnevents = { 'Next', 'Reject' } }, + Pickup = { onstateparent = 'Assigned', oneventparent = 'Next', fsm = ProcessDestroy.Fsm, event = 'Start', returnevents = { 'Next' } }, + } + } ) ) + + ProcessRoute:AddScore( "Failed", "failed to destroy a ground unit", -100 ) + ProcessDestroy:AddScore( "Pickup", "Picked-Up a Cargo", 25 ) + ProcessDestroy:AddScore( "Failed", "failed to destroy a ground unit", -100 ) + + Process:Next() + + return self + end + + --- StateMachine callback function for a TASK + -- @param #TASK_PICKUP self + -- @param StateMachine#STATEMACHINE_TASK Fsm + -- @param #string Event + -- @param #string From + -- @param #string To + -- @param Event#EVENTDATA Event + function TASK_PICKUP:OnNext( Fsm, Event, From, To, Event ) + + self:SetState( self, "State", To ) + + end + + --- @param #TASK_PICKUP self + function TASK_PICKUP:GetPlannedMenuText() + return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.TargetSetUnit:GetUnitTypesText() .. " )" + end + + + --- @param #TASK_PICKUP self + function TASK_PICKUP:_Schedule() + self:F2() + + self.TaskScheduler = SCHEDULER:New( self, _Scheduler, {}, 15, 15 ) + return self + end + + + --- @param #TASK_PICKUP self + function TASK_PICKUP._Scheduler() + self:F2() + + return true + end + +end + + + From 8ca74ee7dbe72bf214636f08848789d94e0a6207 Mon Sep 17 00:00:00 2001 From: Sven Van de Velde Date: Thu, 4 Aug 2016 07:47:36 +0200 Subject: [PATCH 02/16] Progress --- Moose Development/Moose/Cargo.lua | 1826 +++++++++-------- .../Moose/Moose_Test_CARGO_Pickup.lua | 8 + Moose Development/Moose/Positionable.lua | 27 +- 3 files changed, 1052 insertions(+), 809 deletions(-) create mode 100644 Moose Development/Moose/Moose_Test_CARGO_Pickup.lua diff --git a/Moose Development/Moose/Cargo.lua b/Moose Development/Moose/Cargo.lua index f02e61406..913ef372c 100644 --- a/Moose Development/Moose/Cargo.lua +++ b/Moose Development/Moose/Cargo.lua @@ -1,60 +1,75 @@ --- 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_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 = {} ---- @type CARGO --- @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 Positionable#POSITIONABLE 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", - STATUS = { - NONE = 0, - LOADED = 1, - UNLOADED = 2, - LOADING = 3 - }, - Type = nil, - Name = nil, - Weight = nil, - CargoObject = nil, - CargoCarrier = nil, - Representable = false, - Slingloadable = false, - Moveable = false, - Containable = false, -} +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 Positionable#POSITIONABLE 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", + STATUS = { + NONE = 0, + LOADED = 1, + UNLOADED = 2, + LOADING = 3 + }, + 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. ---- Add Cargo to the mission... -function CARGO:New( Mission, Type, Name, Weight, ReportRadius, NearRadius ) local self = BASE:Inherit( self, BASE:New() ) -- #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 } ) @@ -63,7 +78,7 @@ function CARGO:New( Mission, Type, Name, Weight, ReportRadius, NearRadius ) loca self.Weight = Weight self.ReportRadius = ReportRadius self.NearRadius = NearRadius - self.CargoObject = nil + self.CargoObjects = nil self.CargoCarrier = nil self.Representable = false self.Slingloadable = false @@ -72,9 +87,14 @@ function CARGO:New( Mission, Type, Name, Weight, ReportRadius, NearRadius ) loca self:StatusNone() + CARGOS[self.CargoName] = 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() @@ -99,16 +119,16 @@ function CARGO:UnLoad( CargoCarrier, CargoObject ) return self end -function CARGO:OnBoard( CargoObject, CargoCarrier ) +function CARGO:OnBoard( CargoCarrier ) self:F() - + end function CARGO:OnBoarded( CargoCarrier ) self:F() local OnBoarded = false - + return OnBoarded end @@ -117,9 +137,9 @@ function CARGO:IsNear( CargoCarrier, CargoObject, Radius ) self:F() local Near = true - + return Near - + end @@ -129,7 +149,7 @@ function CARGO:IsLoading() if self:IsStatusLoading() then return self.Object end - + return nil end @@ -141,7 +161,7 @@ function CARGO:IsContained() if self:IsStatusContained() then return self.Object end - + return nil end @@ -163,7 +183,7 @@ function CARGO:StatusNone() self.CargoClient = nil self.CargoStatus = CARGO.STATUS.NONE - + return self end @@ -173,7 +193,7 @@ function CARGO:StatusLoading( Carrier ) self.CargoClient = Carrier self.CargoStatus = CARGO.STATUS.LOADING self:T( "Cargo " .. self.CargoName .. " loading to Client: " .. self.CargoClient:GetClientGroupName() ) - + return self end @@ -183,7 +203,7 @@ function CARGO:StatusLoaded( Client ) self.CargoClient = Client self.CargoStatus = CARGO.STATUS.LOADED self:T( "Cargo " .. self.CargoName .. " loaded in Client: " .. self.CargoClient:GetClientGroupName() ) - + return self end @@ -192,7 +212,7 @@ function CARGO:StatusUnLoaded() self.CargoClient = nil self.CargoStatus = CARGO.STATUS.UNLOADED - + return self end @@ -221,26 +241,794 @@ function CARGO:IsStatusUnLoaded() return self.CargoStatus == CARGO.STATUS.UNLOADED 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 Positionable#POSITIONABLE 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 } ) + + self.CargoObject = CargoObject + + return self + end + + --- Onboard representable Cargo to a Carrier. + -- @param #CARGO_REPRESENTABLE self + function CARGO_REPRESENTABLE:Onboard( Carrier, OnBoardSide ) + self:F() + + local OnBoardScheduler = SCHEDULER:New( self, self.ExecuteOnboarding, 1, 1, 0, 30 ) + + 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 + + local Points = {} + + local PointStartVec2 = self.CargoObject:GetVec2() + + 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 + + --- Can the Cargo Onboard to the Carrier? + -- @param #CARGO_REPRESENTABLE self + -- @return #boolean true if Cargo is near enough to be Onboarded. + function CARGO_REPRESENTABLE:CanOnboard( Carrier ) + return self:IsNear( Carrier ) + end + + --- Execute the Cargo Onboarding to the Carrier. + -- @param #CARGO_REPRESENTABLE self + function CARGO_REPRESENTABLE:ExecuteOnboarding( Carrier ) + if self:CanOnboard( Carrier ) then + self.CargoCarrier = Carrier + self.CargoObject:Destroy() + return false + end + return true + 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 } ) + + return self + end + +end + +do -- CARGO_GROUP + + --- @type CARGO_GROUP + CARGO_GROUP = { + ClassName = "CARGO_GROUP" + } + +--- CARGO_GROUP Constructor. +-- @param #CARGO_GROUP self +-- @param Mission#MISSION Mission +-- @param Group#GROUP CargoGroup +-- @param #string Type +-- @param #string Name +-- @param #number Weight +-- @param #number ReportRadius (optional) +-- @param #number NearRadius (optional) +-- @return #CARGO_GROUP +function CARGO_GROUP:New( Mission, CargoGroup, Type, Name, Weight, ReportRadius, NearRadius ) + local self = BASE:Inherit( self, CARGO:New( Mission, CargoGroup, Type, Name, Weight, ReportRadius, NearRadius ) ) -- #CARGO + self:F( { Type, Name, Weight, ReportRadius, NearRadius } ) + + 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 + +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 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" } - } - } + 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. @@ -248,869 +1036,295 @@ CARGO_ZONE = { -- 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. +-- @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:F( { CargoZoneName, CargoHostName } ) - self.CargoZoneName = CargoZoneName - self.SignalHeight = 2 - --self.CargoZone = trigger.misc.getZone( CargoZoneName ) - + 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 + if CargoHostName then + self.CargoHostName = CargoHostName + end + + self:T( self.CargoZoneName ) + + return self end function CARGO_ZONE:Spawn() - self:F( self.CargoHostName ) + 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 + 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 + return self end function CARGO_ZONE:GetHostUnit() - self:F( self ) + 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 + 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() + self:F() - local SignalUnit = self:GetHostUnit() + local SignalUnit = self:GetHostUnit() - if SignalUnit then - - local SignalUnitTypeName = SignalUnit:getTypeName() - - local HostMessage = "" + if SignalUnit then - 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 + local SignalUnitTypeName = SignalUnit:getTypeName() - Client:Message( HostMessage, 20, SignalUnitTypeName .. ": Reporting Cargo", 10 ) - end + 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() + self:F() - local Signalled = false + 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 then - if self.SignalType.ID == CARGO_ZONE.SIGNAL.TYPE.SMOKE.ID then + if self.CargoHostName then - trigger.action.smoke( SignalVehiclePos, self.SignalColor.TRIGGERCOLOR ) - Signalled = true + -- A Host has been given, signal the host - elseif self.SignalType.ID == CARGO_ZONE.SIGNAL.TYPE.FLARE.ID then + local SignalUnit = self:GetHostUnit() - trigger.action.signalFlare( SignalVehiclePos, self.SignalColor.TRIGGERCOLOR , 0 ) - Signalled = false + if SignalUnit then - 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 + self:T( 'Signalling Unit' ) + local SignalVehiclePos = SignalUnit:GetPointVec3() + SignalVehiclePos.y = SignalVehiclePos.y + 2 - trigger.action.smoke( ZonePointVec3, self.SignalColor.TRIGGERCOLOR ) - Signalled = true + if self.SignalType.ID == CARGO_ZONE.SIGNAL.TYPE.SMOKE.ID then - elseif self.SignalType.ID == CARGO_ZONE.SIGNAL.TYPE.FLARE.ID then - trigger.action.signalFlare( ZonePointVec3, self.SignalColor.TRIGGERCOLOR, 0 ) - Signalled = false + trigger.action.smoke( SignalVehiclePos, self.SignalColor.TRIGGERCOLOR ) + Signalled = true - end - end - end - - return Signalled + 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:F() - self.SignalType = CARGO_ZONE.SIGNAL.TYPE.SMOKE - self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.WHITE - - if SignalHeight then - self.SignalHeight = SignalHeight - end + self.SignalType = CARGO_ZONE.SIGNAL.TYPE.SMOKE + self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.WHITE - return self + if SignalHeight then + self.SignalHeight = SignalHeight + end + + return self end function CARGO_ZONE:BlueSmoke( SignalHeight ) - self:F() + self:F() - self.SignalType = CARGO_ZONE.SIGNAL.TYPE.SMOKE - self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.BLUE + self.SignalType = CARGO_ZONE.SIGNAL.TYPE.SMOKE + self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.BLUE if SignalHeight then - self.SignalHeight = SignalHeight + self.SignalHeight = SignalHeight end - return self + return self end function CARGO_ZONE:RedSmoke( SignalHeight ) - self:F() + self:F() - self.SignalType = CARGO_ZONE.SIGNAL.TYPE.SMOKE - self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.RED + self.SignalType = CARGO_ZONE.SIGNAL.TYPE.SMOKE + self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.RED if SignalHeight then - self.SignalHeight = SignalHeight + self.SignalHeight = SignalHeight end - return self + return self end function CARGO_ZONE:OrangeSmoke( SignalHeight ) - self:F() + self:F() - self.SignalType = CARGO_ZONE.SIGNAL.TYPE.SMOKE - self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.ORANGE + self.SignalType = CARGO_ZONE.SIGNAL.TYPE.SMOKE + self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.ORANGE if SignalHeight then - self.SignalHeight = SignalHeight + self.SignalHeight = SignalHeight end - return self + return self end function CARGO_ZONE:GreenSmoke( SignalHeight ) - self:F() + self:F() - self.SignalType = CARGO_ZONE.SIGNAL.TYPE.SMOKE - self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.GREEN + self.SignalType = CARGO_ZONE.SIGNAL.TYPE.SMOKE + self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.GREEN if SignalHeight then - self.SignalHeight = SignalHeight + self.SignalHeight = SignalHeight end - return self + return self end function CARGO_ZONE:WhiteFlare( SignalHeight ) - self:F() + self:F() - self.SignalType = CARGO_ZONE.SIGNAL.TYPE.FLARE - self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.WHITE + self.SignalType = CARGO_ZONE.SIGNAL.TYPE.FLARE + self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.WHITE if SignalHeight then - self.SignalHeight = SignalHeight + self.SignalHeight = SignalHeight end - return self + return self end function CARGO_ZONE:RedFlare( SignalHeight ) - self:F() + self:F() - self.SignalType = CARGO_ZONE.SIGNAL.TYPE.FLARE - self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.RED + self.SignalType = CARGO_ZONE.SIGNAL.TYPE.FLARE + self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.RED if SignalHeight then - self.SignalHeight = SignalHeight + self.SignalHeight = SignalHeight end - return self + return self end function CARGO_ZONE:GreenFlare( SignalHeight ) - self:F() + self:F() - self.SignalType = CARGO_ZONE.SIGNAL.TYPE.FLARE - self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.GREEN + self.SignalType = CARGO_ZONE.SIGNAL.TYPE.FLARE + self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.GREEN if SignalHeight then - self.SignalHeight = SignalHeight + self.SignalHeight = SignalHeight end - return self + return self end function CARGO_ZONE:YellowFlare( SignalHeight ) - self:F() + self:F() - self.SignalType = CARGO_ZONE.SIGNAL.TYPE.FLARE - self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.YELLOW + self.SignalType = CARGO_ZONE.SIGNAL.TYPE.FLARE + self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.YELLOW if SignalHeight then - self.SignalHeight = SignalHeight + self.SignalHeight = SignalHeight end - return self + return self end function CARGO_ZONE:GetCargoHostUnit() - self:F( self ) + 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 + 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 + return nil end function CARGO_ZONE:GetCargoZoneName() - self:F() + self:F() - return self.CargoZoneName + return self.CargoZoneName 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 diff --git a/Moose Development/Moose/Moose_Test_CARGO_Pickup.lua b/Moose Development/Moose/Moose_Test_CARGO_Pickup.lua new file mode 100644 index 000000000..4d5165081 --- /dev/null +++ b/Moose Development/Moose/Moose_Test_CARGO_Pickup.lua @@ -0,0 +1,8 @@ + +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/Positionable.lua b/Moose Development/Moose/Positionable.lua index a8e54b9ba..82642f088 100644 --- a/Moose Development/Moose/Positionable.lua +++ b/Moose Development/Moose/Positionable.lua @@ -79,9 +79,30 @@ function POSITIONABLE:GetVec2() if DCSPositionable then local PositionablePointVec3 = DCSPositionable:getPosition().p - local PositionablePointVec2 = {} - PositionablePointVec2.x = PositionablePointVec3.x - PositionablePointVec2.y = PositionablePointVec3.z + local PositionableVec2 = {} + PositionableVec2.x = PositionablePointVec3.x + PositionableVec2.y = PositionablePointVec3.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 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 = POINT_VEC2:NewFromVec3( PositionablePointVec3 ) self:T2( PositionablePointVec2 ) return PositionablePointVec2 From a95d4b6cafe8be01caf5d5a686ea081bb7086dd0 Mon Sep 17 00:00:00 2001 From: Sven Van de Velde Date: Thu, 4 Aug 2016 08:05:30 +0200 Subject: [PATCH 03/16] Progress --- Moose Development/Moose/Cargo.lua | 74 +++++------------------- Moose Development/Moose/Point.lua | 38 ++++++++++++ Moose Development/Moose/Positionable.lua | 12 ++-- 3 files changed, 57 insertions(+), 67 deletions(-) diff --git a/Moose Development/Moose/Cargo.lua b/Moose Development/Moose/Cargo.lua index 913ef372c..314706d99 100644 --- a/Moose Development/Moose/Cargo.lua +++ b/Moose Development/Moose/Cargo.lua @@ -30,7 +30,7 @@ do -- 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 Positionable#POSITIONABLE CargoObject The alive DCS object representing the cargo. This value can be nil, meaning, that the cargo is not represented anywhere... + -- @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. @@ -254,7 +254,7 @@ do -- CARGO_REPRESENTABLE --- CARGO_REPRESENTABLE Constructor. -- @param #CARGO_REPRESENTABLE self -- @param Mission#MISSION Mission - -- @param Positionable#POSITIONABLE CargoObject + -- @param Controllable#Controllable CargoObject -- @param #string Type -- @param #string Name -- @param #number Weight @@ -272,8 +272,9 @@ do -- CARGO_REPRESENTABLE --- Onboard representable Cargo to a Carrier. -- @param #CARGO_REPRESENTABLE self - function CARGO_REPRESENTABLE:Onboard( Carrier, OnBoardSide ) - self:F() + -- @param Unit#UNIT Carrier + function CARGO_REPRESENTABLE:Onboard( Carrier ) + self:F() local OnBoardScheduler = SCHEDULER:New( self, self.ExecuteOnboarding, 1, 1, 0, 30 ) @@ -287,67 +288,18 @@ do -- CARGO_REPRESENTABLE local Points = {} - local PointStartVec2 = self.CargoObject:GetVec2() + local PointStartVec2 = self.CargoObject:GetPointVec2() + local PointEndVec2 = Carrier:GetPointVec2() + - self:T( 'CargoPos x = ' .. CargoPos.x .. " z = " .. CargoPos.z ) - self:T( 'CarrierPosMove x = ' .. CarrierPosMove.x .. " z = " .. CarrierPosMove.z ) + Points[#Points+1] = PointStartVec2:RoutePointGround( "Cone", 10 ) + Points[#Points+1] = PointEndVec2:RoutePointGround( "Cone", 10 ) - 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 ) + local TaskRoute = self.CargoObject:TaskRoute( Points ) + self.CargoObject:SetTask( TaskRoute, 4 ) end - self:StatusLoading( Client ) - - return Valid - + self:StatusLoading( Carrier ) end --- Can the Cargo Onboard to the Carrier? diff --git a/Moose Development/Moose/Point.lua b/Moose Development/Moose/Point.lua index 9b7f73546..2372135c1 100644 --- a/Moose Development/Moose/Point.lua +++ b/Moose Development/Moose/Point.lua @@ -381,6 +381,44 @@ function POINT_VEC3:RoutePointAir( AltType, Type, Action, Speed, SpeedLocked ) return RoutePoint end +--- Build an ground type route point. +-- @param #POINT_VEC3 self +-- @param #POINT_VEC3.RoutePointAction Formation The route point Formation. +-- @param DCSTypes#Speed Speed Speed in km/h. +-- @return #table The route point. +function POINT_VEC3:RoutePointGround( Formation, Speed ) + self:F2( { Formation, Speed } ) + + local RoutePoint = {} + RoutePoint.x = self.PointVec3.x + RoutePoint.y = self.PointVec3.z + + RoutePoint.action = Formation + + 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 diff --git a/Moose Development/Moose/Positionable.lua b/Moose Development/Moose/Positionable.lua index 82642f088..d53ea0c9d 100644 --- a/Moose Development/Moose/Positionable.lua +++ b/Moose Development/Moose/Positionable.lua @@ -77,11 +77,11 @@ function POSITIONABLE:GetVec2() local DCSPositionable = self:GetDCSObject() if DCSPositionable then - local PositionablePointVec3 = DCSPositionable:getPosition().p + local PositionableVec3 = DCSPositionable:getPosition().p local PositionableVec2 = {} - PositionableVec2.x = PositionablePointVec3.x - PositionableVec2.y = PositionablePointVec3.z + PositionableVec2.x = PositionableVec3.x + PositionableVec2.y = PositionableVec3.z self:T2( PositionableVec2 ) return PositionableVec2 @@ -94,15 +94,15 @@ end -- @param Positionable#POSITIONABLE self -- @return Point#POINT_VEC2 The 2D point vector of the POSITIONABLE. -- @return #nil The DCS Positionable is not existing or alive. -function POSITIONABLE:GetVec2() +function POSITIONABLE:GetPointVec2() self:F2( self.PositionableName ) local DCSPositionable = self:GetDCSObject() if DCSPositionable then - local PositionablePointVec3 = DCSPositionable:getPosition().p + local PositionableVec3 = DCSPositionable:getPosition().p - local PositionablePointVec2 = POINT_VEC2:NewFromVec3( PositionablePointVec3 ) + local PositionablePointVec2 = POINT_VEC2:NewFromVec3( PositionableVec3 ) self:T2( PositionablePointVec2 ) return PositionablePointVec2 From 75ea07fea08e07f6d4fe69430a897fe5f5faeeae Mon Sep 17 00:00:00 2001 From: Sven Van de Velde Date: Thu, 4 Aug 2016 08:17:15 +0200 Subject: [PATCH 04/16] Progress --- .../MOOSE_Test_CARGO_Pickup.miz | Bin 0 -> 186313 bytes .../Moose_Test_CARGO_Pickup.lua | 8 ++++++++ 2 files changed, 8 insertions(+) create mode 100644 Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_Pickup/MOOSE_Test_CARGO_Pickup.miz create mode 100644 Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_Pickup/Moose_Test_CARGO_Pickup.lua diff --git a/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_Pickup/MOOSE_Test_CARGO_Pickup.miz b/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_Pickup/MOOSE_Test_CARGO_Pickup.miz new file mode 100644 index 0000000000000000000000000000000000000000..a6537ced1ff76c185a3426554bfcc02993a83389 GIT binary patch literal 186313 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 =M}IW{FiQe5T$9p?L6Jcp?zI)=-x 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

IQ3?6Esj6)Rd`T=yU^+UI&X?Oly2FmFPwo@ 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 m zJ9_@33dW;+Vc>HuX`@h&$r`!JpTNzBt^Gg+tW-EX$J0_It3}ynuVqpakcq@JdMU2^wMf$qB~yESw$Hc|x~ zn56%hf!-t>ss#{n;3B0B2D$~~2`5@r@s~3@QnJxn@krn16(VmF;j1hF2w%chU#^ym57UVYT#dwmQ&av3%3~i5msF`8nfjA5ItF#G zs&DQlrROWk`BzRO^de{eJP?i?1tzBxENJo@33 z@oaU~sV$~+C0YGyAq5gRzgiXGs;pDxs{E32S!2fAWixrcP=BhlD}w1@Nr6O$T!lKO zsS0(P>mWK#KHL~asO#T9t?CjiM9)xd>;`_)B+|tu2!(#|g1L_|h7G8S@=#8E&1I|# znR{Ag_Y^Aq`Snc2u9%>i6=UBZ)oGtl<6^OZkQNs?W|ahgz{M&NRX*Y|L1N|24h-Gz zc04N}?liqF~ zFOcOAZ;RNaE}Nj@=|qX)M;L~QF|)t~6|xgRDetLnkskc*-K|E=an6PUpZ>I*=I5|k zZ7GqWbC})nW|2G4s+hS^9(ID}IvDRe0|?VTp^InSxG(YjWG>Ln@ym)+Qz9hFP2L#M@P`ea}wCPb0_Al$B?Vzd6jaqTh zrbY&W<0Wq)JS2E%4E)BoYbBzrqfQ$EvcXELsa`vCr8R)T!Qg9DrZx=0gWmZwDeRKFc<=%>U;)FIM&)7V1S)jLzsQ zQK@Uc6;$ZY{!AH5@N^4G<6 zaxwmpLv{z916GGVu-g#sHE(kj=TRtd5SgLVG7$uz(hge4RAc~uEacLMQlbfWI?C#5 ztyu6ceM95e(7a2fvQE4{ATp4-5Owa#bSMP8JF?b7gBThHCD~x`I|2GqBIxi#rjs{^ zFP=R+c>b8}>_ANe-)Cxnl0TIMP#z5)5_0*jhlG+JYayXz3_-$^07z`TS~k!i>MS%% zjfS_X{H$B#woPw}O(j{sB=VAqn;@^|Z1*day z0i?w-bTyTMqgBzgu=&ZyE3Cxc)GOrun^FJV89#M$XHE(;;Bzc2Iag}J-#uD~d!AHN zP~!-x_=@;qmonfza_;ruI%jNWgJ^6;`-ttqg zZmo}p_7gde#gCW_1m9$<|H~dN)oW)1#vTs8cfO~#0l>G_;Fs4CdwP6m@pMhPvK5ii zF`o$3RgcuO;WgYOlnARW5WcLdgc2G){p0&0^(mmc5ts8>#mp#<=W2m7tz=mvkOpKI z)!jME{g3;+fbh~l!2PH}fQu*#1%JuUGJC+&D8OkAO2P=6P^#luW;C*9CReY92@y5T z-#1~@OzrSX`}dBEJOa9584?sYAmaRHkU=3vkGdWvhl1bbkw)djl~PX#0qKO|qXcdK z^ioD}bP1+nw!Xi_G!;ni<_<}^K@A87t27YZDKD1SJmou&nz>!29X^QSs)gT7vT!ei z-J?fkeCeO8;7}!val5)Xm)FL?63ZSQ2E&ude#f$6{6 zc3mo6uk$x2my7p#cnNdz?8TqK+V%43(dkKe|Kb#vT)lW;wtedoPp!G#+ZlJb6&Ev3 zFEU!Se7fW87HjOopr~6?{t5f@kICzS(AC$#!dILF5TU0-<<*Wii1i@2WZcNlqVi7Y zInf?o;4i0G(SSgZAL`x+eVH4dD#`UVT=uv^?SAfg6o4H_#Z&-i;x@7qWW%a|C&J{s zQdc8^199-`CVX(?UCkJpL490F4gB*5r0=dD+Jh-_WV$JD(m+(olNz8OE@q3RQ_+5Q z^hl>RUx&4{<6_(OG|Ww|m1NZGv;VoC%18cU>!H;5Z*maDKh3Ul@v-(*&g13cDtQZy zB&$H;I+;{s$GOKb9i+-7Urm1M5*7LV@WuBpUN#YvTOP}&ONghwCYJB8(YDc)s6_C5 zu>`mNHStu9wvDI)8^QEtjtN25!xS2A8%=;2!SSbysZf8{#8EcdHkK0V?dQ1;+k9iX z*V~t1Z<3q-XZ8`~bl|-wi{+7^2i~*TWDLwX1LxDK$#zr?TSP4=;*4}TUR^ZB+L9v_{YzI^f5 z=na=DU+X5T%v?aN0AAo3d!40`D!tHBmE_4^p1D3r&DTHB$(QBPdoSIaI{WjNRJzh8 z*Hi(Sl-#KxlYek`Q3&LRDrS!A@|j-z+`i>|uBx}|-r?MESa91FJ(B-p-Flg1sa5x0 zcfR%V-977EdYO~6^&yl;-=D-u8qfaDfin(3*RR|DyXi_&hMvAHkVGHBmEhv<-JZeQ zM@e6Tif$63!og6rap*A!D%J`h;fD1L3<*(shNgsQTc4C`H1!c*^VU(DE_mC;Vy-Z= zrU~_dR59~Pz?g5O9+^4|j(j9l%?#O!<`b#*=P7tJ+B))g*)6D@*p!gUTztwU`R3>^ zN6$}5%4_I-JW<;}>Rs@g`qGu%m+A-28(f)|yeyAyP+$1kRPRcawiffsJa(tE#b@|m zKe5!ZDkqn{jV%=HSQ~f4hChU^8n0WaDx!Y*9 z@;sBep_CD`{CuUNZ%p*!2k+%xDtB6PooPuz55C-erM<0O>#j1q1;p%(WOa{cAICR^ zy;E#DH-x_^Hnv}}`2#5~4^9s>xDq}$8w1bj>VuWV++o$`g{M63RhrHx(rpj{Hjn31 z6O>}t3;pahl^%)kXYr>S@`owOyw*Bf>r{DuX{^GY7La(xI=9fs7xWsyVcv3C2q+Yqc zD)UmXoKj}~$#_kTe0`GXo0WeudKuwVMlnJW!M5IcqVAB_-LlPbr$M)7l-_1n6xevl z-J_Rt_F!niaWRT~Yv%ttJvj7DneZ)Y$CM53(};=alv*&M#yy!Y5t)0mUHV|292~|C zn*_Ol@1U%%_mS#k+iv@Enrc`wC6CoNV`^)kLVMp*sl>q9|A#hkeqAO`xsWEX$wbYR zFsV@C$E%5`OQ6xlDfM>}^1T*BtW43PjW>dLa<$klz(af{ye7yPKpAau3&4C!D+gu` zlPNcmTQ-}zOk^#T?sujEj*S3zzBBcK148idm8mReH>@tdm)lMMv5lqdcKC(0Kiugy zHj{cBwq<+iS4=N=;B^e91`=H!j3-;*;->aBw<V;mv)`OZhv4&&5_uu994EpK`XJD_4BHc`D#Q0m+>vxlo)g zr}FIx1fLY5KA8!nVOC)BY)t&nv;;#N7uNb-R@yH!hLMWa0BKxuc1)d#tRs}j06uXQ zl^AF$c(y~dnqiz-l>^T&OQoU;O?MJ%4TX0s2BHe;JwXE zEW2fLwwS13!4v!K>@OOK0Z7VOXkH}8q!X$Au%*j zEXx|ZJ~{r9pLGIK=-1F4owlYDVffxW!!!Ny{Cqinnd{Rhj|S{4kkzY|CScTRr^(;p z7kGL_Q3+gkTcY(y{G(G)*|8aERYgkQ9{6P?&acv7Gijg?SpTT`(r+%|UiRMbF~uL9#& zN$;A5fGMN>7rV^}u*pQR3PPGpMB{Hi=}!`vO$-q=GORA7gR&J(av54l<+YKcah6GBP<|{rMB~S~^vtq@Qq)0Y6d<)E zvt77AmD$G?HL!(BL)Lhk-z*@t{RR1M0#)dXUcnQJPq)7E4rI3`;h7;`g7b7aolh^X zWk8Gb1!PCj{3$UlyaoBqH_FTUn{W1#qo1#q$o_ICkOh#~MFv{I{2F5H=hOLkHvPMl zJd1NL=Zb_X?;t{;=i>XdOoBEJ8vQUE&*x#QF-ZX;#JJQSCo-2FSkP{Av;w(Eii%KX zT=GOE+LgX1S#=EBSN=J$fG4!r@~CG^Mw$Q$>Mr7o5K8!-wG+&On#W< zU}6(0Kmpg_C~6jG*8s@Dp}5M=rsvZ$>=4)4^<2m#NOLOJ{$zT7o-cKznrl=pWYA`V z?Wvo*5CH+|LgE#IV+Eb)#x52j%=S0mC?=4Ubi~Yc(M%42x5hAdAklwg86^e4qE5mRP^ue%7B1x zRUGpub?IW$l^sf$1ewLf;^SP8N9AKeTrP57d@`B2oE&uz9y~}6uEn62#a{AcHhp&? zPQr6>FqtKflE=AJB*f|yI@mjL2E4?43|uNNnnl`E?}_u7DXGdNEpq@eX`~ewJa~3| za&r9Qx&B=%y|bU$S^I3sT+%1X4;_a9+K5=i_#vcdn7*FmfVQKLWr4=hom3w~xjfKD zC$?U2KgjJbnix7s)a{k}WyZre%tdlXONQShceEX}Z>{hr?3AoQ+>n&=d1^MGcT=&X zmtGp%lDR76T)mTX1!uL77t^x~3so&5J&SCXS{<1mA);T5+LU}xK-%CGc^Z}&{86KU z@fE|Poc=)?CXIvaX__2l2WfJ;9M6lZ#nNU`roMtc#f=@t6v%rflQ>h;EHz_I%^*@2 zJI55WZa(d!MzZrbdmV%HIHEQ{+uiLK%ULQl#O zHG=WkIxzl*old7rUx!%yZQ1c-LtHdT2~hwQ;j?t*(W$BJ)@98HImrN7fL16pvLbJ(K_y8=w`q+mrtPkwi{Xhyh^Dc;`renhi9ub)Y)aE;R3&j{sv5GOX22H5NR2H2WrwjAV z<&Tt9C+yCM~Odd3h?vzvGh1$ZcBZ)1s6En zlInJ#FKSb`6R!+8m=Mz3lb8aM44KsaKe$7~>COGPvl9k*rPdyccu7iQyobou0Sfg> zAx8ezH5@KAvng-32|K1_&h1<|f*<{V zav2f_Ce8`_4B=~3$J|npF>XoZ0?RzgjLu{sk`ZYt@<)gAz{We>oTSvqc29&{W61*p z?{X`rY*}u!FxOj}8!XNZm#3PEFfWI{NLfD|XT83}ou<$hVa-I$*$%0C#|?(=-)|QS z*r{0tEMHFS3;gO`ZJl>e6JNB)1JXpA^rAE=B0?wvp@T>dNC&CXi$EZuNJo0_h;$*; zAibkND4~a51f)n21*8Y*y!__({`vXd?(FW&z2|(+Id|sHo!PzfT~w9+Xqx40{hfk1 zDt!3v@I5(u=l~r1Zrk+4Nr>s{lz$XO!KCj0Jl1t$4|#lsc?1=_celMnY;tGG*>#24H|&?px$8{t%F zbS2+0l5RwalvvVVr{CN&X7=byN1yo{VT*EgT$0S~r=CD`Zc15Ol|Ma;vl{E~x#biU z^N6<3pW))I@YmGC(`0rTu+*!Ct*C9ZZ%UKWsfMb#dM2=-q0RBSEy{&lc(E9^l&X`% zR?QKqYwT0B5K_0g&^S^kJ_Bo@?9B2`6B3y%Z4y;XXswd4idUH~+1P&6B7zg4U{g^bGALPa6tE+)&E2s}4mKM#Tu-%8ggD+&y<;rbhYGawEpzCOf1b>77B)@= z*TDJvj%p2cKDwx?^fSPN-*ZhPT!_jM`QDg_hW z-2K%m7nNzm2#F}qVLq!PEBxwY@;uD&Pk@Vs#3>Q)8_gFR;uu@zR`z;#k@zEI*{G~OX^nuZXe$0~S{gLHT zH|y}aSVqI}uBYtFW_DV)cZ&_nu6=OGaGMhakn;4CsY+r3R_Wia-{TZLdMsuRfp)j=fu0B_|N z&0T$_?;*2!at*Ru#C2ff<%*gOh@LLVN^5chHL1@b9c@bp@UbB~@Qgn^SM7C)Qfz0p zu7k>1wdPlSQ}7#uphu;>*1wZV-j^4%RPjbVXY7oQWp0A9I=7skqDH z@jl9MWdH|VOG7QPf6eJi+xHHo=Pvb%7#~(p1+PBXX(9U+uAwc)iu_Dd_WV;`unORZ zi2W+5XsVOYAS`M3qBgS?Ir1`RA+9HLYQbIS$zZm9!Kasu? z6^T}*!|MFUucFU5H|Tt*?-L0s>7)-dO0dW2&xTC%Khu9g!%WX{hYLw6dzX!9x2du; zNHC~5sT%levhrvG{$?(4xAVX-rrzt>Em+Voh6feiu!{58ci`CZLAh&?p1|I6a%9oyd4UWWs(8z)p zADNwxVhVD)@_#&$8;|x4*9>_)-H1SxkZY1ze31&g(YsAxFw}Qx6Ud%21!4Gvx`qp&aIF9S%I&Kt36Ry4JH>fbf#lR7}Iu(CE4(&8g zBahy}iG-HgLMnE&HO*~~(_*D>T`0-w1VBTT@vN|FO4RswlY9pp%?O9ck;NlYB6~CZ zw`=7JWYsb=}+8OnT=Z2O&qc%O^jOW>2Msr@2XKm zpMpZwB4aqIy26tj!0ku+Zq$^APS?#vHMSK$NVkdw?q26T|DrBJ$PjM* zN6q726hh3I3{m8L?D5-%v)S=2%qEHSReUKt;joc`!FI=3FHK*JW3A8V38Rh_uWFe< zrMf)TfwM3r;zy%H*|w`nFZ#Z|o`+T?2VAL%|DnsXW;BuGitp@44xiQ5yFB~BZw`bS zX$46;Jj>6QteONl3tae~JV%l8w6F!n_|o>M4Y4J7mY)iBwk5Nf~5b=7q^EHWjG)t2h zP@1e&>MQ&VQ%VQg()HOi;Z91A>pmT75|FtEj^1gYFmNy~++R%KW%@Z(ybs&PgA;Zy zO@tObQIQtfxaO!nh5a#ZdMhY)*}BuDlV`nGW+`@R#8G^vQOu-R{&WC5d-3Ue>Xq(d zb&kBaLjKCE4M{)}j5{H5p`LaTWM?iz8=7;udQdLZlRH_4-IiF7;*1yOL$@Byo|EjC zWN^9tX21*Wl2*mYueG?Oh-~LaMo;5-Ni$P2B}$C2w;v2CaTzgyIKbeXs<#XKQuQ;u=$*m_WnS-8|M-4?ZBz z*dWp`vYYPE>T;WAt|MRN8g6a;T&>mo-eo`gjuCA4F#EA%{fUHVKbc0gf&^7*Kg^?$ znLDb8x@>4dqvj9agHszX_B2&o$=AjM8Gy@o{1;;=(*dJhA}_*qJAzf4k&}wU#b01& zi`G3#-5k6}4epnG-%G+%88+LJ1b6kh-VcAE|B|lh`0;fs59dZ}`XJ!QG!A!Casd2m zJI$}y;+xyNxB!1^LAIP8gIAW&TE~2p4P$b1VuNAi;KySnR}k0duU&(Khf`Y+GBWbS zKoII61fNwmGcTm%kSGb7>Rl$ba5RId?AB`rDquF(eAAi0iaS$}Sb8}1lG(W37T-(3 zoBhi5cA`p>m{$XiyycI%^20glBlVN)#a7wa-dfo@R5HFN1q3r(ZL+&fWJn#Uu&P2k z7avyXp3*|09m#0YMx>Ogz7)iT?MKRG|_6gi5d0T)cGJH zi97iG;X2#~a8nAyd+#WV1iezCqbvjduwd5#K#Fcm&mb;2$oG8P=Iaf@z=udHmLrqM zhU$DqWtzWf*mtWi91%x;FOlqQl?U!&gV#;L!{C`d0p=(+jJ8@Ez%#{tYVZcRygF9qy^Z zIFhBLcmAP;^&;Bj=2&&lLd(MWnp`(39#@q0PB3UO7R4pQt9WGz%{BlCC3&Py;}l^w zKpdIrWLfD7lk+VeTcW=1$M`pDOT^nEHRbFa%ZQmKg&vO$oV7MFcWv z&mv2zcr&s`>!w|f3Sw!{MWI%lidGeh@myWLBN4Vv zPu;uQm*pL$jR~lyaWVB;6z}FT&rP}!7h@(CCzFnB%|8}e`DerkYkunOISrsB^WriJ z541nq9$8UX>BuG8J_;^c2sP`M8}Oz1J*V8e%S;^7QJ_vL6pRxV|2~2*?l zO`CRsvazQ#sbqjl`=X*nOh3y<@Iwf9+TZY0h0PzHz^abR3bYQ z+I=CJ6+8^`0=>3Zyjvr7N9c`5I)mv0_;BTFVRt)2my1O&LEu|A+_~H!36R0y=9oIk zcOoHI(iH^T1C>7&53dX*&#Ju+s=tx((>lE-FkF%1ZAp$Oxc`7@y3XZJ66KUAJ^zkL zlcLKkGk)$B`|g5#p%KMNp*7p-J_?|D_t#!mp$+<5&3%QI@t39-Lta zv{q<6j=Nw%oA0e2c50H7?G)gM%dC>fn-vg@sdjjD`CEQ$XQzds=-ftEG;E z3jk09Z~;UB0Du*cqnN!*MFIfGSpWcp*p`0|oPQtKyNe?X<_K~9+o_F>!K@tHy^Xb` z`(NXKoj_QWv!H-0@P(q1ygpbDXya(@fhAZ%eg1|>Ft2rEg92i)7yW-Aw4{GQTrAym zY+(>jsI~3il$G Date: Fri, 5 Aug 2016 06:50:30 +0200 Subject: [PATCH 05/16] Progress --- Moose Development/Moose/Cargo.lua | 628 ++++++------------ Moose Development/Moose/Object.lua | 17 + Moose Development/Moose/Unit.lua | 16 - .../MOOSE_Test_CARGO_Pickup.miz | Bin 186313 -> 198657 bytes 4 files changed, 205 insertions(+), 456 deletions(-) diff --git a/Moose Development/Moose/Cargo.lua b/Moose Development/Moose/Cargo.lua index 314706d99..158494e9b 100644 --- a/Moose Development/Moose/Cargo.lua +++ b/Moose Development/Moose/Cargo.lua @@ -38,12 +38,6 @@ do -- CARGO -- @field #boolean Containable This flag defines if the cargo can be contained within a DCS Unit. CARGO = { ClassName = "CARGO", - STATUS = { - NONE = 0, - LOADED = 1, - UNLOADED = 2, - LOADING = 3 - }, Type = nil, Name = nil, Weight = nil, @@ -55,191 +49,167 @@ do -- CARGO 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.CargoObjects = nil - self.CargoCarrier = nil - self.Representable = false - self.Slingloadable = false - self.Moveable = false - self.Containable = false - - self:StatusNone() + --- @type CARGO.CargoObjects + -- @map < #string, Positionable#POSITIONABLE > The alive POSITIONABLE objects representing the the cargo. - CARGOS[self.CargoName] = 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() - - return self - -end - -function CARGO:Load( CargoObject, CargoCarrier ) - self:F() - - self:StatusLoaded( CargoCarrier ) - - return self -end - - -function CARGO:UnLoad( CargoCarrier, CargoObject ) - self:F() - - self:StatusUnLoaded( CargoObject ) - - return self -end - -function CARGO:OnBoard( CargoCarrier ) - self:F() - -end - -function CARGO:OnBoarded( CargoCarrier ) - self:F() - - local OnBoarded = false - - return OnBoarded -end - - -function CARGO:IsNear( CargoCarrier, CargoObject, Radius ) - self:F() - - local Near = true - - return Near - -end - - -function CARGO:IsLoading() - self:F() - - if self:IsStatusLoading() then - return self.Object + + --- 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.CargoObjects = nil + self.CargoCarrier = nil + self.Representable = false + self.Slingloadable = false + self.Moveable = false + self.Containable = false + + local Process = 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 = 'UnBoard', from = 'UnBoarding', to = 'UnBoarding' }, + { name = 'UnBoarded', from = 'UnBoarding', to = 'UnLoaded' }, + { name = 'UnLoad', from = 'Loaded', to = 'UnLoaded' }, + }, + callbacks = { + OnBoard = self.Board, + OnBoarded = self.OnBoarded, + OnLoad = self.Load, + OnUnBoard = self.UnBoard, + OnUnBoarded = self.UnBoarded, + OnUnLoad = self.UnLoad, + OnLoaded = self.Loaded, + OnUnLoaded = self.UnLoaded, + }, + } ) + + self.CargoScheduler = SCHEDULER:New() + + CARGOS[self.CargoName] = self + + return self + end + + --- @param #CARGO self + function CARGO:NextEvent( NextEvent, ... ) + self:F( self.Name ) + self.CargoScheduler:Schedule( self.Fsm, NextEvent, arg, 1 ) -- This schedules the next event, but only if scheduling is activated. end - return nil -end - - -function CARGO:IsContained() - self:F() - - if self:IsStatusContained() then - return self.Object + --- 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 Event. + -- @param #CARGO self + -- @param StateMachine#STATEMACHINE_PROCESS FsmP + -- @param #string Event + -- @param #string From + -- @param #string To + -- @param Unit#UNIT CargoCarrier + function CARGO:Load( FsmP, Event, From, To, CargoCarrier ) + self:F() + end - return nil -end + --- UnLoad Event. + -- @param #CARGO self + -- @param StateMachine#STATEMACHINE_PROCESS FsmP + -- @param #string Event + -- @param #string From + -- @param #string To + function CARGO:UnLoad( FsmP, Event, From, To ) + self:F() + + end + + --- Board Event. + -- @param #CARGO self + -- @param StateMachine#STATEMACHINE_PROCESS FsmP + -- @param #string Event + -- @param #string From + -- @param #string To + -- @param Unit#UNIT CargoCarrier + function CARGO:Board( FsmP, Event, From, To, CargoCarrier ) + self:F() + + end + + --- Boarded Event. + -- @param #CARGO self + -- @param StateMachine#STATEMACHINE_PROCESS FsmP + -- @param #string Event + -- @param #string From + -- @param #string To + -- @param Unit#UNIT CargoCarrier + function CARGO:Boarded( FsmP, Event, From, To, CargoCarrier ) + self:F() + + end -function CARGO:IsLandingRequired() - self:F() - return true -end - -function CARGO:IsSlingLoad() - self:F() - return false -end + function CARGO:IsNear( CargoCarrier, CargoObject, Radius ) + self:F() + + local Near = true + + return Near + + end -function CARGO:StatusNone() - self:F() + --- Loaded State. + -- @param #CARGO self + -- @param StateMachine#STATEMACHINE_PROCESS FsmP + -- @param #string Event + -- @param #string From + -- @param #string To + -- @param Unit#UNIT CargoCarrier + function CARGO:Loaded( FsmP, Event, From, To, CargoCarrier ) + self:F() + + self.CargoCarrier = CargoCarrier + self:T( "Cargo " .. self.Name .. " loaded in " .. self.CargoCarrier:GetName() ) + end - self.CargoClient = nil - self.CargoStatus = CARGO.STATUS.NONE - - return self -end - -function CARGO:StatusLoading( Carrier ) - self:F() - - self.CargoClient = Carrier - 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 + --- UnLoaded State. + -- @param #CARGO self + -- @param StateMachine#STATEMACHINE_PROCESS FsmP + -- @param #string Event + -- @param #string From + -- @param #string To + function CARGO:StatusUnLoaded( FsmP, Event, From, To ) + self:F() + + self:T( "Cargo " .. self.Name .. " unloaded from " .. self.CargoCarrier:GetName() ) + self.CargoCarrier = nil + end end @@ -270,14 +240,16 @@ do -- CARGO_REPRESENTABLE return self end - --- Onboard representable Cargo to a Carrier. + --- Board Event. -- @param #CARGO_REPRESENTABLE self - -- @param Unit#UNIT Carrier - function CARGO_REPRESENTABLE:Onboard( Carrier ) + -- @param StateMachine#STATEMACHINE_PROCESS FsmP + -- @param #string Event + -- @param #string From + -- @param #string To + -- @param Unit#UNIT CargoCarrier + function CARGO_REPRESENTABLE:Onboard( FsmP, Event, From, To, CargoCarrier ) self:F() - local OnBoardScheduler = SCHEDULER:New( self, self.ExecuteOnboarding, 1, 1, 0, 30 ) - self.CargoInAir = self.CargoObject:InAir() self:T( self.CargoInAir ) @@ -298,26 +270,40 @@ do -- CARGO_REPRESENTABLE local TaskRoute = self.CargoObject:TaskRoute( Points ) self.CargoObject:SetTask( TaskRoute, 4 ) end - - self:StatusLoading( Carrier ) - end - - --- Can the Cargo Onboard to the Carrier? - -- @param #CARGO_REPRESENTABLE self - -- @return #boolean true if Cargo is near enough to be Onboarded. - function CARGO_REPRESENTABLE:CanOnboard( Carrier ) - return self:IsNear( Carrier ) - end - --- Execute the Cargo Onboarding to the Carrier. - -- @param #CARGO_REPRESENTABLE self - function CARGO_REPRESENTABLE:ExecuteOnboarding( Carrier ) - if self:CanOnboard( Carrier ) then - self.CargoCarrier = Carrier - self.CargoObject:Destroy() - return false + self:NextEvent( FsmP.Boarded, CargoCarrier ) + + end + + --- Boarded Event. + -- @param #CARGO self + -- @param StateMachine#STATEMACHINE_PROCESS FsmP + -- @param #string Event + -- @param #string From + -- @param #string To + -- @param Unit#UNIT CargoCarrier + function CARGO:Boarded( FsmP, Event, From, To, CargoCarrier ) + self:F() + + if self:IsNear( CargoCarrier ) then + self:NextEvent( FsmP.Boarded, CargoCarrier ) + else + self:NextEvent( FsmP.Load, CargoCarrier ) end - return true + end + + --- Load Event. + -- @param #CARGO self + -- @param StateMachine#STATEMACHINE_PROCESS FsmP + -- @param #string Event + -- @param #string From + -- @param #string To + -- @param Unit#UNIT CargoCarrier + function CARGO:Load( FsmP, Event, From, To, CargoCarrier ) + self:F() + + self.CargoCarrier = CargoCarrier + self.CargoObject:Destroy() end end @@ -349,244 +335,6 @@ do -- CARGO_UNIT end -do -- CARGO_GROUP - - --- @type CARGO_GROUP - CARGO_GROUP = { - ClassName = "CARGO_GROUP" - } - ---- CARGO_GROUP Constructor. --- @param #CARGO_GROUP self --- @param Mission#MISSION Mission --- @param Group#GROUP CargoGroup --- @param #string Type --- @param #string Name --- @param #number Weight --- @param #number ReportRadius (optional) --- @param #number NearRadius (optional) --- @return #CARGO_GROUP -function CARGO_GROUP:New( Mission, CargoGroup, Type, Name, Weight, ReportRadius, NearRadius ) - local self = BASE:Inherit( self, CARGO:New( Mission, CargoGroup, Type, Name, Weight, ReportRadius, NearRadius ) ) -- #CARGO - self:F( { Type, Name, Weight, ReportRadius, NearRadius } ) - - 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 - -end CARGO_PACKAGE = { ClassName = "CARGO_PACKAGE" diff --git a/Moose Development/Moose/Object.lua b/Moose Development/Moose/Object.lua index 1a5ac146c..eb546fa22 100644 --- a/Moose Development/Moose/Object.lua +++ b/Moose Development/Moose/Object.lua @@ -68,5 +68,22 @@ function OBJECT:GetID() return nil end +--- Destroys the OBJECT. +-- @param #OBJECT self +-- @return #nil The DCS Unit is not existing or alive. +function UNIT:Destroy() + self:F2( self.ObjectName ) + + local DCSObject = self:GetDCSObject() + + if DCSObject then + + DCSObject:destroy() + end + + return nil +end + + diff --git a/Moose Development/Moose/Unit.lua b/Moose Development/Moose/Unit.lua index 3888939af..05cce20a2 100644 --- a/Moose Development/Moose/Unit.lua +++ b/Moose Development/Moose/Unit.lua @@ -187,22 +187,6 @@ function UNIT:IsActive() 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. diff --git a/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_Pickup/MOOSE_Test_CARGO_Pickup.miz b/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_Pickup/MOOSE_Test_CARGO_Pickup.miz index a6537ced1ff76c185a3426554bfcc02993a83389..17f82420983a9bb322041bb22b2a587057c5ff4f 100644 GIT binary patch delta 197849 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

6XWklR!u#e;61F8~m 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

O-K3H`t4s>mU}GP(+-I4r(9Va~K7sIv9wnnm9@c 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

S^S`*P3h>$-y0cS zX-7p@?vRMC+$9ZNxkD1V@?|NghG!3A&p$V}BAIOn<{wp~Fi#`0ky;~Np&K2xQ@zjJ z0}=FZXP8l~2Jd z>0|`=H@w>SvY*AY;@uh|O8RG7!O9Lhd=-)~vMc%tknY>fY=r8bR3fke+#4? zg=l5|REIs%%(FX6H0*$xQCViEsBzhPdGhAr>C-oG06vWJkIZsWnm5B-8ocZXBLODB zT?4(BLDr;tyVe_f_lSmm|u`rxu^Y;$nRZ|b6v zT~V(dFRZ2#Z5G(tgq9GyZ?MSvCBzF49wNHGccpaN`6LjvWSpGr898SRy? zpu#bhe@A@RLe^BWwW2Lr*s+cgaOqTG3h>&UHyMx`{?Rj~uV1V^egTD0N#xG$I2pt0{V1t_TC_8zZr8x z3vZh1);-8nnR)z@@X4X|Go5Y*0KA%qHeuyvrg*w)G9I0^Kk^ti+s(|Mt_VQ%)G6#$ zUgpH_r|*`4Ue>Bi|tw}LJV1cnYMWWE6c znaV!;Sb9Usla1Z3w|Rmj8N_tbpR4XxBu>!L-4s>f)k7SW6&j$dEn(SNc3glRPdc)9 z%1Q`Q%+y%`I}~p%G6Tqmq?+vQf2dlVLfr?Wi=8>hS?m|nE44akHq{XW7Im7Q+|2u* zXothu+c698jNR)~HgWWDrjg~_xS2yQm?-K(CJyvBlYS~ei@%TA#~f{6$V#Lv#%%WE zk>-rOFHBRg9)=|J=(3?!-Q>izF?3jBVf_^m*sSYK2F(f84o%s84(ujue@Awsp3X0D z9N|s{_sL< z_l{SMclL_Hwx|{S7iS485Qy;yt{sa&^lQcFyJ?4`GyunCG~u#ai*&}5G^_#}^oB{k zQO6MujtDX*K@syLRA?g&>pXOs33_sJuye3MGNN^7BNDQ=<9a30BvhwNZ_ebqYdtIJ zgN|^M&fK~YwSeAGEpkit(>{Law*YUgRx|VoRPw=uuMD6;X97qHm+NYZ76MEty<=O^943!UtlI$>O}ZN|QSg=5^JJK?)X;48-BCiJxa{ zl!lynbfJVri;zh8qYz5iu*GLA-8+*<w?;l6l+e_)%9@yvXhuf5~#7wl$W0mNm7-a5y9_rqX3$Iv1Xed^3S6)vJj z^&y06ROhWws)WOXX{rV<5GDH!Y{TQ_z3r`|=Les)^}4Iu=5h0T1Yh776o#D}9G4Rb zZbT2H6Jgo(pMS8@|K6+JdYqk(Hq^tI%C4+Avqcf0yfJ@#CAhL)=#M_M({H=swQ$Z*3XKV7oYv(QKTr zRt+k+1 z3Ne=82IW~tA9X<*1tc|_zeyM@^U2Yar>5>3w?iY`#eb`E1hru8Qt`^jRA-y(z_MTy?$b*ZT9C!k3u~|8vx!H4a}%7_~?-lS!1LG+si^5 zL|PLuXO#MFEWytryQwW^Qk6DFldaLz(}4oN=-JGzWsUazFPmB8Eq<6ie&niN8!c59 ztW|2MZhwocaUIkxR;T6iG)BVqi`X3Bmbr-fFRg$xZ;e^D)KP2gla+}o-TIGDwodl8 z<8J-HK90`rQI&{W|6x($YY(ts_YMz^_fGZ>_P3tC++E1cKkD{RLm;o}2M_31*4Xdg zcixxK2IH%uoW=b2m83&H{KH~$-`EDR+5b3qqJMJ4mZ+HrKhK37d#fDr-I_@cd#;xD zvF8evUiOZEy>&NdELn=XIS&IJetB&fe3e)E*YYBl=i{q*k@Ir3avJAB*E)?mnAP_t zeOBi-?zFzG~^rL zfq!E+jjGzQI}A1@51w%Grq8a6ck&+k=YL`-X1xW=CA0xmhzGz~F=~&Hayu zkx~s%#|ig8Ds1Nfs1=@ik&Vns9~_q_(Yf%yp|5clCo1z>8XG_Hc~jI83S|l0s*J@~ zHqf(xj47Q~y^6{S4<%mnn1_-(aNk3T*MG=*7{ynp!b3@-E-`}=gm93kSgN?qwLeID zF*?8#ES-AQN5*Cim+Ay6e}8VgL$6UKbP~m{6CgTLJVCU)I3xTp_%wF&~we3RDRJ3RSN+ z#avxS#W3eLm2-U@^;640P8#0(NY6 z{@?SV;^E$#;>*>B7uQ!9T<@v^K2uFctm4@=ldINZ zlt+kF{JpVUrQw8Ob-9Eh>VIloC9|r=Ax!U-F7K;WJ4XkvcG=HMl@AqZ$O%fU!HeCO zdxu*)P#+q+$j77W!7yF__{HAg8#X@%>F;uMtt)?)!GAO2-={jso5>@oV*o=K5iLV1JtXj^v1dc^!+|1W27id}o3d8gA4{#P z$0+uFq1##2nAANGYwg@UbxR)72nlIxLbpG35n&O`HBh-5zV(aV5))@hLv?n;T}WFi z7I2A+;<7at1{-g-L$-GFO8HlFQsJQybP!*!+hM<69?OF+u+UmW4Hn@}S1}k$g>`IX^D>XjkJ#&T4CUl9&S2Jhtbbrca`TIf zILuGA^T3Ll--j=^e%n1d{yztM`@1{dN5Q}zK;oe59wkA52Y*da7ZPhQ7+#Gg<|4|j z=hN$=%*&ph0FGZ*f(fD#uQs&gKcNAHaq(_2RzfQiRO{v;I>Q~ny|AQ54D}pTs2_{zd&w!&Q=Q^iad(l2)EgNW zDi|R0xQx4KAb&c|l~^^^)=gH)VcL&o5VfnAR%$n}>^B|_waDEgV?qlIJ`6@<%Gk${ z#jVJ!nqNZu@o_MI@2+>5Lu6)l3AchlP7JSHM2OifJjPLsht*>N)OKx!&|nZCv2d1S zh-hx+A-XXnRU9Ug4$a0tE2g_@kbtkBH_)Nj@ntuY_gnM1x9kPoVH>Prn+`** zu$GE8|}>?Wl%M{{axKf z=TZrkXT`N5fsxuEfNmcgp6ni(`XBPk(b+f#8h@5`f9EZ%zNuY;2pU!eLDkm57n4}6 zlcU$$C$Eoo?c}ALTC$+(U4U?5j>bchrQkMDcQ-ktzRSnOc2hT2IclxDRH9tLPL z)G2MfXvP&M*p=yZa*McwAaj;;K7ak#k+J@D8kVasfv;M0$34UQ>{M5NBj#(WdVii_ zTf@3pXHjM%u?pcFR0lK#wlnr(xx!iH3Gdy9+b9*e%_AbWq zA%Y2HOhH`OsZ;TdAJZ{WYcn)F34c0_c}UuQ<4M=-TwvwYa_=fvgZD2v#b6ITL#@b2 z7ocC#l15N4$HrGsF*e{!(Xq6-Q_NJ#nZKC4IFF9)Qi#u5JLs&1RIA<|qm6#lDjZWd zt@-~`w`zdZ$$FcSSBU@SL7%62G<5J2u`0Fn`>6s;SVi zTO`*{qA4s-smzlacXuh|I@IaeqiGtPg|Uw4E!Rbc^A^kEM?GndNFQ)@GGO!+rY%(B!a-ikt&<@!QEpaW#elW#TCjX>a325 zh|v;LL?jJ?C^C_$MHBp;#D5Zs*O7_H9zj-72SrFVb86~K1hKW__))b0x5tjuOeAgu zqxUf*tguPL7HHx0fLLKAONkjFtk{Lg>QQG^*u8r>>q5Y+EYoA~-WrLIULXvL^rRr^ zv4S!b6gE0~UI6v8BM7jv?$lOzqr=X`B`v=tsrkJ2_LqPEy0@<$bAR>aD^`4=yH_vr zLQA`iaXo0Du%<0n5?@tgEXW={j?G3oZw)JNrKVbFmFOXs44U#!*Jw_;`(InzCog}q z{%MqudOpo5w1@raXszfa2n-_2Y#nTeM(l)eKFY^Kqw1oknd9rh$H_LT^ltWcdi?bJUqmwZef~GvpR6{QIo7wqqRGYpY;HZ4jtVZzwy zY&lC-rBT*Qb$>-+b9}C*$KiB*YGXbc^Eey5lIXr*MEYEPliNA7eE#KT`8+kt%Gv2C z92FA`jguNKi5tk9+fqO%pT#(wQbsktme7ttXU;C|eJfsgB2UEn5vkS9d03@eP!n2ONEqCORNX^CtELN$$(ME)i3xuh5Ce+XtDn)FQLXh?*C>jo>@q z;63Jozk;__p1Qkc*xK4wx!?QLm59!tt`r-0j5Dx6?!;k@g z8^Z!I1b^{dLA&RI!zb$2z{+4)?cq2#p1kbZz;Nzn?^JDFdR z-rz(?B^*;Ra#T3LqW>V-=*E#1?S&H{Jcal6p?_A~3yB7Oqs7<^#@Gsupo_Aw6^vRc z-40R}scr|Unp8K2RY4V-!>FQ)EubW+BHrN+7MAZv?5x&}e1|BUq)F~%oaG`?gY-Y0 z)i9Zi9js%Q7sc%mv)Tfw-h=kgs&``nc+q{ShPAijKtdX{H;k{BtyIBXbNAg`o z`ye&hnp$70^h2=&tGn8KCvMv(gA!dfQ-4XIleA;+IC26J3S273_LGkeX?Ip1y z?sV}iRzUq1$Vw$M+tnqeGFhaW<)+d}jx570_)?vPNhX-ImBsUuTiq6P-NaT#KNEYP za>sM~H$?%Z!Rw~3FS7ZWKa=_OBKg1Tj~2hK>0L4EzB@l;p?z=VS)=NaQ$_`;Lw`o4 zvE!9!xBSzk(~AHAljgAcgo$&bThtsCUCnw!NAh}kX(1C4##>`I80-XaRLJHzDd(9_Qf=L|AsFiH&G>QslK#igVq{K6lv{w>RX4vAqDNiw@n;L@He`>Q z)YCmDpr38mg7R^TPR|1urF}_T>>9$rE0CZJTpA+{O-Vv~q?GAzjDO&zfsx{J zxjcrH$+m=#w()*GpG`4nezW%byRT1I!445|Gv7L{>J946btR$aZ* z^Pvgs(b!sHUELJ7bW$ktFQZ>CFw`OiPGDy_&abZ1#E{5Fs&EqtvG%DLd$~tWXQ~eU zj^eQN)9X?_@A>@?{wh;~DZ~%Tq-^fpJL9(i+vC>8~aYs96*r6Sq zyT;%f2)8$T4vN+OLMttt6PL2y+8%=Zj$-TmCd|YxU74!32%obD!y#p^L{krl>~;om z7pD1DfsxZWg&}yk0>eZr2r(Llc{!UFkgK%p?+MiBSKX9s*rj&8Mg z#&gx~-5KStOKjfV?v8M?{c5|@d}peRiW5752ER5yjQrrm8!}N^On?|g`A6g zIMHzWEu}FI7t#&J)dN2dj2mgc45k-7v>J4gO?~zQg4w)W#ZfVzjV1}@QC+*V@(~Q{nv%q(9G_k05V1vz zKr}>-nt+C2JusVgdR4#Y5Kc{X;OwMdzbpA0f6@ZWDuxraRD>kB`n3|J0Mpf8w@ z6yy*0&0z%%t1VhktJ~rQOM>A(-VzP$rgLnm)jBc#cKg2da_^U2weQ4q_#4lx2>Km- zL))L=kg9jyo6my#u36nX+;|;C?2hjNe_o4^mj#AM=OB3EWgxg3M2dBtHv1dFiH`%} zLHSkg2^nngOJD~_l&cL}bQ=sIk8vLj-2CaL)%PMs)nNlq@3#1$pPKFv6Lb*4${V7A zV~q@qrG_J2v-Srcu5A!P^Vx<)BsY?$5GF5UfuigW-?2e7;Die&(9M$y=d2jKe;qoC zc`%dKFO){rFV*Xcr|&vYKw`K*&4>3hA3m168_)Cml;{>>@pW~Vt5}MPo%@bZpo1i# z0VLF(2vciYsm=d{rV=9ys*C7D#tXdj)G_!pD|YHkMVfl!8t7^C6{xoi3LR_K)+Tb% z?IS%@98HEj{KH-J2r&0WgFD3qf7H#B3)o;br#u*#mO&r&&sZT*o?nBvuI30`NY}@+DQ*VxH<)2cL70v6?47#lFu?@$v6g>=7?^MjZ~BW30n6{x z=3$TcL3)TEAMdJT%_*ud+}kbsIlceMo$#kyg1bR*k8TO&cXv=X5?svef64iCt?mC} zhlB!h-slrzFOAHU03N#s<_rqnj?{^vzY=GLA%q7;D8@``4+@XcpF=XPt_3SyKdPwE z>6wM*l7xnBPCNW-Z;0Vi#Slir_<6Tgf)fv2{_nh0XJ+so*o1OE&Hd8~D_Okz-?sn@ z2h_7ay%iN5`D@7oasZ*He}M=-oB2Iq1&kjXFQN#y@iHBxHUX3Z&T4!rFN?XN>X3$J zlFcWO3#o+o1?Y4fPQ;*woA$-&Ce{kBzAo;Gvx0J4=%RCq$#;buI>N?O8iMHFivjsd z_Goq0Y&{;M`=x$g`Edk<1~@CO)ooOomkb%y*McS9DD%P92)$Q1jI{RbYYu z(i+mZ_y~{$c2@&pR8SPiAB#be)i=rZtMLNR&9ALB-rtackzj6#d&#kVGsoEf%j*zOC~bqf4W;wFe%*iBAkNP>0+#k(|qC!L(WH(gBprKVrC2?CaP74X>pmf zHlxn*?n$GBwa5AFo~rxyF0$i@RJ4(9I5HFs_smU2hdPR(j>LXejK?{N;KjK~{X-c; zaiS)lrOBEC`Usg2Mb)C*EvWc-@IVJf8jWurJjn1_66>+ne}Ho1piT$g!ZI_Op=#T~ zLUr$y9o3eVI9G$wI4eh2^Ra2gnLsCWS$!Pb^s@0_dO^=?rOw2SiC;fnj?@N%MaMhE z&%D=qA`9;dBFw940y_ky7Cx-68rxt#p265?Dtd%4KtT9&FpYWq$fQnXNIL(F2Ls;$ zqag5sP2r+Kf3;!h=dJ+{4>eS_6w+OpXc%e>8f5BqbK4vjY9#OT8y`Qw-pEC&zUuum zo=NZf5f0}wDyV7)X!22_1yqoKlMvF>)hH5Ob{)(=vYu_0p~YgoDu$zT3Jqq4W>D2; z0`k^J{n*pUzNc${cCvWt3`1b*!BYz6+pC&zKkFXne~<@Mt@~<<9IZ=o#2In|dgRcN z1rv5P$k!Dg2;dMYozzO8m%s)20XexW&hjacVK=dtwUISnOp*&*hf`gcblZ^J8KC0c zF<7cNcZ9(xM ztheFie-81x5SX$K=Z^^tooIT5>?(-?53p&wk+nTuSf8H?nJ8llRF04boef1&LJv3k z+NPDgEPNtFm1xXvJ;<4xqjB8$#wMvYnPy9j5d`76RL^7mR7)ulr>LW!P6s!Dg|>uH zRFB`#x}mp5&cZ&CIKx(yXH%dcqd~L942XmWe+6^@TH48JU@n3PmEQQCc~Hp6ly2d7 zH%gK2OIsdwkI+ueFkO?|x#A1U*(|OM%8aCrLRB>s@ol#J0N> z+pJHxW+!Y9@WhT);0~VIo%i^Be>zLZe|%`6esif0?Y$S|J`}s(G&LbT`;tZ@mhH}? z(3Kr+7Z3|_rY^U=2_TT_crUtBiy2q}Jt;BS)|u%j6ZrbMl?9e1!T27odv7q!Lq#_3 ze2UlD$=8_Pelfe_xdA1+n~6oz0zZ*L24(uZ6B(nOkRb*V${3M!dCLOq9qXWZD=NdEFym({HEfEJCh|z9|-iD6h810 ztiaZWdDpE!c2e-LnEONS_`VtQ%%wf{)lTEyZ7EL@2lpnmn20#YZ>esIf950-`n^=3 z;6ff|t#Pi(1+6K{`ICwv+%0EQhhYm(BYa)@lN9{O?E9M#{hXnEVd0OXTir_h`;!*` z*yw-h0wBXph=80Iv0exi6=Pzc(~Co-{uBy=RM`;)IiKJn!XPzmDGu^PNYoA*~OE;=111jgolSt`_Ec4@(}#bQuG%c1)x@n2?JcaL7NyN2_&j&r{MOXns#8?v@N+{Y@R`@ieRi zlczI+*K>KYW{k~qJ93Ed&mo`3wM)yq==QBG_{dmQ!)wLr={ecBgb8L(SESMVbz8D~ zdZsacPnOj)d^VrEe>q5+7+kE|uqV2DuQ^rynp}oq_E(eoQe>uxGpdtn$_Tj%$r6BQ{y$idf_YKrt(GaSZ40srFbV!#`0S1vd$vIDLd! zfWj-s4Txce62@D1*L_|NE^;O*+Lqs&4QFU2x0;=&2bv!lkF`cvwNN9;r>MV#Dd}tw zCK-fC0;>n_BuqEdNG4=AeQlb-V6?MU5q`ocFnM`Jf5zs$$)%#f+NFUeZbH$i#Zkbq zJ=fEr)NE&N2#vn!V$<{{0$cnBc&R-GRF%h%Y*E1wRiq=d2Ce3`pEEOX|yT#x3ZLp&>(s2oi z_>4)Ee|}G(r0EX{wF$2|pA7r4oqh{L?FwLSI$rwz44UgzQwRCfR(~UwK~(laAU)6d z7C-g_AvZW%eiRooc1ZY~&j5p0L|l{=Klc&RIhoRF2zwLV8a_gvh}9EZ^MT#3Y(R5D zoVw-aQK{WKDzDgB;)dO%&*2KGsmi_WIRQIOe`uT3=Hn5{-m3p{v(IkIY&=uy2Jpjm z+k*aE4Ewm5&8Sp;P-OOlJb+j))>gN&k^9%IS2l5DQ&nqH40FFZm6%==aJ^i`SV13m z{|iPX6_ve105JP+sfNHbl9N)%X57Mj=S3jjLq$J9$YBr5o=Y_jmO?c`e4zpR7@<+6 ze;QL##6hMU{KG*;H$d`?z41-tAdNlJjh8+n4!O?7T-o3k|K(JG4;CZouXuh4jcFeT zaa4=-$SJTz*cG~S3^IBhnmiJ!=ft%9VIca-QTUS*V-O?ISTC1E&e!jzm?+L&b`zOR zWlyG1oH4UtvOKbS^=EO7&Y}jJIFEite{$zo0wef=9fJo~2-W)iCT@Y)v21*oP3Bkc zAOj9`6+3uX&<(i?G4CKh-*$05;yL*G1SBm3j=Bw;clR6>CxVAYt~3|ih~ic-_X!lM zg4L#k#gqVrGcrZUAcvDyl{MGb)vX1y34x`e8VFVZnEnj20{-_#?ns5I((PE)e_#n* z-!v6~H2SLX>=z$#wXk!&d`ytVSbWBoiv+ilcDY}(a>K2CfSc|gqo&JjA8Ur~e478? zxe`TgOawnWB6M6w(S-&^9^AM(1XG+T>ksbuf?M#4{-oW1%xH9IE-ke1Rf8S@{^||Y=bn2WN^b&*j)IIYPM2P3&7gz)BoSDq)Sp5% z&V!Thmq&Sx@x`@9_!NF<;iVQM8dbzS4r?|I<7?(cfmcRDwf>{?8?8o| zq{KrA!+7uTU~m8A?Jv9Af9szc-r{~c`tVDBww`Upz&ag&#IQxPBFoj4G1oIV8yBS* zqJ1LhSEwc2M*aAPPKNt5eO4H$F___Y#mHSPh4P{LY`&0Zde}9@k=)cZox3(s(ySAw(o9O=8z3><_;2C8 zkI+(D^oFbd1+rBxZ>Q?BRsJ}G3!TBGK3C6UuTP` z7yOQ43A|pg0IozV?5?`aXkuPQL}FjwVkT!Cjb&U!H*Qc`%8rh;|F&5*AHTZ2t<{Ym zp(*q~cbxLqL;W5SgyT1Wd~}%e|_zlIj`YfG=E%qzSlSNAk4Y@U!?-|}Q~zK(@68KAbl^2jq8fWw_>oif?=`3D zU1UNH-khSJnq)|=BKU^5PntM=&rz8j$%I!$yz4YQe|MJjY|b2oob)0I@LH;)_)G<8n!(|=aHyhyh!XXU{8g<{=nJ+e}dtbW8Pr+y}<4rOQm#c;v`A$9S)oy zao^#p((^{zACmJ8HBWipV5#qZLknYHYUl~H$DfUdjQd$T%Q#;t8Z8Gb5G9{QkFRoi z$BOj&hh=8nXD$7J)zcN;`7c-IjmlJ+XgjW@|XMcx+^BGml<>)%B zdHiDU&{jd`XGl#J);l?Ry?yffXxEvp@v2JsRmu3_F~_P{`1m{XtkPkxW0oP}OjP?; z5s<~4t3;mq(nq4*aXCI2YFwEx}IzIE1;9Q;zD}FX#l=3Khbve1cS2;uW}IKV&*u= zgGpUpZe$(RAF!OzBmB!9cKV51FMXi=_z%(yhj2<7tWfQ~sNy;6g%z~pETg8*vgmXJ z4^{faS%5AO`BMUP&5L$~c~R7~$U9i!fNfUmH>fJ+AEL*!{d)?Z<`m1rf3P_zoU(i+ z;1n{HJ;5*bm>KLLqt_a{Ec80;qGX*tC*!C3MZLpcnCNq1WHSTl%0VTO%~nYuo5POB zkxg`yKsE=6{23q{^UH{9{I^6k*;N$POoh{Jk^ETyU4v-VWXsx4GZ&qtURoprM4Md} zWuCpC6dx!0y>e&!IQv*kf5*f2G)zMlNCx0_4pR=wS>4?N1dO}wiV>vAm#&ebURGdD z5iiq6igpPQ;#WwyC?`A8Em|N+wQMhOqQ&K>-1d!rv#Uj;rif%>ETfptNle*rD49Qt zUdnYNqk3e0+0c6vZ;N3@X8^1OOM_~}A1_B|A|VvmPVKITqn{&KS~~$~ZrP`P5~8_FntDqP0@@9axX9lG0rmhJZUXg;`+C*I;xU zBnm|U9*iSmCiAlae}v8}i>n-7WvO#Jy?4SW_5!>XS+ftJT)<#u;Og*Y{eNkA;u3aU z5oPYlr55ncZLD>-CMms7Y++oQPuSvBTK}1IsGFWfEs5UvN3yQ^W~PNsvb?XcP%=;Y zjexJlof4w8dyPQ0#n>Bd1_-FiXo^nT<+5h~Vhe_dG=2*Kmf07gt3z~Wb zekZ9x>hZ+{IL5!28o|!OPY&8S%M}$K!hHJw0I23?<>-o)>D@6J_H>M-aDRwi+o70f zN)c@^{w=GWe;b2DITumQ@fZ}}H}=Y^_3+H>1FON%NYXcB^9_u9ii8XJpTEw*ywgOQ zKMrO4Ln+gz{-Ynr)LQ(#9>%*%F!kdkwm*p`ztcbZF^n$6*FA$NYs$zuyoKQ{JyR}h zdFvjuFKkx1_<^Z9GM-(fb7)$a->!c%=C|z-=C15-f4>7vX=H)h6jfT-;I4l`8{CFh zr6jbkilQWIiPm>bU}gCR$tdn#ZbDPj-b0aEA}=ny9&@6=To5qr1xFoK~(5JD$li{V4~-mTF= zj;aP551v0Vafx{!!`>zE_9DfnJXzq``k-t|SZLi?dG^5Jrq9 zP@1|`w;y*~*$ALN8lH+u(L>76Dvqe0@Le2GfA_Mq^z(5eU@xQ=h6m`D<&{6LTS#USb z7tljP-wUWJTrX@E_lkuMye`l&+1TF;82*;TgdkiF2GTXqT)~IG+B-nTn>OAHZk3)3 zt1hDSQWv9vM*@P0I3zS)E9r=0`}F-#e@scl4MntydZE~I?Sx{#e0H>&C*4S2tHMPE zLn`j{WG&Z0#cvz;PZ6;PVI@586a#qy=M=s5#y2I{e0|eK_aC~xhb08>mPnA=dJEk1 zNl74pR~Yw5sYC<6ittB?AcJ*B(Hrho4V+P`x83zcvD@3w0Yy5WAR%ykPw-3JfAysD zi0ffE8)o<4x}D&48{0(~6Hz-kCA(|zHSjnw0W-yXO~qP&6S|h~&fTPP#qPx0q>@Db z@HM$ZUlYcqFXU=cjags7)1(Qa{qQqc+|Q(jU};eRqYt5!kU%t&Agq@O{ch@OQi*0H z-g<5(D3rJMF=>EJ*jtx7P2mugf5y>AOXG3OVYqRmF-`d|A64Khpb7jdP(;IwVF#9- z!hZoN;NRsS#9ewG)m&%Gc06iym;G=%y1m;`oT|i~jyPKM(tVC9ajVwl2nXE2F}@rniwCWLsyWG(KGpK9CM)hsc4Qj@B5Y%na&Fi@c-3%X+&?*hdaT(j&uj1>VA2wElYUE@W zXC{_!e~A4E>qFGHFAyTO&CNrWf~0!=w!G`Y?QXi;t$pjN(4n#Ke?SyVzAe{*uZv*c zsZX7K$AG`;9S3YJ$A-s#{_foAmT?m6{C9pF!7p(Hzc;^~yEuE_pvw+Dy2JM}yBb_; zHHxE@s24L;%YSE{vB&w<|B`KKfJReBj{x`{ONjSA$DHl@0T`rslOGK};(L*NM9%B?51QI&H2XC8g5&AbMXHt3SiVAlq)xv5dXO$ix=<{`b)wN#Ct~oj)4W*gX zss8JYhlQ~kn>I<5i;?llv9BV}R5Y)XTwAR!G844xYd6KNf9`V~lP< znGXDAfja$(_j`otsjc<#0L!_nE6^RmfdHb-Qc{TGsGc6s6NDzY>iSXG@VW7s5+ay@ zOkeBp9q?dTIImIJJ4|13qbNt+Sur^qfPan>vEaoRs9YUUy95KuY^+4*adYCPQ{ttU z9KpUss1>X3e^*^1Jwd3=D2l0CPgCqzd;SMN7oay58c-Zt?kC8z71{pTZ5h%jf3Z%z z4+eKC>lmGOBar?0BqLwjfh?Phm1{0A;_}p8-8ati#!0Lsd7^{p2^tZ3Bl4A94j@V7 z6g5=d(@EERJ6UbWCbJsm9-3&`w*jHpm&cLV3#5yde=FIP!wx zai7k2Vax}gom_QojVN~U_3 zvnPOqy&1bp68EC1- zu#=Z(-PQiUl*{|TBE20DHgwj6|L1#1aUCg_X-F>9E0@py-Gpc5O*V}hq7{3WFm~Mz zm0VZ?mHG)>dX5c5Lfa@x*;-3Qta>`fh=s&te~$<2VOBUc|4SVq@ai|*w)5#o)f?aF zHE;KPyqw*8#$%xU%k)ocnws8H+4QS!svbym_|8tph8%vY+wGu*|JuI1x1YO68tCBD z7mi1C zWOs|qXSu4jU2H*l21{5>&#tsxkQT#be}3WkESAvRGBis7C{C{$24rbQt!g8%B&^@X znx|r~*fI#cB5KAL-RY=p;1%Ed-2&r~E28_Vh$}U4VjI#l%u18}h{RR#wnNx$M>hk@ z!@a1r2T$~o+6Sxx+hAe2%StQRPT>iM02a|(Wdlv^3n`D?nwhrzL^(kHJIT-Bf0+%a zf05CeQT zvyM(|xw8SNu8Xd%oic*(XDor+8Md1iG*83{lJ%VsL88FVcBA-l5@^CN(U?KHa&^=o z29SyyM8y<2NQ)+4ICc&rwC;^D--SmeQ}jv~Zh7K%(=r-IN7m4hbmTWbpEH$lxEx#24VV)QDiJcr*6ZFO*Ia6N2J+IGq}N2rG$@I!UaM#Wt)?6gLvXtPHJVYxgBK zED&dh)yYnz9sU-NwNpb3>~Nf&brHtK+@bIN_@=`cwF1|jrrl)v9z@@<)n)u0QMon( zkG|OME(TBCO5Q~j9;eUvf13Y@#B*nncnnT`p;$b181@CC@iaxa9}#($9g#;6FcX4> z-wy9ADo+iwlD$KADgML!onkkH

AFh~nfVkcVG-~tOdp2=#9F0A&_&6Fh_+O^UzlWd(W`LhuvM72AZvwxo zWq1+XtsgN zSliToMO+kGf9(~tx8wEgom=dYerBqr8Ic!~;986LIxNlG!N1QKV0fEhL4;TcT9LfT zj1z^|OE2Xok#w!7bgn?bCGB|OPceBH#XK)s-b4sO-a`a)(Oby8G&#ES5SvmDA|6V` zTsat{L^-%~IQ0{F<#w8;1}Ucwj|X%+Wxoz*9Kkiwe-d13=c6tjtu~$@*nJNN++y49 z%a7}MFLM%F<~(X*?-T_oc(?YB$bdHZ6g9Fp)vK-SHHeg%J(rW6{nls-I^?#7_TQ~J zygEKZMMI%4p1NZczjJi($~B6=9t>l3!6^Q4YsWWp59$d^_lDN%di&Dm>jS-KN&9tH zA-R@Xe^RW~ox~p?!_sQgbY0)trrYNr3R+cdovx9P&C^B8O$k40qVAR5g^fCQ(a=bp zYnR$aoqtCymBWP&tkijJ8ki_+V9jil^}5+Eq@}vkt8c0<3nRAbZriA_x>H44t2-Zk zb9F9cdv#IOHCPXamateC6?=N+e4BOI%K}F0e+ChZ&H5s!z=OD_%L({hV};X;XS<%p zjj%j!yhc=d(s+H0|_xna9zVzq6%;!&&2*_)%<4}b1k@Awe|R}kNIV)U6dIl1=$)P9irXn?*?<1w2@#)3 zt_35b!$P6Vy~BgO{gb!9>~61rZoc1sQ~K~rezvZ}+6ZJ;i&5Vnaj*U0GiT$XjE}fq zD->0>O&;leS|csjsG*rzbj5_+4feiUY08DBQ&ot5i11c-!*g_D`NCG?UGkUef0Y5a zz-b6=Ui4HwqZjd6SiNZRK4JEv8wGYRQhMDqKVxYcTA98uoG)to;`do&{9@?Nt3YK@ zWA+ku^nKX9L>4G`a*Ax+ax7m0!1DDez+V3gzCZaOZg2gfA$~FQ7q7!=`$71?k?Yv@(4h~@XfS2XP+M6hWX$ay}k-g9JYvPxbU^b#aeY#`JOE;fg z7Smp~r^I-Q-JM@uUk=LA@3|H9xjuBS2BJ_LI!C$x3?DX!*}I$U`4j}bo?_RE?53Dw zo)NGW&^qtk0X$yp+$b??e{@!6iqD;2IlJ3e`)k)4=0q{WiYY%yCjTemVsGJdC1H@l zC$;F{y+SEZ^kBhrcEfg@Jy2UMcDcWwfBf8C-4NT`H=(oFSEKxDfP(?*EP{$nka9gl z%Jn2tB8ba;ba4qTwbY_D;IHY<@zs(`EfdO4cq@k&_4)efeE$wi_bm2t(VWCatc1|`Dq#%^MAR_CTp z#jHf7^sGcBIji4tc$kRSqcS^S#T89QXUxdpmk_ngOrCBXe;vSH!oMA1Zsehqsehul ziP7W&OQ0S9rjru~!06Q$gfQIkPnvhk`|*>AJ0U-)RhK_;edPy25BY^KK|CjWr`R7z z{FJvIh~80=K-8>e##PM6ftyWRodDhlvJckTP;Wa^lOSHJb`DI4+V-V{M03&g zN!efxz}T2de-Jm)bGeE%Q5;D&NO#80h%duWcqe_5w>fibc$wgR*Q0;ff(**`6+3#_ zuZDbskZ+noetrFMFdddPKz~P|zqcIj5nRQtTF>XNd{vY!Av>>6z3ir^I(D>76;@)_ z+RW;!`u38BH%c&J1+eNL1ih*L|Ffgp@*1sH>1(Soe}wpdS|q!6WqZUy2>729XVGu7 zx**#Fq;96C=>Tj%lfO-rwg3wIYQgZ|8~*oqk@;38E+d)6&Qzm`7?a{|vA^?TdNpWr zhImBoT9{=}ol#%?_(Y%QxQktX7wrZ|@~<_X#`>+Mv971F-f$Z0&NK*@-O2PBwY{&1 z;;H?Lks(1{8-GttCYk%xWJdqxrU3M3lHRiZr_`q1+`(LKn#|?5%DLQB%;hFEm*4MT zF26UK%YRqS<@buY{2rSN(e297q+vUC*4b7|OgR`# z&`a1nuv(JS|3@bZ#JtrLg(uC3LIlUw#iq2UF$7F*>~Fz|m#rMqldZ;Z-r$ZMP2c6a zMFH|B)wttf&u}zlb^C`Lq&8+Mf!`L}%#z3g2!A2VPB1~Pl?dP>rKGu50obqD`yD-2 z(>fS^z!fJj#R1g~Mi#P%UJX9PiWUY(I#TmDr{-@^^Y?ho|8{CJRS>6MeMBdysh+o* z@D!-h#GG0sNsSf4T}<-X=q#Inth)@ZXWR67#QmWyNo*wPWk<<7fFy5CS`$Wmy$)9L zpnpo^ffAF)sM|u;uK)62R=j-KwT(q(T!FXOMLC+;`Y%=egRX)Dd4~~}+;Tjw14Wh; zw;-CU+r0{J6+;rV{C9^dY1WryD#5|;dOm|y zr1b;Ux;n&D!Zf37f_Ff*tBL_rqpdTm2PJeze&2twL#DaKaJc4k8_ey%*bE3kS{yf;``@d$7 zA3b{1%h+DkiiW9{^y3wx^+~b+O2AsRU|jJSrlx~a;4R(F?*H`Y$?$&9n^e~CXZO(x z?mqmvV|D?xBLD?HYkb~PLhfun%wZ#dDP7BcWz%zQkpMUaHU>SmN7uF!6Mt}cr^Z=% zS$u@3ic;0}4}zbH9|a?Og_f_5HAbr6YMtS~Y8Rf=GqhE2D1G(mQes`h|!C-*bOrz6ffhsBYn9{ESCx~+TAp5^(>fhQ($g`YmA*uFMMfU#`0l@IzNznuVw?{q+C&7C=U(No90adNmdgAMA;&gN4Tw*P zhGbu7jr9rr;cE>_bVv2x-paPq(P*}$`Pf76KZSUc@LZhH?@5V9Xn(*k1*-+;8MoCb zi`akZe@`4r33;aLVNgTeT8ynJ0JhrMvi@Ka$RG?;Z20Bl>pUN3^xv(~w8THuz4!j= zUtMblvwc|iWpRe4v~~~CYLZ%)aQfcoel8#D3+S$XY?H;*M$@idTnfzB<9YUIC_QIH z{;N&%mfo|Cuz%4m+JE*_Klb?RRf)X>dsP&kgq3c>N^fUm)@7`ONY^haaRpr}n`?tE zZJy(m)1GZI3jAyKr2pqwjkmO=!s4DxM^8JmLHWJ|?%?#{5BdlGciI^Zv81zIT)it! zpkhxJBW9JNG5k*epjyy@Unx@Ya}W5<9NsI=Z7W;HE7yHfg?~t6C<;~!3S#&G-}|GX zJJ(np>NW|2it%q-$$#51ZT#fqU|B~e(HJv>-goQ=6R#k0z8Oo(hBkWBKzUbMminjB z7WI5Cm3=O>$t2UPl0f=zuWlFe_Jsof%0iRAniWXS2OGO@^7aSMIJ#V9T-s!ONBMBk z(W#~jjqQj?Wq;ks(AaU&v0>wdMn`tG4aP@Cpo>u5JF&cx zue5Ga#9_;M*{W`x$m06DSST9ml2(dQgqKQXQ9Zgqs=-kb_F9SxPq56 zY;ZJ*3_9XRwVw8)T2DDli3k#|tCdgHg)7l|wJXs&JAhOhWUp77?5}@MCi@ZV`V{1s z>I!&)-hX}&{8XGIc{jHlq7c5d)bwO*8lO%Fo_&ThkJHvVw@d&@gkV#jj z3Q@F*A{9~hN`IP4EE|l^!0(x;jd?Zaw+b7cx;C;=1+GP78XXzXs2^<5QS#ycBpCUu z4u52gS-|>+2A&EnWQU&!W#mJg?F7&O0N?+; z02T6iL)ZrCN6v+i#vwDGZ$X{X=o0PX7P#WVYlSG5=B3c14Q99-TZ0O2u{XE)khA%DK^(Pv`)ntR_tSJ?9_h_}y~=pi6!ZpQuU z-~fVj-ITNZ3QZ6=2nz(UQH*;Bl&=soq62H=mA#&g#?b-@W)S;MalT?DdlYPL#KFIV z@%~ZX^(b@a?bsEa`xVTA`?iZ;N zd>2U-z8aM!gdvL}T|St+j^gj}^?%^wB#sRq@~af8=7VAMSd|J>X34>g^OLC}OYtLl zIDq4vIyPH-PX}Y5&C`kla#)N>UkxS-H>YtBa6t1{gR{%gB+<#? zv^Z0EADtc*z1=E7(OOjlH@T_;YQd{2Fp^eP`9CLH+wnQ1&K2gNq-h@tv_dw zdO{htYc1y*wXY^Rvq2hn|gcA1T4eSMehU^8O8DD1D|X08V_ zMct=v`UH&VtF=aNDZcOC@!{6V_KV#k#bV$Wmai0G5e7Kc7v7&k9ST_|GY!9$LQ(j4Cx$ zED%Jxn_XT}qhCkxERdB~5KQ>&65o$hFHD$@&!(emP%-t#^FfcSDSz}NFko+qYMXI! zaX|(egKYcw7vx=fG=(#Y1C4>6=8A$2h8(yAQy|&Rnt~bHp88shq3C-4ZagY4ogSxo zIUmnVzH3?>NN9gGSET)>@5F)P#i))1Wm&IdxivD6*Y?5D-u`o`y*%Vi*@9=PF?)va zfzina^v2;5Tn;|u*?<2QxcX4(!sZHz#FgS6ahF!;+letC*nF^nqJgJpspN>FgeItjWCUvBO1BD6zDz+ifC zD?)az?SsRU-6O2}!X#-()PH_-@Oppe?d$!$6KwEHuFm*41%DlKdSSI=NVt%wb8_^0 z`{eZzjrADgzRah2RFiD7fALPSvI`J;cr}_Rfh3^>KPGn2cRG2Po{(1Wv2=1p#3FmHO0!=e zxc7}ikvQ`Frhi^B(mZuUb-aUY^kG@e%$=1)i{}gTBf+o&&UAoeN~D=E1)HXhYnPi( zr^OUl8Qcfjfvb5r16p@6`T!>8g`!#)Ax1DM>tud<2l#*g9^5#X>?r1qwPtVe5~NQD z)9vY?yrkD+f)#0ubrx>@LNB0ctG?Jav?UbFTuX0zV1IEmWs$UB^5x?Vk+$q-VHxUnajb^8^ZKP4CSnThG7U8eS-`YL3vf0-itM@(+z+nv zi8?zL2V;6ls2^y=F>wu^IPe!NGR2h7`KeWNY z@g~G(sDG0=nAGm>zkd5_@A!D{V4s%lKA+P2Vx70fV}wygTD{#qc=c*)e@CvfBA-h0iQxiDdb<> z8yH=D588sP3=Hnb%L@v2>QvGVlZCGKoVHK>l%eUykVlk*NYJo=!P^_UzhQtPr23!Lgt5ky5 z>MmW%+>W1ehF=^xtfpLKzouV>S0$FiMpo4uBkSR}pi%^&`7SFvn_|ba;S}jaTUlhQ z+%~49%g1SLE!aM<1sCWBHPv_W@~pesKhx#tfLKi552ln+(SqRpa+|})*o=HE34f%# zQ*Y*Pg4TA#zF2qa&M9c>jFjyAC8>mC>|#pK-|1ekn>~N4dfv+R_ zyFR^O2t9WxK6^X+D=2{!Dv^>8v}ymLOBLdI#KTQ4;UXqz2gNGCK#cQ0`F|(3L6uYY zsJ<~L;MYOP4$%V`Ix`5MN}b4GtciPUPT0+mfa*MWnm_2sV)9fA}Z#~~-r5gdw?sveCTyRqA zSLZ%oP3p9tPnCQ)RQDa$i?9q(KkLe3yln9EDt>NthLe*)fS7qw4VS)eWNTvd?RLi> zR;QnlRv|knW`psD517i#K*_IZ0+&-&%MOuRD1u}8!XOwc|gKF?fLwjm`N^@PkGy^=~nPb;o+m(62taw zZr$qwr_Z)q!r7ZBI+rPE-Hb7?rz_Y1j#ZLL9u1WK8V~ znbuKPbOe1_)fMwy$RKTd)ky&ND+&QZw=*(&P*;d65^|tBuuZhs?zIS|c`KDjRJG{j z;*5t@PESJc&>8Vq);+bLF{#~TG&W5Bo?_g`@IFb*{HWBg>wjlCXKWqt$SBYAARCX) zb9(I8FMpWI7py%d7>;c~0z$vCI6`6K7sL2KnLQTxlTEoTHJS02E|jeZ?aY8TT) z2p}vNCPBETUVn-q4V!Fc5!H!gqbiQ52kLJgEKe$Gu+Ftr9%es3Y7SA(vUy#HH)_Kf zky+BvJ{{4+KUNNvN6i-X4Rbg}3QSO2@)(`29#cmLnrpTOmaXE1B3!WAR~w)O9CB_d z$s4ZUaoC=9`y;~rRPjFB2U{=qPT&F8${Z+k{QRg_jem&*VBwYSg$larmYSKY85VM1 za&4-{T011K=lGp%c{HCB%j%bqNrF|^-iDGiT0z~^;c9Bvlw0XpH()6Y8TkuDiFC+Q zgI1bM|GSefq9u{o$bQ{A+E*L;FWKHXr7bPMId+Z-=fOx&@et3BwoZ=rw)b{+kyk~- zJjLA5aerWgjmy!+C0gq!LF_Rc4S=P+UAGTjADv{cwvJBrwqEY;Ae5cGW3Y@+zt~j= zVv!Md@h{o`OwfQ~29i#8&8svjH=5B62fLpd=Zb9P zt3(R(lEmUtShtoi;OlWdBk@f$tzf#{>~)@7++*7v{aaRn)xHO&l$kZ3h#4VLn9wlZ zC=NOyQ*w7OUaZ#D1L%p)dI-A5N&)B9-;n@q3vjIRWk61xPT;u(|H826Lguoj20;Zm zE`M?NIggZLu%Cq(T**#yN2g+Z@};3&JIju@YDXOU3B?L~v@ifGIX!Y7V6_}8_H0#t zZ#PHBmAFFMsHyN!qvvhbYSEUab4;n)f=XQuO4jdz^CI$$)+V1DNleF9eWCl)nm1rJ zVI+998AA2sk)%$p8Dz*eo@^ce^{wCK#(zaQf7i|KXZL&A{kQia$n0Z26g}^tTFxm! z_4K@$`hfs`U8qkGm+^`c6y>f(JzVY6(YyJ~+Bh?i?};k<6w#xC((86HvX-D#i&{!V zY$dAbn!X5|Jr$T^>NROXhgwy44rN>R;noM!JDgg7=AKXZ-=BjsS|{IW^V}9kHGgJ6 zD2!uYDCTF({0N$Tvp3flu@gkQ^XB$}S|#rr!+EAN(uNO;qGp4pAkoO-N{n$`+>|B& zoU+>6^X&DiEN0>rDgeif0h`%X)*DY&*kd-gtNeSntQ6=Ox2yEEmyO zMP-1_dK@WhN*}*v{g@>s+Q?gLM}L;&z>Yq9I=|4>yo2EY^^7ems(OZ&wm$EctQ)?= zEN+^%fJvHcCPo*xqzqpug%zc{gHc;G_u&$|Ti~J7v$%Ay{^VlT(4N@Vth6V#)vE1@ zsa^wnVo~uc*b_H91$Sz8{KL+8Av@#6pYweKWj1#c*!DN?A$CTIX!zNFrhm;w$!0Vw zwGpvcfY`gexoTG9dOCLyZZhOPf<^8pt=F2D}U}wZaFzmL0nR5P;aX418CEl zMhz--pT@HL1<$}(cy}^$>eGh5)n>mjT~cIxHHNdTo6ARD@tVBgXrea7i;pe<=UUp{ z7F^8u_SP_?rnJi$=?m+(w}0HhdVmEUaK1Qyo!T4mRt`1sWSy$!8v8AA$Cd`_ZbnGk zv`lDCALqR*jMXA=87$joc$u@Sup(aCPJ0#tF)PQ)zO>zA6U1NKB)}IJIxqH4JR{_9 z(+1hKKyI*-n@uV$`FJ+!xj7)`P%iW4SRSk;&<)N5_l-~uGqaQh^na4}_B$pQ(t!G# zBo%>ej#h9h8WM_On^75Ml5Oo-0fj{q^k@rT@dG8c8|Few)cNE?e>6ESx)|bUT!2Pk z3?^ibCYytIU?@gUc}jzJ%j6SlhmeAL@7(si=Zkp8;()0O$HPE|{AuAKTdk-_`L!qAjQ+zp4k{@qS4l zW0)zr^HaI;(@ZxHRXi%`bLqB$k4qv~ z+85OEuJ&8{{dsQgDJ+aouq;~|4Z|LiL`e>erxfj-CJ^dq1^fi2! z)_YxhHhBW5W3K15=fO!6+n^137nnF8Y7vcVn9D1z|{HeUp`ZOj2e7C8;48pJeRCan1 zrG*D(2Vd-hWfS?!nJG0n7TbIKMIUxtz%lUD9`^ zO9B+B(CTBE^xJc$^Fu53y=tWZS?cvt*$=hU-;-JjHr)VhtB%Tk zXrq2;qf%IPhx(}OuGCR+ja1{Kf4sc;gT*|z*#_2{Tqw&AdC;rfR^k@VBuk&wi=B&w!VopvN&aQNRdbHj@ zSBtEET=vh$MKSH_UjOyUpC3J1UF8lYuSS!W?f?8p?NOI{e(+}n%D_@J+QCwQ7!C{y ze&KKss=ESIt>5o#boza)rT>ObV@}tQbbrdGkJ8x`A&AkqxG-+ldCLrPoa0G>YnA;C z2E)Nb(<70|=$ssI*~=XL0iBd}2F|SNPmaCuLRiI|4r++Vt zi}HWoyfJp70v=t@@J~_phl;fX&hxjmpRRzQ{q$n(r&nt~9fR>KRP3LXA3AP?fxZIr z3{!aGuY3w)7#00Otx^}qr0CDbhaJo8NGBm~?A;nVSKcoCy8)@tpaDR%7@^PsN9|2_ zHA?VhUmIAvo89l|Ol_TB=V(4T8Gqql{EwyF>IdCQH`lg6D}yoNu=A0bbLV5wl;K8< zSKY4`m}|c3e9*wa`dANEU99&l@|h*zX{sI4o89^8hW7fc+3VFX{G?4@$Rq-SstiUL z`O7({VOqm}$G@`-Ts$wkkX_iuWO0(n5=@Ar0sH?hSNpF+DMA8GNUa6?-+vQFiZZ%l z-d9K>Tjs-lzwf3_jHP$;!^~)sdXBkfNr%#ohzebcpNKOCPLN*2G;D-fWw{ZKr=ksp ziG)Ujt71NxK{KYfI-_X1@fI~X(cSRQrKe?H9H0q??`=3g_wgM)o{( z>&5V=7r}*|x3Nq18(3SzJ46w~!Aud1(OCwMw7?0X2V;SB%X^z}m z799sA-AjILEmCDOi$9x&VGZ8+x|3$_uZe&MZ=!5NIF*E(YOF^3IXM#ip<(}sf~l^Z zC*xS42H)XDT=3pD1D=FMVs)d%V(PJS9-l4>*n#92Do0S=f1-C8X8~3?Mu_b)$3t7x zLJHA78s(x6j<@GQxPObf_9k6*B8E!XzDiJuaMF6G6-hDzhDnFb|5M+&_*GjaY}8Hn9`XqD|xZ zc*xyyY)$6l;ZE5EcSIZFOQ^(2%Hd0wV1X&5jd9VMt`eqPv04|?5@W2LfczM z&kwTg@t`ch?g>~m5{#%sXMmBJV08ZK!4#;4f}CMgoH85c=c9>|xI|)Wtz+FCj%Ec1 zpTYRpD5yYgqEhjRx*>!?B8cJyWb}{;`<8 z&$_P;p5bob?0=3;o(}%-_P-DIcj4nd|I-St2_VX5$Cg1X#g%P^N5y{^libin^Yukh zx)c*ij`yDLZ@px&D<{7l>MmARj$a-86@O)WJL;duii_O+uM>FGD6fk5c_;ha!+OtN zZXJnw>$cwcSS>{dvRbdKQ047|mj_2scLke1Kib{hcYnJ$+I#+d_ej@M9R0I`eN@yOzlY%aKB!vqay)+ zgbxPf!O_DO<*2OL0=$%DwzsD8L*H)+yec%2~fyRF30&!{WkD^+kN@+;8zRr z&q;{5$$td#H?Z#fC}G~<7mWhyKkENKo5MQI6-_N!+xojgTF$<62Iz`e<==rAIUeU{ z5cgIcyJ3DkE^f3I07N@%rs)w-bqF>ODwFg17?ae)grVtZG8_S;oGF4Z!; zJ^>$a z94Ii5ssI#@RlcpaKrFS$nokVSiw|NNpWr~i|HGA*K?%Nk;EM;MO)o3)Xd#6(srgY`Dk2D82ww~A+jCaOtM+75 z=*jof&}Dlij$VB~AMpB<6|(wBCdg1~ZKGJAB73M6iy3{p2pGe!d@sHjd@$7&vcRs+ z%YC>i1jekaxTBT7@MU_9UVnK-r^dD^Qf7labw$yybf3ZZ_ZO=j>mZ2(uuC$V&Iuzm zO6h+EhOv50H!>6P7Ja|qrQG^37>&XGsNXS=P@F?#+TbQ#Iq_b${-_s202ucM`)V1c z2SO{M@#jiH(ID?WgxRe`TSzNxutU%+DwX@o%TYnmNK|b_)2KfI;m;db^*u7refepr<5af2K}kZ13g{Ok$$yyCa$-~|&2K+ywtsXEMs5){JiaUPx)JyVETq|0fns*2vydxZ1=jC5+bX%@+C%&{ zR(Itk)m1y6A)8mKUcnS&Stt<40B1gR3z<1s9Bls*cE4`x+YDE1durDmTJ7KXrjf9C zj8RBbK^@BjrMKOunLbgh-axkj&M(>mC)-Em8I9kNHXa8rZGRRM*W^zjJ2hosL96xE zrw-aUBYZ_>ZY<)errw@;Kgx$+edvDwpm%U7R1y%l|xbN@V_Ick!|IxDM( z%xVKw)_Nq?lnj z5+9KgDp@FKU=?w;BlwOV5+qIa^|BZMbfc~fdSF6kRFT|39P{9`5vY^`xo$N1aBtFP zF!}H<#`1asuyI7F>6XGt?A@)$nfJIG8*w2Ge1aJx6s2?fNg59)STNe;Iy0 zY=yL@l{*FQ(3GY+mD^Lf9lRN$YO_sI*hrH&%Kz_tG^PC%y=WLT_7sd`P@Ljed0 zlR`s}3WXe26aF;bFY0jyyT<{d;)s2U*OYvUap=-}(Xz-pe#xxwb z!5D}Ax>3}&X|h{SJ|EuQ+(D3Oe>HV70{Q|om!xb-$fud5^hjc-sNxs|BwzO%#rotq zbuYI?J29wS-%o5++jIj-@mSiBspKDs z#3%XH^>{GT73gxwR%)5fmP~9*#Xf3C>h(z#4NOv6AkI2dZiDe@i;wopw34~v zd((wVjKme&D;5lS^3;M+e+75hJfS zfgN_+Zob(b0WZan2OXy>w%SttM^x;!Tdu1?5C}Mcn$w2}v=g;ef1`<5L)1fnGsC;V z*?V$tpbJJV_C6WkraG)XjrK~D*cG3e6#76*Xe${loTH@!5*pF(*(D@jccaMDx*2Wd zYucH~_3bI+`_a1uh``!>yS0PqWq*D|!D$|6t59w&nZypohB=zJ_TgwLT+C(uyYHvdJf2}T@9$pHu(bKjEDBxB+Rm0Q40DE(W7XUiSCO!@+cRaE?{>CP(TQq+iWnMj`ArPe=S)Un3EptE3md-^;Pj9 zZ&95mR?$K>YQOnzYpKeoyZu6{@kLFZv~Cm($#%Cl*~0iZw(bbN$ciqxpayITuI;_A z3U+h5zzMRR`;6Af7=82oJfho~Db_$iV^H1vqVM|Z6ktSNjKt ztwU5IpN`?Qf4B~-mHA+`FC2pXdEtr85c$yRnPUOK+Lb0@HWe8QIx-9PCc z>_0u&I@&qj+u3DV-x{T-z($(B+LY+w%l~kJ z<$w5hf4clnx3>R!o7IQ*sm1>kF8-%V-2W6^{7-+kxc}_vVEyu*71aVoMOat=6TB%%eT-OLsbh(Z ze_pn_pClP1>`1Ht&ow-@Ck1JFtmC!6hNSVyR)a&^z)V8FHXSO)V(a;C`n`!OBww7k zyxp6V#=Gp>cwqA0TVjvSj={?<-hz_rSM?smOW}@E3GJwOmnLiKvN;2bwb^B%km=%b zeB+-JS8X19+IAG^OeTY-T9FlyQK}?8f5RQ!rYuhQOIJR!`!3Xub~EV0@uXf6=8(5| zQLBB+DyEf&RGvz*`MZsNXbRs;{I4zFz^Y!HT09Cc1e~E>=kRU7(?siwC^&MfHSR*m zbF_4ovUp;wJ}&P3PQpp`=^v|Iwi+nv17DWPK8Miswa;)(S?;6ez!swRe=d~L zdLw?QrCW5c^>J|1Sd$&MYEMvbuU+u26l(^ZYu+XV0mCapvv$^pm)*2J+_XM@{KlD} zY2j#lr4oE`P4AuPd0}Wv(ecXh-A2F57gtiZ+gP*fJnXj8>8?nU)<&P(Mw{DOm+J$E zlNRZKKdVk2L7OG2V3w_bxz*rcf0_Y6d-u0gB(JpHa;rpcqe5=2KyJ6^5C7rze5LKC z3-xvDJvXZBHj3-kYU}8he~NGUr+>&}-fF|8%DnZCTXlIGWqE5=dAlut_+!f(tLcC2 z_;-DmBLio4&w00q1Ql%lh~PypWnSIBAGCcI@Fd0MJPh1ccwdgp9jh5gf8UPqZ7e`B z$g4Df3}$0obHK7N+a0Lt)^Z1ug=%H_p$O+YvJaO)>z9?rjl5Fe7mb{{z*<7!$w?($3jw!a5P1@G@YX9~R^#TfL zbd!|j$IZ^RT`fHY>m|(14tnhFc+eCe+X?4$+8otQ~26% z1X`Z&lBZznh$3Xq&AiM&_^tAF@e~^nF*BA`BZglRoBP|_j-S{U8R`7Rv4F^36RG3WENQ0yDilU{N zA8<4#zfT?}-zOVe{o2=_>hZ?!@t3>zSoi9DW~Rbd+9^xLgSGrvUTwIo9QW#*ex~Qse{W;s^=tJoyu!F@Gxxk; z*W8DFFXFN5-Yl(<5w3z57dZxO2O|}Z{?D#Jb4<2+HovGmR0gkg%#*?|ze~3KiucN< zw0>Hg0d1~~wO1WU!qs_==O0BimBQ*WyUyQ0Ghl4GT+@68dgd^bm(t(OE=FE^~an!~%-Lf(af-ytBFg z(rh%+B-l+N{JS4ul|un2;qnVytv=O| zYNhMXhMfDHiHcJQlY=W~us4@vh}`7zi){6hA3t(j><3)wmTL8r{KG7V1nWP3{3)yQ z2XINb#a273{)4R~eD~m{n&nq;nvRMJGQdhn<#YjNbejnc)8CjId8&J%TvpX(`FTa-lv#M9v-MI^Q1bo7y9rXNX_Z}H8)gu6584MtA*6_r6l*rg4JYk!E<}DR z*emKsqdGOnw(Fq6Y;nS!iu1fL_@|D9Ato#0EIYN;OwA4QHcgJoSx$+>tPhpIQc_ZP z@Oq}6`extC1Ta1r2Y?pvoW7MVwH>BC+!(9H>K zf#tMZ4I3?JsYtnm34yRQF%r0BVQavZc;w%vu7xAR=0;LSwF4CrWnyb-Xe!wzUK%;B zO5d_MSimKZ#~>!{8Je7yV+jd&8g6cCf@y{MJAIMOY`5}1{YKhFlq6haL{L-0z^I&> ze<)S)XT0RXhg+5G&dZryHN54RsfT6<)oIVa*rE9kJG%m&LM9V{5dSH3zvh7vm`a8Wa6Gq4eljx z)eQb-9`RiB2x&T*U!CPsDchfOGCoYJf5m0RM3@=$bO}k?OJ+lYO2ZiE;}K{nnptGp z04^RO;~u*sXAM<{4?E&n*8)GE+o(f65K3 zgHz?N_*0e*v#wLpn%{{z6BFmPNM11Me~%B?r!uCD*yxWwRTD zmth9X%$%z0rU0^iBKN~=Yv!W?&g>n?kXCj21kDR-8h{~44?!^}$c5o5^D%N3I=^A0 zzXKb+gpjSAiQdkZ&>QRh;cC6}WYhqv|DF=oy*?Ei?T3u9U*lLyDc2jre~Yd*)~#}1 z{~6>eBRGY14Eb_8miIY4Dy`3u{XzH{%vClkX5&0fQ#Miyo5KzWzx@}a9SYOd)=%m1 z3+1=z_#QS5Z95(9FQY5jBWb60NSV$1D5#AIq<2E6udovE{vC~brR?Xg19Dvu`)pk# z)}yg9ol(4|LGhF}Ah!%qe?-dcF{*eIyJPvn3+Fb}uL803x(CFmyzITrDSPEawr6=D zT~NaZWb~jS9XWXT4Bk`OB$qfc2P^_%QWd!c>AMA#{rC~?6D*|5(dA)Cy7Lhwm%s8n zWFQce>0LgByv`y{G*#ZB&V8?snwwlll{3@2Z?o^)Q%fBsSoZnb{_e~M9#>B-uHPqz^NiJ6n_ zcp?A$OtcU8`#9-;Nu@vwk)6;J(gM2wx)6Ok2n*nG&_*a8N|Mzn9_G@!!!f@+sq?K- z>f>9rzOkM&Zss%4v!a@q@38blHJ|2kqpW5|<^zAwYB(*f@xGQ%E=*ms8~l0QrpbTr zjka*bzo9L-e+sPM^5$GPkJz=xweh!zcL54Z_#WhajylYL^}9>c{Jz!ui+gSPd}nwt z&X;&Ls6W#nEyEKHZb38df^+#RfbbPj5&+=9i)>*ox#PjRv`pG)?1#kczs3LS%Y&Ed zK|Xwv$6_e(s^dHS#>Se`Gmsf`pFZpDEB4JB8@Hn1e+)5$ozi-pN* z)nIpT*V>_x$&G2)L!j5-jn|`PG_%zo_On?A-Cbt}Sg||wslS(uO6(20v!*ES&Cc(l z)_rQNJX}xe`At`$mnVKc;!_^08+5LG>;@plf6m|af1jXbV5HhI$8n^YK9vdb#%Z#F zCDvl9wMi<}lisuZ94dFCK*E#X@7m!=HtJ7;!4lYE{9dT7^6%-N)Tc;yeZ?bw%R z0+cVdK91~xhH^Nxz^dVwqUIrzsOTvq$BTjH}eg?9!lS15Q?i+USXs0 zi(YJ}Ev2GWkAT61F$NH6Z@e@vB=$@>Jf?T(us21M}jAvXEcXT$DOmJ+cY z_z$)Rqq|TWi>RHb_oDvu%k9ghHfH0_R4_Lkk!xCNm8sXH);xK`WBltppXYVU@w#z2 zUV%guG!-c;WlDYez3oIUE}Q8iCsl8NXNs7NqqO=ZAyA;qlRJe~K2^ z%rXY20E?d-8=x*HaKoEonoJ|ZPf+g8Mpwn8sAl*bwj1US!^7Vk&}57{xshvYV%SG< zF=2_^8gS!?5%cY#GvC(#6Z{gOfg$%l{f0i{{V+*9Lv1VpZ@eUVK7d5x9}|3C?;KGe zjnl*D&~R%rxy4G4PXN6~dw;{Ke>Pcpa(uLh^~|N_;Kjl2srh?ji`&5eWxS&j*MLUE zKRLDb`Z|94IV)yt^R-0-PV@hq=ab=${bDy8`)})y>bAJ`NEZyyVwf+(-d8c~(%&B& z6kR%tuh79CkwzEWyBiDk7Nv%wQ4T)j!}-kcc&3-WayY8qdY;R^pV-2jf6BUl3~Jso zLcA?@oV5Zck;!8|Yz^LP*!NE-hX;>io&{Kd?xQ}v{xmJ}b49R7duz58l|p~%pPrTFe>l%3aTG14-++2G`tng1=F(fH7ll$b4T9ax^<`ac2We1{ z_)8HGNZtHY({9qRKL`O!Y?1&2OK&cDDzFab+KH4{q7RbXZ`xqSYpbA0 zns13J--~{PlQjQ2Ic++ZPEKRzk{NqC3-`G@U%1$wF8Et*bNzYYeVo5Hf3Ph@4FvLbAX!zKUS$N7p=kCn*Fq`z0LUR5W6;5^1J{0x#JCEL9~xtP)YklKZYthZC&w>OTl5WB924#^ zxTX{C9*NRH=>V{U)T$v;Qa5-qAoUX6sW|dy-=m)28AY(Pkw@Yt0P0dlKI0UoG_Bw8 z2(@3}h8IKJf7f9r)DNzzcy^gpemuT`@C@LO=x=MEb;!Buvm*5qsb)!51a;jG4 zg4NZ(V*+2Yyb1>dnKsF0B=EZMvia<%lL5NOQ>dkn$uAVfE@jx^9Ctmr;t}Epyiz~Mli(2!oF;Bzy$$<7X#5x9&K)_)E-G2K+binp_T!;UF3Mx(*5gH z#}xI3(1AxkVSfOd?A}`ORgapvR^|IUmB;(q%3N=g z4$Bf^$!B#xEDwiYTnrFgtt1E}?69}sXsM}$IzQ(Wz<(QJLBE5?v{{XlrJa^DhYy9u zbEX64<#!vwDWPnXgM_x5q;+cCZH5a!ZGUiTurzQ3;}^&JU4vL~W5py@Y6qZQfQe$Zx*P#&05qqcQa_atO4{jhUkFZZ zJT-00UpZR5hEhdL{xtX~)DrXt@8vb<>^OC}EKIUs3J$l1HeKRcY~{l*0hl8HcDl70 za5Qq9L$AYNtvXca|Eu-8>X5ub`Xr>~l8snjVSkIKV0S=0~5(3e6P}H~>ROsn$-kDsHERp9X-0~25aB<@z+c=-6Zyv_?t;ek6vkwUuj7HPVN4kuA`_s-c zW8Mu#Ht4vJ>OC)c`zoIpd#- z!><&DQ8HakCMG%!5(wN~^o>hyEc0sOb%}VaU<*KGlbfq@n#Yv!{luX7EPorm#W>Qi zmlIAh2wZlG$mXjV&`OnZYRP*OhaURk&_m5z?@ttJ=Ay?j&Rkgs_E zGV)OKF-@XOb5y@&1SZ)v@8Ms#CwRkT#WYQxg!|-~sNdouz7iNVwt~QDpI5B<6fDv< zj!9pC-bo%tM6W;JQS|B@0)P9@uPXgu7{fe{3I3z_;S5snWz(BvTwE2i>VB7nLLq0x zESXPc#n`3cG+pFFa1p{RpyYi!nWqcI5uHj#9cgmRh0>#EM(JmX3E#nL<}Y|yy*z5S zilp+Gp!sqF%GwrDlPg4JO6vBjyQVt?eUX1%hMIYh*a zxkXSzpYO9A4*o#Sd-$3vG|G%>O9aYfvp0gyet5k=EL{GD424>NdXBUw0hSvl__e21 zdzM=)3Po>59>ugfM`@bLC-2f?a$Xw5#k=V#tkrWsMaz0xrQ;`=vQ)|z^Iiff;c&s{ zomge1_={JMi@$;CkbeVzVR9@5ooT}x6M2pq@r|{|(FN(?<=)=GV8Hym#kR*@VWKuX zkt+vqFu7fP!~APSW1GgV?Q-9#EuucjU{+dtE`hg8bA8w?0afaYjGi}RJVOUgDJzkU$m5Tbuh!u2; zW;v>rG~-v@_KR-QHTPNjCHGn5755wQ6TI_P(rO^I0Tyq}{|-hoaj)NR?7B>`eHwQD z!M`2sy*xcUdZM3F(24~W_G;fr>?yD-&8NO~3SN+EX@8-k8a)0SW{F*~MDKkI|h5z0g?Eqquw#%_TbZ4lI!j%!^d|s}Q^u*4>WRq#vPN{YfW#9SSecThHL%OTI-jjaU%9P(kh6|1=_tnbv-;vFFtzZoho^x z5goy_A*i!*3WO>htr0y?5_^}zel4&E7cARWF*#$;XEWY~@zU%+%pp+UW1K^FQod(l z1b+?dg-a#ihiWiWaR~p~EyA)Go^fc8k@(YDo#~i=&djm-mVa3H0%bTKU>c5=Hlh}g zqIU3MD4&QVu(G9w=i$(7#H@H@qaE-+qDTO~MT;bYFK+CzyfM&?ZQbi)_a1ot_hxJl<<~eNvXAeErho}+AMXMU2F8VTf2Wnd$<S8ZU3-IDulTnpp^-`sCnz*}ce{SOB;fm+B^?~io^%@ zjeI^WZ*Vq);+GuLDXbC;G2rs5yB2mr37fcb-tY_`!289|0F|~b3eVzgZTN)BlNf}c z-Zs(|q|3tHQdoPp-FTH zhWJ=Lya+rr<0bT-*w75*u7_`_^C=dDd|zJJ{Gr}`+IB-gjI`(d*- zFaE@!^DjFU{>_v4VbhzyW*qh<0DrUyZXfKe8q|+8(2snWAAf0(x0Zo;SF*Vh%qo`= z9h8#q2!2S)BkakhWtFU@W>aR6vKV$HNTP4xNTke6sXc;{tzPou#|O6jmMZj<{KE`$ zTmSg+r>x2!U>P>pS>YcVAvI&bwL3CSQo!~03NuWSN8RkQ9C7wZV2$O}uzSN6DbWBc zVxAu#4-PoF_J77_`Ug8lQxAmmdRo52JbRG&a&BNI{zlNw%58_xB>CQ(X}ViMdactE z&)?f_Z<{`to^XbkP4faX<8@7+oYI?8Uq(6`jb4y;4|^JoFlCs0qRyb(hUTP93lK)y@l!yJsn+1OUDKhP}1bj&GBpw9u1uLeSb*^bsG(3&|4)CwsiP3t3t>Q zCa8CoK()9Gp!%sn+rFS6;X`*xWI^YqEKRycl6)A>Qz3O{`Z~(#ZiFd5gmVDSt2KfxndVlXbZK}UL%V}@!UND1zM3vVdNRMJE3@_+XQW7+0NyOgHW@gfQXQETMEVh){L zohrP1SD2l<(#zMrVhZ$={O{;)y#VwnSHq-+3~?W`6~E6a43lY&51yG>=<8W@9rrRL zvW~}#7e(s9a8Lg=!FHv%Z)*0sSpoKG%3!zu+h;I=amM^c%MMF?-d-z~>^Jr917Euc%qwT_+ zM&{PXCehE8BT{CoRySrpnNwvp!<%@Y2+0&mns3ujMuB`v?cX6C5UwNw5iLwzmVbc$ zZo3$eC}U(o|A5QPPN||vGc^kS|3EIz$r+^6@Ac0M1N5(+7jSr+)|gFP|NpV3+p0uH32&=l+z;1d8?3gXg<@PY=ya z<#Bc8Rm^7(1_!5aQ1=4A=%~TGv4&TeUCbHVLyWqVk8&@uw~~MQjhlT;#c50F4(!d7 z-sthWm5E)7))#dAsY(MPLzTpJGIzi1e5Apxdx|)m08^b6aMBqHp5*V{(to7M;If>L zAq*}=IN>b4W*dUK{|*sR=Swa-6P!=$_W@JT<} zeYo3C>`lC%q-n~ETAO4v#DBO|C)xta_-yrug0sGzYu`?y??dOAjV_(Ymmg*ix=H7V z3CgK`wK*y(c6|!BpUoEK5B8HV{vEg3xkQn;{HX%dm`^gJZf$<94$QynmeVV9W&{Gi zD?8k`$c5*YBf|=yB78=a^~eSBR2^MTeZYb_9f_=?1h)n?Z7;>-4u8ew2M4eid*3ER z+ji>tDtMKtN2-BTwT20pqjH*~bTuTMY^GSk!o4n3iE;5Fq==)uyzVjuygB&m!O`jd z;o!yYsp0u2+s6}PZ_o1VT~4=aL1RPx{X82jRdH{cRhLWE#HYk~zY2pmMekvE*WGh} z{0Qj3#f!_2A4$iuEPtoO+7F1qK`%_t5o~%ooUW^^2=wto^&ROG6BoGINc!U^@boVLJi`j(DbEBjLbr9%~hj~@wsI27=i zbia@PjbyH)F34%eS;tC=>D#sgdC~m+;N{Wbsjf>DtPp9FO@G*et}{$jwf~E3^9+ps z$4olzM%4svUqEY&xS#xS zvjy2r{J;fiyMH@MwUX@%A##N+4+Pwc?VPr4d8duJ+Sa_o=Dg$fj|DTm1u;8h$448#Ofy=Fzu=99Rap)CoM9raH_by8dGI>X55ZYTPV>G$_}N5_7;cd z=VFU%%zMW>hpASp5kKC|y?9*U8vts&mt6x(o|Y_D7Juv8jsUg^@F+Yy`6F`725y`t zzV#L7+Ps?rf&6i-L=#x>@Df(HJ|sa{(8vQ&Y$UI?z1qZTT8F15fqcLLOE3<0c}e#@YW^y%Emw)%w&=_gT9P4duKse9^YF^RLkr5U& ziOqshQu~CxC&Aefud8(jDeJY}-w`l%`JGf83U9BxCa(d!=0XF@^Mj+8q?jg^tD;y% zdg_iO;|UVu12&b~*|$`BrKxy9he6%N6Oy|9HoSNfg(ZzoGFIYEFjC=_?7NmuRGq~W zB7YV5@+i{6CA11B;10q_DKq$PPu)Lao$M&6-|QVff4+OPUxQvaVt+9n`fh`G`on(m zQFQ#I)pv)D+ANZKb+O!Puc$5O^*!ELSC@_26_EFNpe#NDzQh4wmz{#fXimvrBKiC_9wmyV~bTbXX0>tH%MuD z;L9q2VypXNB5`p)l=eZ|zPI<4JInqJeB@4QutkF6(i?t7(QPR~Y#FYw%rAjpc~wCXlT{ofK$LH2->5rZQ#-BChB^VzRzut@J;i3ASARSz z*Yix8M9XDn)6{mA88r*OdR9(;v~(9cw0KT&9_>>~v`&Oxn)rPjI-&Q-*~hR&nX(UB ztV#s32KMd~L+D}V9`b9_AFb&NWRNkl4_lfNN38KSua07?>g~7fxw;)xcMyJNI5#%# z0a9F>ZwAotF-aewXi&PeMl3Lqihmx9p!(o)B3E_7r>>>{PC6Qip&Kah1UPW3N#5o+ zU*l=T`m;FIec!^^s~a6e9uyr4YIaV+2g@b=@2_;{uWQYM;-xxy^`NcvcUv$D7pk?h zU%XXg5zS45-9+Mxx8&Ms8WXtgD4MmrzKEultAL)C{|w%pTQMHmhzNP?cz>N4*!ky! z7bnLQoD$mb$L+zayhhhs`xfr{VQ#))P3vZuv!-W&B084~Euvs{ zPRtH1!7Zc1U-esP9r52BvwvIgFfFA1hL$8C?K(~!QIj36&xq{?#}c$bO8ZTSVKXFF^)N8q1+{y% zm<>eVWMdC5YUL~kM}RRp0T2q&!;5q*@Ugni-jg+;630%_zp0#zZ-0`DTzUN2RRU9G z_Sgfu6PM4$3xJ2>1G3KDJ>8|>E3d#CY(`=-8IDk#3yF4vztY1=fwnV&(Z^%2cEsS! zYwZ~+AzwSJcEMgI2s7$>S=AUVXv1sk8=q;@h(_{D!c--gUKTf)=Y9;%ilM351!)OQ zHPcT^1@Zc&kKpW^{C|C3%`6$ApQ!l?&L{L{EhlWM;i}(?G@|I<)6)&^V5G3LU2R~t z7yO8t^EKL8?PW9Y0-%VQ?7cMxvvKf?-B19CEISM!L%jsJj8C-WNbIcAep<}Ra$i)FTdz){R=jTaw zkrfkh!7R`ZuebuYnGF1UOY#w=R5X{__m_{fmo|>Zy#zq+I3Vx^7h&9;iB=#?FF^Ptk{yQU0ONKC%Uc5V|^pT=nXu zmHlSL&VR>#nxUnP{dqS%vV~;7O_hvrc54r+_hFjns_<5lDk6C+Bzt3$OQrdb?cRP# zYD#W4iXqe=wYd~8cN=Zgy|*~3rfQN=0H53$z@}Agdm>o!dBDTmX`65La+srs>^SC{ z+<#L=L9UM&=2iwU@~+Ydp+~nN(rPeSr0qp~%YW~HIANMD~exEU9-U;vs zLR%rP@r)3$Q5Y?95BEP2tqqKioyw_3fJBU2D{W(lTM?)T%9`1+fhvk71&cyarmgZh zIEspRwYo1U9NFBw>v;9v^}9r{nA_W&sSuz8@W(XH#+fa0p6 z&wr5xwoYnsJW7nOyP=Ue#8gZkf4n$3sJDEHTbHv|hT@@dJXTPy zYKk%SYy9)i4CdySd~C0L z>jko+C~U7>3v06wDe;XK>J?Il^c^?w!Uj^g)jvnSl@GJ=_$(W~O)Sdj5d;wTi54Ur zudOKv-y0-I8;Q>+Ii-g63qU~_rGNf%39W8~ERHAkJon93`qZe8Blb=I-fR6q*B$Il zk(8QWD*Y%*Z9!%pZhqGAN#U*SSIW?oA@{cx2}3S&{=&th_N+uUo|-ovi-Fd@cKIZO zVTw>FQN_>lVR@zFOp`j7UY&>dHa)e0YljdF2G1lOL&b3mJfNx!LVuZBg-MPeF$_ekKExR3*s(cp^H~tXepgw(@NVo=fA5Pb z$7X2KKxQ3gj=L`O=mO55_p-Xa|GL#$_%#5yhqfKd?l)&EbUMw3zMsKyY|2Zv{&;+YC|g%$VWeV88wfRp2ye>y z)KOuY{0)8&%Z+P@xJzd@r!<19WLHlKAEr6yYU@g7isT%_)EoKW0x}^?4CP)-^Bk7Y zrip;)=EFScnt#%_SG@=X9ZKsr!^dBL@lb1q)YY+B{cxIIwH>dO%Jvtl8m_6!Xe{@7 z%7h0sF&p4dCx-`*i8we+l?~BfG+Yhr6*86Ht!H@Maf0Pfm0#b ziHbVU3@HGI%BtKJP*TNc5r{jde|vEdFMV^eyT5xfcq6<{^SclSVUB|uP+@}@Ck*O% zNl){UO|d2^wp9*Ouj-3@I)tn$m=kbobF-PN1_SyEx)9TxSc(Z*5arGL$LxpK5#X!Oi=$!;*HU$YA~l{tz+tY)NZ z``y`8pC0cYZzqRV$z(QLLMGJb$-^M(=#|Y?g}E%N;;I;%a}M*$pm3{pY+{&WHfHWw zKyoKwnKsoKx=FdDHdc$3To_q^v&v!CsqX~PLriohV45R;<_y89**g#+@Gzk6!hfzn z3s;qp3=60&Ig+l-&$p1&Z9W4Zuq-(%I1?P&>P%u+_*gdOk ztkBt$d&`=~>Hcc@7^<52(?C(sKK7wK`3=4VG0q3x6*buCM56 zr2ty`aCd=I~mbn`=bU1f((a>MBuz!S_r8O(+^T?e2T1L?M#V&po{+Aa1Hv-{b zZ;f!caS-_$9omZeN(Ym-%e2 zV;kn)<-FkCg(@kB!}*lhP{P$QRVqH~g3jN_-xvARWh!=v;(X*0Sbq*NOu@JTBvsnA z2ssTA0>%BtUN@3%W7+pj*!w-G_L831q4|Ilm5|Ww?(CLtqaYi6$Iv~2zYn?CpEi4rO-OdOhK))EA3mcT&?Cwb<$_0bhvM3+)>NK)L zzg7!J^eO^VTx)pzYUhStHy(jEUNcv^XSTm%Js#)z4F32jg97t;;X_u_U3HbqDg#A) z4MwcwJ#8j$SbxLV*kAFCXq$Ysws2H@d9{NIVyd>AZR~6c=saXyUhzF#>g)u+@U1fG zwJ_69m<1cFtSRG)unwA6tRtbV`{ot3-fFKwEE(=kbNm650k6&5+U-hOGzt=Kuuri+ ztl1szWq;F)5?%Ii`w)QM2#EDJuy3d`*|Vr%BltBX_YU}c1vckrw)ngpkIVOHj6IoO z<(wo`8m)-oMWek!x+XaELyC!}EFP`7k`S5SZ<^bl&|G-&(z+sV&Ejy#WH@FvY^DgEUHUbtlzAVQhqJQQ>9_#4(2iAd%CPiy|q9uUu&OR-b zZkw$z>z0#i{!s&DW^jhnUbm&qmXi!bVQ2|$Qoa2~7IoLuC7w!-K+rhiD;QX!Uj{*h zETLm3>0sM&aN1I}7yU^nt9z+q8)=f+(F9~`99Fy!Ph?h{-`9|aohV*}hR})liXe@T z3xD8O_Nio?S-%w3{a|c5e`a>A=@@fHL(hl2T$SIUY<0svp%rd+`s!>l0~3}vwEGXu z$?2J<@b_oOyS~ZF-7H3*?H=tP9zCgj3+!QPqb+Y%y4Gq{8fOsT{!p`CZcxN%ZmWNw zi26!>ATpGq*S-O-U%066<@@#YlMCk!dVf{yt((|Ct3Uu!1tRfSJ7(Re(TbTWq^jeW78aB++tL+1Ehk5Rfs5N+M8tHNm z`AVPIoVh-oPP0h`qVS0kwB}XpRS#rVtPTPzOAJ<8wI~4b)VKN|$-v4?efa?Yu77=? zQyBl`AQSWI2)17@{Q3-ql=ydaAqop#PNa!gp1}WsQ7Q=r-BKqK)T0lHoXhuN3b>Rd z@EDlDoV!gR;h{yS2U?L@Tz* z_Cu~a5+0n?z9wBu71s#H!x7RqNPkJMg0*`M^Kt>fp~>2!1{j54O%1(lRmwQe%7dD|9{}cv*W+T)q11+ zdR*RMiWjTdV^g7}w0o{^-z9k;`*pEL*%!`LRzSs{{iHb5=~2(1#Ut1;dPQeGY1ZlO z+c}C_FaAR4uypqKd^i*DXv`SFv9$94oad}8!kaH@1ZZrCoQ)(-6ShnLiWHtZ58ME+ z6LVRPnWVNU_I!IV1%E9l{DwG{k%xArc9*(=vh5Rnmu%xk6W%1NvXPCf zs#uPqspch-d%_?d!Z-sp&#`20Vs|SlKEfi>VC0H+tq@^a?tm|5_dDBv@ zfbtLmivlai%`sVCF<#eGrpw_f&@R@h1+!aOs}?1RPt&6l_iRSA$JZcmT z&F9pisq1|!i(g)KwmLI!zZd8a^KP3IYH9UY?<0PP7z^Q)^H3R_vsZc;OGg`7Dnu9F*IOe4f=%@5!T)T(noitc zHBxa!F>0};b$6!|BlYJkbYz>x%<)#S#ye8hcpNDga$fMps(Lk=(YR9eyhhA>_;*b+)u>8pnqo@>;9XGK#24KK~JHJb-LesAbZ#O zmW&M~6nq^uA=4V^ogw6KC)6yong;UjH4wn|?N+_?o0lNjE8dxP3-f+{I2arr zA4w*UD{##!-%s)>Fb;Q+JI)Tjm}z4Wr!_JMmWtYdPq!)d zjYHp8&zL*LKeQMR`p6O#hhZD}JAE-M+^kEL{FGJs17M~6#8c$sQSyMRrZkl*dBcC` zN+8NTcz$s7^3C4y?z6*F7?PoZBCkI_T7S6MC6p0R@c>=(&QC7FdzyU19}=6Ha=?In z`vGd}u}}cTn=^a>l-XaDoWfnL?I+2G7%75lm-l(?%`P$muV-}Kdn}8E0M4ukeejEJR zyLEE2?dSO{^X`-_$31>?$9;>#kpyM6JVz*Rl$&kBYzD%Cm7_E{$cC4e!GN$-yv?~K zY}^E+&&N5{Sw4H8=aW8PqFB~|6n{I!pe^5#S5kqaDMkkzIYGvVL2JW55Q~ZvAKHB-1iP8mM)HYcwp0rHXqR>ib$mZc@I-#^wk_jGDOHdXtK61iH zD|TZZqqy;4Xg6wY=e9uPT0v%uY&yH4rx5Z}H4QDu`M8{1z`@jnX33IrY0x>rQiyqd z<4s%;pC;y($x)abdQrDnihmECS<`bQOe?K9U`^RDzn)o=IWRl%Lk5A8DqlF*Q%&GC z2a_c>D16;1u<>Y!0N!?fHii6_Y$=U`{6$5hqThK zua$e5HH<7|bm|@AQ^&EHQ9Qc@)vJ_*dwC}uEvTr(^x7HjRn#X-_ar8^4k zl@);=>>ZyN8On_sL!y^QL*vo5s;5du|1-++LUC%~y`c@N+Q9XEP;|!t9qt=CUqc61 zl1@lZ>%7`n4UhlfsDC*wkp)%6E2zsvCK*HUa|j1PVJ|(gG}<@hP79W)2RH@q3%g6Aj0TpQgvOXlfalFm0p7su zaPcI5tvwN!n+}J5gGfLw*P1t>*iEgORjNB&EdyVW(ao9^NPot^MotU8amTN`=C1?P zK>D@meIRGa#l-RSlxqp;&_{xnGhQg~84=D*&ln>Ja*VnxmYguh68RTo&jDi&T<**4I1Gz_-!3o7msJ?QDOQo|GA{-fleq zAQoUTHX9oL#($US1g#M`cUuNw!sin&?1G|K=!^&z$x$adl{}|dVD-G3E|H17k~$$V zp0KLMPtA;CEC(xXKtO`C)8a7PLs*!;nvZ9NX)b+MS|t?p&pqLd^px}5;q4-aPz4g( z2f> z+($7UApysCWbJK5KfAG7Zf$+8KSK`N1Vq@)K4{_QbGJ0)5r7%AA6yGm0UoB=>@DHw@6!N0V4^Ra;|wQ{qUqa;XH1j zmjY2#R?r+3tC0D(!goM`G1H)9?;Gbn>7=0tQ-Be3794izq?W?H92?4;&Ck#M<*nd6 zcKR3aW)8!3^EhW^IgX(9u-cRQ-5^iAZ7VjP@X6ii?ta`>`V_3x?i*&C+l;p9I z=QDYyM+=-CM>>Hys1Y|QuKi_>#BbuhkdeU)r#$vX)-q9}jJ_@1uyQEt$ zx2jw0Rj$gtWWAqkZT6Fn)0Ly8-?m*^>VLQsnt|5xI?>@eMI2#{oLzL65OHxLNfRBjBHkUFKA1$n9%@}&J67G8U}@z{ zkR?rm_I|de^nr~sXFbqW7uQwc4gZ{MdeIG`u|F-Cm#(lD&RRAJGl#`PQ+<&{u}?<{ zwf?=I$V7hsDFaE675O6)q`?W$m48akjla7+uN;-yV4A=SPvSVKp#vjrt%Y7&Q;qGP zXagtzaIk#JSx&kwG^uxA&{)}8)CUCsuou>|hqeBF;*7$) zpl17`nxA#e*Zwoq(<8{V%0Sc&m!quV(J2~NzyA$jD;iO@lA zMkuOFu)2-ec7aWp2O7FV-=(|=Z)Ka&^P%uv(}O`I}*$o+_SbZ~O%yc{Hh-RDv(`QHXN zMW@f_2N*&%)w=*|wWaDldFNNediZbr%ekqbLjkusotEWw1*q+Yn1c&AwWSe3<#L%1 z--6`r$1XJIm&9Md_l8(J?X`%LVdyu)n*2AO$00O@Qvlg19PU5ekbfho?SdS3+{22T zJ=k*16`B=Up%ZB@K8ZR{3Y9ct#1Q%Dh9Zn3Ipnqp#{g4Ssp#)x4Wth%I4%w+Xs_B; zBg6TDIrzH0S6i>G4TDqUKTLXQU{A|-jTKr1*@R&;%o_yDC2F#(JVO;&ux`oxx^$BT zJj8(aFaot|JK577gYh}G|84fVJ0lc7Vdqy`5wf8ndA48EtcO;gp+j6}mU zL&{I9oDSvP!{bS}P7BKd4la(4KFmL>%$TK3+m`P>r{z!Mxq%^aickeBC>GPRaysEC z_TS+GDkMJ-}6BG zRJ-G0!p`OdV%pRv14C72Ka9Ojvg2P78BB zg2fr!4`ds{!GEc!nLT;JzulkqFZqN2i+^*_LhjmHQw{c>9_+t-c5srKiF2uumJspQ z5j~+(0utgJUvsqRYz#kLl^-NsAzX0WiY58v!Z0=vr0~TSToKN)=`9g~s9FID9 z>~>CzS!Sdx5GCNf<(e^cmdy;pZt(b?mg9tyQ)t$TU4MEs0)W8;)_JvMoq1_RhUhk( zgwQPZu;w@R#F3azC$Mbxeg>CB`&tU@&Dqt1j?K_SF)Xf8v~rSB%u0!+6}jSxl3zMw zH9s;8mS|9j!bHRDRroqhyD1u z^XgdEH1mFQ0B~J^lXa@#zLf45Bqlw!U3b1fgF5>-OpdxzvnLcAvipru@S{ z_>}95{P4fuBLCt4r76cark!}r`5l6X&__QzN8m+BipxmoFi}~(VwZOMk30|3uX=nP z`L38REQYo$7v30mF)}w`$Q|THYv#b>d0>sr@qZRArsh0(x>RIUtD9{r$3^u&ET>cS z89A*42Hf#_(>#}OO|>MdioH<)Mr7e^Pq78o71f;mRgSF|Efd9k(JwBsYIO@hl>fv? zton3AFopp25R}?#V3XRIod%x=&g|ZLrqLOf(J}irud4(YT2i#tpu{So(+ea~RO^}I ze1GyTEhgt>mwY^peCotfQ1gjSsxmhVt6{TwHd)8^+xX&n8pK#Oa#|6r!)uVxBD{Kq zX^rgmXU}(Z)Y-P)&g2?;jz%VsCJ4QF0G*bdQ2?pmW4y#1`*5p=UZAja_)-E_0pMV? zlp}hyCT0c0yaV6G%COuynmwcs*469>FOa2jWVEt9vY|s12Iqy>1zpnB5muVRF4!ha>q z=Ckt3+#x`Nd1IqhzLz*>vO&i4EIBWP3WnpkOM;`_x>J+Mx~WX*2lf(dK)EP4EV0{s8lYx?^iOqyn>9%;1YS7dZ%I5qZq_ z8F>LJ9&El)7iYP_X1?v(Zhxi8&(2R?%bC(9b(J4t<$@A+L%wcRo#YbO=~tjv%aU<1 zc`LW~a$1=ChT06L6tYA*{vp~iMZA#>ly|w4cnHkAGzuqgaZ`eJWQ%E`0i+`Dgxmf`m)FDcm z@RK~Bzx(bx+r5PEQuc@l<7@qbVE!|%QW-Ub*K5_&fNSS%ats0xugy}-npQqt3|Xps z+-U)3iLPuI)#hGO8;f;OXF;HlmM(*EBWpnd4W~!8)_`h9+xPu7&1N1O62nWIyiw9i z$GLo_mkl-mXh4_08p&}Z=I?*X@w+@w{yDbg9Ul@GTi&IxI|(3e)q}#mr5|TTASG^R zaFqpH!^5@;7^=20@(PlC_4Zv8CpM0cvgK&gL$&;uVYO#|friC_{g2LG;{jj}fPG32 z(LI%-r3_m|<)&;)-$=+T-uB)@m( zoiaC3iVvyATWwu#hh86Y>2ReKl!Lxwr43a`ml@YVy$t`YGu-%-O(@!94a3-sFG5jF zQ^(sEG3M7G4||%>><-?>rX? zpC#{|9I9Xnz6(`On!SIi>2pdK?hxxK?vvl?C*FWt9+bKr;c7LjQ2~3UCYSHHRgBzQGZ&kAA(1?52Q z+3`@Y7W6EcC)tok9_fKq-54p-K5X}uQO_`&gx*E2e}`-((;$EKoS93SIR_@@z9w(# z;czhdeo~br!JN-g2}YhD_$ExX>@#TEI4zs^^4{jPhzvSWI_R+7T7(ihObVSwTIdkU zJ|{Kc0RH|6_l4~)IIJ*cDbLq?T>P(Eo-ie)r2Z>r^XcVnRvfE-Rp)RI-atuX#qBrl zjTt5epY!elyUBmPx2FB%;iDRi>Pdf@TeqvMXQ=*me`@JX$sJZ79QJ+J79|82^ZjKx z&J#vMC68igVkbi{d+~R9HN!?;VcZ;Ew4{gU+L4KFv}P5p?ubPcE1sw&2y}EhuH^L` z-oU}oaH7IRI&gx>?3vJ2lSU`gQ@$=-nc6 zcKCmC89G&LG(UeFdU2EGtxXATjV=4h$K zO+ia-XcbrHllSrl?B%-2qmZpO68wMiyP)y#`E_S}{+)4Nu*c)`2x^f`>a>MspZBsy zypF;h;?u=~jiSgqITS-BJ22V|vhQk5DCh8(Z95sJCBuROu8ROR*%z&1TV$28v2Y8b zQ;gQ}g4ZWCdG#}60Y~q8hHff9~(Rjy@!85Y`4HlxGJx`pGeQ^6;Vi(T}St%vcUfqtVi#)3~88G7IuYc&aAl41pc zLIIXs-b8>6wJr6w*b6&v!PR^9JGr97c?f0l7^8iPF#dJh9u{>O3vhq2tR+I7r`1&;2HP>aLrnx zM3*~^-P|xZiowSv1TV9RP@pH3lyH=vO$#qH3*GWuvP3_EtxT+zoQM2++=LD>Eu!RJ zv`OoA?nNG39h(%!7Q*yYn6I+H+;r?^$1!@Z4KBrt8G5x{$D?&?e8Q4SfHn{bD&C0J zw}I$VGOHT!-x_~_4Y>^|aSr(MW%DuGSeliQ_V9LN1O*H;n>-R87li8dC`Lt>k#M*C z7=*T{)RY%}*ATopaezd)4`PcSlXd)^oSqfqqAI|nls^1rKFi<(=<>H7ZSp;`oa$b= zQP^p%OV^$oa_0>VWoU@gu<6|qz1o&c`oT?OXS!dTx$v3qKm^U3z{?+s`3J-B%+~*IDHbHb>>iNoE&iL5M(M~1 z^fEjm@bY-7E5?Bf8H6S)@C*4&nnvjQ4bPY}Myt{L_5OUyZLcd{)DkF(r-bj{26V72 zP~W$U>Hx5&-b{l~Iip+@Qv|ANY(*!^z0}d;?F)a#i~y*=)AkB)VQTq*{Kytx58z+7 zUxpCuc!E#-`)*_aO)V8*KNGT9X(IzPann8YYu$xD^DR{s4e0s!V!Ngsv&W$Zh~c!# zs##B-sFTy<{bPC2Ih#Y$v}7xNl>Wev@m_~OXZk~g&?0bPTr2eBQY~G)D=}Pn5NWfM zY$<=sgnHL3PlY|}7>pc@nIt4+l6zBBZv}T058+~X}Lp1?`IMPSY0l z45`$3OZANvI}L=PtS&Ue2WjzD&_Dv(%XtoDZZ{;jn{x18hmBZfdZ&0o3L+O5v+KZF zpnC9go^f{+hY^&15H)Gt#T(=UMS;(dwi7r)#i z10jNr->YN7y{3Dtos<>}y7V|3CW4`lw=TC;jeM|1dN?VVrwR>yyj{L}>XTDgXZ4PR z!|6(#;^|IZF(;{YN4fCsGR;Vj@|o_?5t;4)W4#P6=d=Ct{p4{ur6VNPhsbJJW^RA^ zrZNxi@bXK%@KkRSx(=h_dj2Zx_)TneO>JW$kpG^{VJjZ|E6KKUO0kfqU zxIg%l1>*kHI#@TqotS^Wn)mVNF zy^N<9vz3?8wfUrFblrb=%NQ0mtYW=FbQNR$*j0@8vFa-N^Z3M7RO_)|6@z5-6spnJ zDf5$dD2?*(sGiolG~BF(| zz8mAlEZxKN@?Adi;^e3uZubdNWT`0+H~uoyY0~C4Nrg@4ZE$~UM;D||JdHEnXPgyU(icF!z;?zP7pR zj+%~F>eQ-Dl5^e}41x?&-maeDr>t z9PirNCohkF-u-`D$NaEHHM=vmP@&k(UTQzPZ`~I03~1D9v~836HVB{IyZ*T&OvgF? zcAV0~!%=9a9q<`qPeE+PkdR!E%%D^6Cm9_2&&JK8vC!hd%Ey{8Opcx|O~T`=7k}B$@yQjNo(DDT1FU{r^n-A12 z24K^5PV&KZ_8ua=#~(&vkiX``AN*4u6VySpzu;Z;v|DS}Ywq-+NG$#XA(W&a`~jh> z^h3PrO{D5C?O;8Of~6RN>2hpt;6Oj>Yd7enY4W-wFS1;)`sxKd6tj0?1t4O7ZzS7) zc@$(lj7@)lXP~~N#Fo6^8a_nZ#bmdbhH+m!B!Ob$X%3c-3P<#z0X?zRw>^Xlcav#X&izdTEUSO#llR$83;1`0J`yv={$b4;$(n>&@DxloA^UsIl#7IYX1Y zt88|eW@lA5QYZZYPDHIWZWh+~rB`DMbvXXyS;Ao5@X(^minkRawKz!N)<16o%L)zk z@3w!D_$>wAORmayXuUnTxq`=$C~e;Ru8BrgicY*CifFyI-27DNuR4JT20d_9LoA>* zyN!~vf<4X=#J6G8@jftO^2zBzG)r+^V?%efW`^J%wa(Ei-;4pzUuJ+lJ=-zh9bTdP z0$q#r47C;^d+qUB@DY#A_`O7*gBQEzuB(3x^YLF>z2wJ_C?h?<3b$0FpNNhAj~}g- zJ|@sKC+`aXu+tYPpuGMecv1Nty?!egJQ>|gvMY|D55c=Ay-z1Am4Mxn0AP$mRXa*62qvjU|8i zs6ZnuvD5|i0_R?o*#D9ur8CB$#V)rUL?-S4#2E8?NcmgLipgtco_ycIL3Jl4R`}1i*wNICwCY8h`DqXULOvpy7r7*pC$_g3fdVcHgG;4Um5-?TZQY zV7*1Q)LNb77uj?ak^+Dr=#nj)C2x=hUl`gfKD@D=n{5v{l2v#UHvtBm7v=Cgxhdyn zwJ6@Y#D=etHUL4Cjc>J_fXE+xf;Z|pha$5p05N|wb<#|qmhZtSrSBYEbORvQ-Eoms zUP~k@7@r<3u>ygH6DOY402qIbYBeGdBBlh!NKcsfCtw^{BgSEqTukI0%XrT)=-CFG z)!S~p5=enC@Ro)~u-HTxJ(wO`TwL_o-^lz)DZ7Sn=s-dg*jVWE`!E0UFDZ}l(2YTs zAj=G~F`vsQ^Lg_iKAlu*7~FYandf}-FoCU`{h%}htL&*}L!rl)!4-cnM5Y>Zis#_; zl#e{beQ!yOP7A}n=XfwN8Y2nH!lS_+02qnB^9;zw z(Lu=i7piHWb$7th;h);cQ2v_Y3Y?;NN68F+I(U41a)6tSj$wWDW^i6y=hN`G^_Uh# z)fytRhKR{-=Hy6Tc{_i3UMaYcr%~c`l!D#-jt>5Ma6-hxNFhF)C7lOeCb#;Ds2Z>&6gPB2HwNN2PEtDux*_jBZJPex6LOyZjotjq^%0QM^S%cdvg5pg=4#2%H9X4 z=%qG{N`%TVPymp}C=vf|C4?g0T~@R44S5WDpgMWVa21wjRSV3Pi9tXM@CoPm zbOMFJ^K~{AiA;aa>(qwW!z~4T(CkC(0Ja(&(Ke#OphTVY)EwQ#H5jM3wIq>8qYfkp z)UD?;$dL(%F-7Oa#eB*U9>@sJ#|1}YRWkGiMLs94Xy-|(&_w27?^B$?f6j~H+c9ZW z^7Hdzh(S*+?QEqFdzPQXR$-Z~#(nKFC$l>YX#r0pn}dJPKw|F|Xg`Ay{>+-jN8e_C zP~U4L$8C#%hhZRs52ll=E3` zps*h(ltPJt0}LQF0gXe0AFo3Y&F2#_`-q;y$c90rz5%zxRV}3VP=qY)->RtE9mfVMvFjkXo&M9 zzcw5Z@SSfEor`G40_zPbt%bxcK@0*?H)5J0(Q$ufZ(#^)r0e4eo95jj)X^HgeMh@>Di?*$foOz<8%tB>WZavgR=|!OEEkJTI_I;~dl}BZAKA)ljhP=2dCu>Wm zsdD_-O8?L^J9{=Q@^^AfW2cqY28Fts<;0R^c6S;`#M$TyGBKfSMmjBoVUVy569|9h zQ&6>cAr{B19KP+rT>*+jQXb8xI(m{zCX53f;PVm59KNKDvy|%s3H-83k#*4(P@El& z=u)lqbvBj>O@hT$9F!H2l|7A2(_#kU9&?r16jDq|XWobk9vGwP1bC9q9)oi%hIhv$ zHw6DPYFQ-MBRS8i^%hh+sl?P2UHgBfM;caP=k;|+3N;(Er9J{TgHg_Mb6nu-LK+4C zDF4s~{RHfJB3(24q0ms5B7Gpkv!+@Ub`q zzhS)SRH7_L=(dg4=wQwNb51vomUWGjzlY_4R(tr$a-3-?d=8Vb=6nUPXSja__?(O# z^A8y$VsN)#D))dy*x>X*?A?0nP-6maS*!xyBfpWk?xLY3CydoHt{#%1?<%- z`cNcvYIyYX5k}~ud5Mmcj~x!R4Jt-JQb961gq=ko=#I;;|B4O`TSlQDz2dSfQ)p$U zVk&`<3IK;{x`1{WC!NLUlc<0AbBQeSP%UIe`a5_YRrGd0r90w#m%q=>ft3x`KZ&%1 zS0yPa@Q#Fr0yENfaU@;LTs^}y8-y-Wjh6Z;1a1frWh{`-;NF6_T`2b+DNb$FF;oTg z@$hGQ;up-@_!Zl2JZnH`6*=H?gBwUPLHnU5Q9;=oXuBmdh9?BoeuB@j{q|^U$)O3l1|87&v}}W_a?ar!3a77 zx}7*ZMCW6SgO6b1$vS@@4)f}K4*N|{ly?x#wGUqm`;z1%1w^~MI?HBii?O3E#?tWA zrIyxUHAM^r1)SnKUbs6HStG&j_FS;L4J*4~Y&sR$4!<_WwS&*+MLr%SHm05aD3Nmz z0#T>XQ%PerR0BuqZ~!+vSI~$kR{A{q&|ZPB2Zm|4HK&XEKmC7(P{Jm~UerD;A%o$_ zJs|p~ z!Kt!zktUP_CD=Xw8<~xCi?@^r2tjZ>MRZ<-_@mv^V{>^O7sK)zuMzp=BEyF=n`L4) zgP1a~sw|PiU~hkZI(YM!-A9`c>I}N(7$w+@3SuPf@A-bkIBkx84A!?r2 z)(e{#-+xh-qg#;BOh)nQv$d69^Z&8WD12j+CtV2#ZS~?-Gl&|>4B~BGU+1t!W+8{n zjZuk}+L0a4ijTafEuCa!cyzkTCO7;v4f4v6+>b%jgC2tkS5`SEZcM>2C;9nUdl%WM zPzD&WzNvq656m1UrbTuTNK{y=S$d5nXz?2(ire>0rne{^L~&uagfku>8Rv<5E_JD2 zSXMs3XY5odeUyH+D?h|E4D%TJV_gzjv*i>q zVmyD=-biB803FiKP-Y_X)Pqv;z|PhO!(#H|Z4H(9ek6)c;e6)CHk=l0Bq0}>C&~oT zjbyD@^Xaslc3HTB183XaFCe2Vwx(n=P6P@p^O__K@Ucc?*jO1D#IAQ-h=YburW>^* zKClvji~(c~p)Q)FtIAWezrRw;TU z8g=oe{nC=3H8xo!0``;jr*_C>cQ(t=iwoi$nR|A#glGq77?tI>e$x4=gnP$|(xXlE z*CIet$M-8isu@HmtY4>ym4(YiB4uQZZ(GRpFMjp=?XLpquL9|>0_m>;>8}FmuL6H* zn?TwgTwmGeJ+Dy^aq-1wdd_=dMJn{HCy;OPtB3h3|1iv**_~%TdzOt!W(MO9W&T;7 zYb>Tl7~@pqB0^u8g#(|1d^YT>2QYvO6Kq{i%VAz&oV?mZkg`?Wyxb4vVB_Icmj+OJ zYohllPD;YB59rxOh&3zxM2rjz)OmlxlSb3fHVj`pPZ(Id%j+Te!~~|(h*Ge5uy(&W zy;s$beLjQ{sG^dmECzs|!brEw3R@5gJZtGbKgLz1y)#rf_gkt1IWR_T3MSZaXVHpz zpH0U(;=Gxjd~y^Bf)&$zsP{ z4>S98iymDTLNU(1LUMOI2&(bZs@=%cqJ2@r6Sa0*R3!DOS4KgFYb6d`@Z4At-Gi+q zh9^k|WnT+wxX$c{!Bcx^?dX3YxV>S0c{#5P?$u?BJY&`PfGpWcwx2)Pj`)mb0{{%@ zd0CF#u`@2tbM-*`3GW}DlhkY2oSGy8&aee3Z!=2_|?3ylA{bjoH0;)x2yZXn^ zF66~!JpaQd33?p3$4NdI%yg@t#73r+n&eD>DE0`slA5}HM~l>6oKb&W1kB1)WT73t zC(i}=y70A3&iMn4^=hmx|1g_or1sG6SkBqm;%ep71@E{o$o7^dVeU_4i|cHseChZs zHw>dnlbV!c=IVMg;(U_-r{Va{Sk><|^ae;9VLX_OR)Y{<2RAP=t#t;+9nJ!Cet-+BDd@ovAe}@KrxYmiwBHIHGa{W=NKPNab)tWXBH#awV zjX&AUVB7a}qVs>YPearX-LP`N+4?>k>Fdqc_h@>hGx;t9)K6+3b{A2lsfNd512|fm zSsuFM!`jk>xzmoGO-pqfsY=Zk=Xx&J!DY|qa5-yda{JBSPdE+tkqphjhNW4=g1DXM zstTzMX360?P&nAi8-D-6EO#-9C9porO+UFkGf*D`{?31*tMOg5U?;MRoAX<9lFbrs z9iD6C)b?iv-dgP zx&_!O_k}a1;8l?s>Yn`MrE(t+cc=LJv&-%V-YZY#-6og9Fe-ZKpe1j1Huk(M%L-Jp zp#x*!K*WDceYs=kG@HDI_pn(B8n!CBR zhjv1&7o=91^`5g~t&AJ?ojl9WXYk$^et(WK!a5RJt?&f){d#QG#C#K+QhW&MT&ugX zvP6@`V~p~d<1*Aj-9K#pmTEa^x$B*7Jp@|-;r@R~b-{!!_mHiJtj?mV58YOCxvgS1 zkolDvn2+10o2Wh*Uk=^~(jYZz4U=0j+&_$c{2})753!Gr*8Ac$6ZxlB{g1Ie{%7pt zKgT})OYGy;=4vo(Me(o|Ma0$*>&a~t#czO^0~sVr@9v7f^^jBhZp)Kg7w8QFvHw9! zDDHn@thTOoIpzlx5JirO0ZO}T0jRPCzc#QZa^|23^KZm6b%mSO;|x zFIH=(v4gmXrCwx7K)kc|qX`)+NLfzSfy`7Q zfwxhNv%?-W*5IeI9v$rT$$E3s#k*o!POc<+MP0l^aR+@D#7kVeMkL(|Vji*%XUGA) zKw8A-3JgKhb7C5a7#xy=ye9xtR?NWB@x072Wf`chwI_@XbL1Q~q%Y{FlJ^VJ)VqII ziPSw>Z9A3zg+q?@juf9OJ6-#7Z*QX3dxhQw;c2UcsBPz>e+7+7HJ>WY5r?h-lGa*3 z9=9*=`r?aLMhC>eKZYD{;Ia&kPT&ETaO)@QY+kGC8|f0^D~ zRd0VZU~Mo=2w%1Cb6RASTmS4BF7~ejW_0W9bqlo{KOlSEjt7P4*7>fBAkkfxt%jyQ z9yhgi9QuwNhsGtP@N0<)hhn2mR*fgQy36qj^5U&dNR61WAT5a^4(&M`h-1EgB zPrZ2SFs81$yX2Ej!g=y>ykYV(#A+Bijt&g4$Azt4oROm72E*~{M|l65E9auMgH^Ff zK!eKHua&R#jFm&V%~``e_yK=FqL<$}1{T#GiZ+jgKX5DeW(m?6u5Q~591jggOA@t8 zo=sg%ZCXKwmgx;Nv-eKBp20fQ4My##Q4FjkdWMbbVrN)m=l^H#ZI|1~kwnqo-iUpN z8v4vJsixSHJnr427)>-S$+FJal0#D4+uPIEQ6fuHttplci?mJqYTtkC#(kvw3K#hS zAn{SfA|-YA%yh?0w^#rYi9{liNF*|&4P#9p^z-R7y=Gq`%tI(c9ly|{68QnOvzadA zY#Hw*ToW9-%j1KzR(^=>Z(ygXrBBZX2SbZx*Q&?hwp-N`?2MF9p-TUSWeuUBN@;8I z;zQ9(*0vw=sS~`wx3+(EKd6hRr`-b9RSK| z_2pQx@vxP0beUo135POA+Jhkoiro{`=#>H0nNwwI;D;|W4r*eBF| zs4l5foi^{pJc}6XRHhMp;2p&A#l=0K#E@JHMEit8*gbRzVBFqN4umua&V~zU;?}+g zOkIExoEv|wBK-!;=2gS;<&e@_iP;IIf zNR#FdU|0VBs~)rtnJ#08ygoiY-EU;8K%zY67};(dZ71D?8$!&StYwTE9l|S6z-U%h zG@J1U@nTKy{(`#OyoXM&uuVI#y{Y{XS_zZbJI7w|+awz5eNsPSL?A@TbO3+3fz6BH zQN}MhgZv<@Eiqk6Jr;V~BYKPpPc1}|)_k0&lAG3Z6^@z5d&k>!Yd`_;ukq9hF>(eV z_bDtGgzSDYO+Jj(dGFKJJZaCfDVO2{0wV!7mj(m^Dt|}CIXEoPn~>kO%&8;`GwxPZ zXq%m8{JfJ$IpsX6^@LX7=e`p?5y=HY$}(gMHGR0lFl$EU29~>?Cj_n9=_EF-Z$jM2 zYBDELqvvy#+Cl@JHRz_f+PbRZ+O*3`-E~Fn^-lfjo@-Rt)s@($m{sVpD{8V$dkvLY zq0TO)(0|@prEQ9GomT50PbQD2OLwJp)u!vVk#4-WOP&7EysFe8At}PQ7Q9z*GtL`A zm)!&CHNGpIbc2^r--x25qDB*p5A<`!n{?nb(17}r%h@$%gz2~m$R8QK;)qRWApUW) zIiDvtGfd3~mdw^kE2&J|^T~BGp!fImMqPCfU4Oh-@<_~%ml4`EGEf>Yn-99f0Gu@H z@gq}wdYy!qLY-(pXYqcTb1U!HD}b#del}^{3$Y$#%MSGX=!WR&J>z`jXf~(x4pRny zor2ROrBalBmZk7}v5#f)NWwO_SIoyVh_}jjbaQx}#J-Q{nmfoQn3ks9KkOJ!56)DG z`7no)HePOaq7at8)(J7vw1F4u~MZIu!cOdjA?%VbA$j4f)x| zAHxm>CHdBSK=F3z(n)vor)Tz_7l`|B_P2Z~L@Pr{@4;+@?+Y+o73MqLHr4Q-SL%d? z-p1SN2jIV!wAC?tJ=y3zOTJCEl&sysUY%BdFZ0VQ)6BN|#%ZN+(~X`p`s95#{Z!DC zSha{C()6-xuZ^$rrQQoa6J|J)ujJT}5426AT`kP`CuU1DJfiW+-Oaim9r~x0R0vqZzs`!GW8M zbv8Mi?SRm=6O6M9?x~5xelE3xW#Le1CxjTUZRtRsJ$NWo^e2Kj|$^I*fhyE@Q*=qWMKk zV0>e|*7iD)Pna+uB9Ma7Y=!OZo}SHbvhpz!9{&Xk%gfYN>KFC;8KakH*VBBD6)qEg zCE7kVufg%bG8Ba`+UgRNSv7Qdc1C_$z|$z9Wcl| zFeJJ9r5I<`mkt$TQVS8#FBB(Up?9rQD=JqwDO(#Sv@0j3D{0sDEV@m$y6!SC&F;;C zKY{g;$99r8$A?E}@89n4J_U8i#aQ5Sw$(PR7?#$|<3uz4OkPIy9&oyYe47mP?%c+yQ4 zgPS)2H}{OhV)9+vN#mEhNwUn}G_w>;!3 zw>;I(;H_P?=LO))n+z?d&ujrc-0$V%GMh}>HPQove$em&nYN54gX`HNHiHV|XszP9 z_27yt+eHhjfQ>oR6l5#opoyrb zn;SCvUnqc+IT-c}5x_&gocmA1VE>ph3$&ESkd8Tdsb~+s=%owB>(UY(U(pzn6+q!p zK>=ZY7hZoi!V4z1ZKi*@`O3yBg53HHjD?i82ZQdAw~ae(-Ak}yr8D|GQZ?%7EX~@3dVXo7%g?@U% zr;#k-mcN)ZIWRuHx%FrN{wwG!kN@-dX#ZBE8jeM=mCONbVdJH+b4&)uI z&BWboR|m@NkjjIXh(jCw4tkU4p(f(O3=984JP=Hz^O!TeyOsci&A1_!(o>W0KT(wXrYrbx41{VUxw6FCjz zN(+lU`9LE+&uqbZ)W6wyrM_&0OHmr_mx8`g1+Ixbr{esCcYZby_e{jU!9+SHakh=P zbdEs)^HDm{XAP$nW{pt?k*V-{iU%o&zXe3pu9^2mRA7VYI6vQb@Dd1GAjAS~Wb z^JiQ-l#`S422)9AImIOf$>zSc7*JGi?fAx-WRxIz)%iZ<5SF;_KfO-^;@w_l?fNml*byM3{@sc}*07WBGshj~h>i`a&QwzK(q8Zb5Z~_pSLV{A-;SBA1=h#vR6^P#;j5_)vrppnyBH zC2&|a<5UAmj$me4l^^`14y;4oob(nO_Q1`cm-7rg@n+-Uvaf`?hRtC|bO0l~u2GqN zgjj_}A4c1;*z&rx@x9~pE=EUSxLBB%=A_e$_=|tOsHQI?DSeLKP>4YqG?!E50mvVA z4#IWnOpc7h>O^2A{zPga>~7*bPNM!`3wicItnroLp5S)0&fobLR zLo2JJi54`99@lSx$x-%MB9fRzq5vTGdcr1-{F1LpgRGLU$(oJCJUNt2C^w_NKVA6& zp}c=4=lrPJ8n|f3s`fleWYFh+u8NVd1C*TNfTIk7?wq9)rAvz&P{VCt+ek;HHKX8r zkVUK81dS8~>opT)l`|lvT7-U8?V1a^NMnC3_3VI%c+|Nj;qNl>HzO1MCjNT3lZ@;J z;Ed4p3wDAp?5Y$k*(OaTE1ejFCQxUyH_==st+N% zmcs2`WY?0?sS20hhH!e7Mr2cdA;pZo`4FEt9g!jf<_%s!UR8*j5S<3nWr6+`rpJFa z4vo+qE%8oNmIQG^l}X<1E`HCH&D9J)*iJ3xWl%b9}wt!0sq7P&mY zg){%aVSN9Uf8A>P8IGmOfJRT8$Tm}TNrp%TJo{cVEzTEkE_d8E_*lk$x)OiqYB-*! z5L;dIDOv3;w zy_Iyf%h@@`{#>U3w$OzHV;<6r9>98AkWgtmmFdMi&jAZ%{czql^!mEoiRDUWC`|uk z(7q$5PvI~pa@|wihvaN<=J|isIGb4Tm*i^xyb@2#wh?)2ZOaZvi&l!CduxwdhJ$4@ z!@({r@r%~LrdY>&E+<=cS<=wEa0|>nqRmFUK1J@F+%zaoP>W7Hp-DyQR`tZ#5 zsOIvb3D^57fFF;_VG3Z-c7KKyI7$iG=7d*}^B7kO?0AS57UOXVZh}{M%1SS$j#^C&s3}c1cHnp6O99Gb&4qig ziCRaZkhbFgVYgxg*L1hl!+>yI`s9MHjzwguMH8 zK^Q2<+%r3YatsjZU zU0dI~$X}upjFrKGwi~u+8ZvM>a7OT|#%eFE46$2{N^tXR^n&`VX&#TgkESBFl{olQ zML2I;FSc&Nfy4|NWUSa#Krf5Q{BY0pWiVDO677yERHX=5SmA%AI>|Ln6TOMye<7vE z^SN+z5Y1=}xy|%~;c+{KplNC}P%P=o{zb=arbeM81Ptw7$YFu~y7bU^tVEC*yw12E zaxkcHRChHJ`94k$KwKB3jzb#I8VlN;$aG-IPn$o}j36mim=8kANHb$(e7!I850Zm; zJ~c-1Gm)~Hu3mrXuwdfO>KhaCgalu;G#{5q|JAD`r;QU;GZWwm0)g?}z^W0MeK9Uh zAK%wFzX3nhX6L*Yj;hEC7JuE3bEZoD?BB4YN!wf}45UhK=m$+GS4)ZMXNI zdrr7W8G_36B?C>VW9(#7D4p@iQ6bWO>(>Ov)E{iI;ZuKVK~a4$aNpqa<)W3E{=p^` zf)Us;n08+ZK>FKELx@I{g*dvBDFRQb+g?y(7CZ7VW#c%AHOv}EX}qc2wR|t@r^@c% z*tOK^MNL$nJL>$8Dp!=5pT#RZ4CSfYFL0s^+Tv04aHtowQo$BvG+_nUkC_m!m$<>> zDw$pHQ~G~f56m=2dwOk)@V2cN?hC=PYcdi+rXuZ!ioN%GJvNe6(Jr;yxSp;If5X3wQPB4EY;##_ii6h%c-aUHc$vg~fu2m2H zh(ll8Q!d|ozsjt1zK%y+TXYm#&f8i_JQr|v3A*dxv!&`f`{Ut)8vGVczObsQ8c(xO z7qF-{R*{KY0}7o+xsr~E(9<9a(RAU$!y4Zt zN-Td|^@yR{i>72PS>IeZZ=Gh?%@<-fe^Bg1_?*}27=i`sRc+$vrlV;-oeeWG>*ka5 zLMfgcWDj#|n;`Qm?+`2OE@E$TA}x!ofe&)C|hYu&lG&kOqbeCHnK3woH}m%tSQ7JrpeTz*MecvltJ;^^m-Yu@L)vpPEE*a?OC#v7i$=?P=nfy&{8Pim$@blzvWj^NL zf8&~Su4zk@CN!5V|9_U)ff437=5>b#YqWW&R&BkF4BO7cNqtHE^Nm9!Zmy81Z|-0L zv`u1IECf@J#lk@_>G~K3%XM{bv?+*0#_NWpOTw8;I6ta^XrkD_n?{fHydw3%kzzhx zX47$dX<3sPZ}u5&4IXqY_vzpM8me1GNxXtxNQ!2gly74Oe}C#3H9gtYbuV0-IOJTF zaqpjXS7ly7Da*hsY&Nr>>v?%KkTq<*D#)1ruzc%pDu~+fkfXi1@X2FnP4iZc)u$St#v*(4oN1k^I_MpPAA+sz}n>`BJ>4tXE(8Wvxebi*u? zkE?VOFE&a$8h;F92Qtxj$bC=@2tG5yUI~#wr2_ z3)~_hY>Y`otGChDAXN*uCr2iHGBJ#`iDEs>M%k1yx_?4s8gtdGwm!64U@YoSWQQXe z5Sl6yX{*@(n2o28DaWINMZd$9%;`kiRBrv>;M6vO-5h+tHrk!!&-PRQyo9f9>n*Dq zVEdjTyY_(fULs(5BNyNRQpcosS8GS!274gtEmD3p%H)p`I|Hd)|OTnR>2v_xWu zx4MkDw11`^iP5%b>0{}3(gKs2-^R(^C?0P(JI3*OIVga|h)1Tyu=ow|p2+ViIMBwh ztKL3*eywD```|t=X9HlJWXOFqBTF&hug8Cj*>9cMZ&~gWT<1Uc9xB!vOq2fM34u}P zgbx^;^5O!R?VZlJg!F_$#c~+>u4@DQDmFc^v44Os^KzO^C<&jwra;~lsruk1otQft zm$L~+AU?^~k@c{D$cC1KNohJ_oQdGww$TBQ=7isj}0 zXGJN+fAIa79?nazUQ-n#4s!)_%ZIP;R^wcYQA3_(ZX%X#nM)}5#aoP5Eg$M6!Bs2_ z2%4)KO}hG;`e*&z~koapjwp{vT%8p zSDa9K$T`aM2_%<=>y$)`0B%5$zxBFxt7Crfh2QsIZYE%o|* zHl>wo(gGrz0URM0baG@V9ICDn%PdRmw%~k&DZ0BDaL4@60>Pd$|Gp)HOEqURYakjH zEpUGi`a`aBx2x^sS6=X;*@-fHrn6}QIlJKJ9CFH=Ct;Cnwue)li4>Daj}^%v;Zruz zE8c9h#yhvH&B7fsXqnWmZO?$!vPHF-*($u!gLo{9_}1C?p;388MoD4 znEO}q{qAg1qH5uCB=2UZBzN-=RAA_nU6x>V{kjH5tKDPU#(eap)T(e|z z7nPd8x0xDDKAoe{WYezqz@Ej(fi2~9nqE7Pf$akQ**738)pp0V2AuJ*vb!KfYx4o4GbHMeIpDa_Z1|OC-nx>TTIh&R?H}$9X>HCF0_a= zG4Lm|G0NT7G9DLa!NXkOa7xyKv`ByQYe=6=&va4SUGDDbC(lh}#ft2{BYV`^nk95PPZ8PH-%0T+jo9Vt0Cs|Ah^K z0y!v(bJ#8jyDnL@-W|Srl?)UV?f?z@%k^+BY+fS0+ zH$Px(wHfSQ;PI1xflHRtf0KBfn(;TnjrGj=janh5WGDHmSZNo^IVea=m$kb8 z`Y*Q{+%D3rw3O1J>hKMk8vcKkn*5RW73YDeIUQt{4IM2RFOI*G=YCghS-1Z;zn6Kf zYSY${;ZZxKs`{$S6t%&zzwDT|j?^WyIUdKynay}_FHTtTFcdfz;ocZGcK8EKUhB8^ zfCzs_;51K~AXLfDHerO*Ws=c5HMJ7X!%;U1v`0(_FMg?<8Qp-aZ^FfdK~s z_}Hay0I1{n{Zzv>_lJLYC8E}gi$sDamnX=TPuz$APPHAUl^4t0uj=BH+?ox*RMr=A z8;{(7IDm&ylQCYtjdpK&1%T)U3c(q#(wmzML`|84B!^=3-|xv@HBELTg}m-}^$S#7 zSHW5!e#AwCQk?Q00ZW2lu-d9?q}~Wd>2#wfwZI>cizm07%!h(eaENyPHk^7!!yX;NPYL4t=Z7a}FS`yD3Xdxw?2myz5lGpVDzer* zaIV`yG!N$}x2u09i-vC&r^%b^2vMd07+hluIp|ZbLmNE-=jVlPDV%FHadjC}&z{h_ z>~R3H#MeUc70Tw;hBb!x{hX?q+56#@njMrjBs9VvZo2|f0}2&4H3TyD9=$^;?P^4t zS`NPQz2rqPxkNsyqh)d9kiua6soJ-YWJua`z|adAd-9P$|fG!J0@~=}_%_x!UeiKm3asWOzX>B=^2G#4LMl z4g2V@7+c*yX{jtav|$ib$f=0=&2p6WNDvR|Pqa=zk~L}o)-k2^jkAT<;>L7LtCqDW zd7~(r+01_!K2RHtHTWt_aP|xaV$4dg!@aKf1p~NA)d{T&EKgIflqgy})P9Nf&KjT;E_6!*uCh0#BF^+fFYK_J(kBJa0 zoyBFhmGp46903b;?*KL80N@A#yuntCfXy6XK=gkY;CHxcgru)@&i*Zlgt*^8onWY3 zhh`ZPO&YGkyRV}ASwqdQ^qa09HhnBwEB9yj^*2T(tdlq@V&3}E3I(Z$@MMb5T2h{b0wZz_ zK@T_;4r0+n-*XqCurl@*TrnZZnDgjR)lpMM$ zCSI&nZ6{G{WOnV;YY&P}@?r$03$`95?bpRHzsUKzFB?yC1c3pzvI`fW|i3)#% z{r;YBfy0mVGy6PMR49|@X_-BS?ebs$1zSWcbMU>@1;fNyI#KKG^aJduRPj+Cytgki zrCIFYR;YAS_1CkFuG;rU=^XrZpkPyd*m?$MCHg!zU!QI$nRc6W0oXyIc6j_p0(GPS9oAw8q3om~Q>H}#oa)!)2M)hE~djgAhGa7oDm%!Ev@^YO?* zmiUVtoDSN5eYyTj(OmuK>E;{$Zy*1=_3ZVVAG*oQ{>f|IuI!M2ByL?7gN?Cn4!; z`!3&i@)ZGut;OaZ_R<0jqw_K**fr?bS$B7L-_WAn!x_y=U4p-d#0w`gHE=a&3a5Om zPKu*Z%99Rn@vwP1ou-2iOc8(h^8Vz9_ey@f*Koo7pgtQ*>&Xua-1>LPUuWlFE!97D zb^iAHaDD4LZ9t_@ee5YYC?@$6QcUc{uIV78`CSaC5onL3#WYeLGn?WPajSw}?R^re zNenJ)Cv@lMm;dB_`I)IMK8Y>LiIS>of&2~NWMu>r*?AFEB5p|r#b2Io_)5+J?gaLjj9YLDqg7kYA@mUcHRabp>&BjO16T@fAK z>%UReV3&-XyBuo{1ZG&hU#~ILC26! z^clq~84DsnoG$Gruo{;}BmxhANUc`kq|kcJa`RQHhOrsbYj1wQZ;N!KcE_D`{1}6Oz_G+ItiSg9 zvZb9^lS@7T={Dp8Kp6Ba29}7OYB0=X-dF2?7HbH4X|#!XCpL*RELm%eMz$oC-ld9- zY1zZJ@-!Nkxo(!<*hpoheBJs5UR4Tz@1-|~Dr~CEFA>ZF@NpANfV6QA_K#>-;fX#? zohxv@E(-~!BFbgAri9<=nB#RsCe_J*f{IoR%p}VC{rduTW;To7Zbu@RdI-T9z zOtMmGCZzXo((z2SfjgU|G{tn7f#*e>o`QE13PU3xT3v}_UNx3IRW-mmHsPr)_Q+>)<4_t2IU{qk*v@=IN9GnG93iN`bohv z63DT~+RF(ZKt9w5e)Uhy&e7#6FcPN}=9kNE9mqpCD{p$nwFPTSpSpkA{JEPnt-JMe z$6hHdq=g$Txf%I1eQaHtX1EF5480?hZ+P(CigriP$o;f`OAZK|5P5bAeqwkEWPQc0 zsyeWSY|VhWr`t#RFT1{amh;?s>n8WiiA0FtirrfrUBz|XMKjflkh%lZVc>Lv_X*>Z zV}0E-QYr~^mVKTE!ukXkJQ#dJ`An19>40@Axa50OD`8CXjztpVfd$6N{$8cHSXEjy z5f{tKi&eyb#kje&7}pDnZEqM}+-R0@hJIdNw(vDAuu#_ULVnR}sxWRjJGVuBCM(0TU)?f7K5AoIo7$NQ(d$0ujE!Zg3*{bRCY z`L)T3tpwQUz5_Fy4%iN3yH~w~Wk7+!81a6Yjq*WpqXj8VLqJ2~Q41l3Z z>4}qnjC`xXX)#knI-j8xdXBWZp(`ZR3JY{lQ;;qx^~rg0eNOMsS-FRcCJ0;vv2gUA zbOEmBJPoBK&`E#q@HlzBfAmA?B-J!|b8V-Zq8!90sPVoXc7_mEX zSL%Q^f%`$4SD0pg3ZlnFL^JqfQ@ESH(f&gMjZywRi ziMg>dI3%+X0ht2=ZIJ~smlxOn{^1&b4yu<~FX^im^hg?Rtf(o=8M;QwAQ_Mr#DD7P zE1z+ePR{2BJIw&Sqz(3YlrTcp^DOX?#>FS3{yWOy$U@VKA`hRk7lENW=81gQggQQ4 zdj*Li1zNVE!)wucwJFsraJjg!$e=4O^@!9HL!pfasJxgN=*0wXtJCZnMa4~jakEZO zi#@prP6lj2QozAAK~2{Mc&qE*-LMrW(o}mxW;1qar&9&dyv0h(55a^wgHS7^%#_wX zB)N@c_M1S@olpnHcM%xhRl`6yJY`K5Rw5H!F-`T;DWycDCSrhK60>SHpV?)zm)&F( zNP%yz=*~**sXXQCbNhp0^zpYKvoZdco^O84%NcrV&`jt)&@}_`NJk}J8v{PB^!PtB zm+dM7ApyOY4=Vy0e-TMNQ>;Ff>14ULR6oZP-8%i@X+!CNdQpJO522On#bhL?QP@y{ z)Ga7KvqJW)xK6LqPaljQ#2ry1QjoGLlyq<_P+BFzL~A4=FdEIS^RaXPioPFE7d#0I z;X48DBsX#lu*`VasBI_>Q?TCge#mCDl1_EXxs%z5+$HwMf0yYcJt=6bhUS@^D|7hC zq&OW(iGVuEi=36Sv#7+nf;w=0>Ir1L7G2PmFHeKk$2Zx+wguFImh@)QsPZw%#&^Fj zCRrWC3@N6s()FHWiLwtD=2f~Gx$#h>6Ohv!wuD-!C4Du{fwSz~hyC-bTKzzf4~%dP zsyHVNXXgc$f6vsCfKJotRoyJX0cs5Votif)>UgADxs>{$%t)Iq;mRnzxIkg~U+MLA zyhdh6+ol9N^TE!xp%1u}08~ZX+nsDl{*|gWzavoP6M)(m7_}AR5_o&6Fcv!5eOMthq?X zN&`^?#$5=GeGnv2Bbf~6&-q<01V^Yr*~Il_~{*GpKckxI9#oHHMWhvAVFm$ z87ve0`>$w^dkl|+-`w(r)%Ra_aW{Ru`|5E2=()0U>f7 z`!rpS6vE6!2XyEG7>i$1l9fZc09k_wCJ-8`Ug^~7ljF%R)0$C=nbC)qwsO(67cbuRvO(#pCaped6g-7F287n(Lo|daUJHR5^iP-s6YyAA(hQjXpZKRcxyVT ze=Rd2Ix{X96M?Osh#I)DIj4nsJ{yh7Y+f;vJ{|zFA?e(W=7y9?sKafwt{2g`jRdOW zb*0#a9iuSBkc0(VV=;H1Rtqz$7CHhs4xd$?W_?7o9TP!~DFDI7$3u*u0067z5~M%x zQMWaHM@S4CChs0BYu#;4*#8}u-@R-wf1(H@D6~m z3dI}KhCh--=dXka$EU@PNbV;)TyGn!#QG&o3*92&?^c4o5@$Ld9lhA$1hw!t&nF6D zwSX@#^^Jjb=;WSQjBDdUHq58mQY!H>&wF4@E>=|Jp=CeD@M2X2CX6&SKIf?-e^z>S z1*dEB9w~_r%B8BN{=LOC#M7(U_)U)9$h`H0R>cK7ykU}4w48#j9C#Pr29`><1MjDI z*$7`bqS~BUCG1Bqdrvg`iEVGp2Z=#3WHLMDu>d+ zGFi-Ss$aMe($3ozdbYw21=@#Le=AJhOBDKC`4$B}t24LS#T@;_A)KrwR2xZ7Sw&|? zregBk6{T7h9PjOOmDfN;V6kiX!xbpGcIv0r=H1_8^RS;UG)xJH%dpq5XnQcVkv}k8tnXrVgax&yHxA}QdK-8zi+58~P5qS4?eG@M56KziB?d=PMf3vrHdZs3< zd~BTu`uPMs2f#EypWv@|$YAgnJfX~{nr+2&I0)7O-tX_89UdROSN}VEseYa5e)uGL zJU&BNt~(9uH^TuW|6@=o-#h#jYzY7QFU-bxbDt#6`zR`cxJ8LzE^?6QYB9yqhS)Cg zWiZsVpS1{dzal$A!W#I5e{9OgB*}~8SFeuWnL<^3czUYnp}uV4ms|X|=7AjyETJQ~ zSo;?90#x;x*COMx6Zwi_f#rmqB+4tj<2U<9?_V6A?92N8j7A#=^V{pgh~M|0E6Ts; z(IQk-6!+9w8lk>b|G~g%=-kUhC_iA@SM79j>q@E~(m5`!P&!1{RwzzDtb}pXeE_e$ z>7Vppv)8`qz!XG;Qt}3#HJzHsu3TtD0v79EP8DNy#40b08$~D;vBK-DB>f~5-FD+E z$W2~Jmi{5_uTB_RZse!4tQO-W(dbW?cQFDYf8X`G-}Sn`FTKu)hKBgt<)Qz6t_?#X zzg^=OeKtn)*Ji!iG_&-Q&1H7yCm7kJxbYfkPL#hO&y|%aUIYCSODK*`xgo@kM;XGG zZc3c)@JJkYCk$TXdBP$g{=e&*IU={?rFvnZ*@xZu!`a@0=2u?swe{yV( z8*05j`ACYtV&lOKvrKYG#Z27vUyPKJSqN_h>!ZMq8ecLnCQkgrziLQV@x3ORuQ`aj zM-zJzpK5z$fIiaXoK4%qUbtzWDeA>eJZ---JH-_Tq@aa<21uSO_$O%o^hi>qmc#f2 zCMNALyGUoFDc$7MQ^(b%!?D3Ke=OTlRS>IZTVCX}bs(N-*;Y;2)0(nBQJGWf-i=_t z7~R9K7rPFE-n`CY_c^+nixr$F+HPZ!2_wJy7z@`@qRsN8zB^` z7QW9U+MW(EwL`~Oa0r&(z}mq#8K_MO{8n63m%WXqi|Cu1ES<1;UR$$%f6Lk@X~9Z0 z7AYf?9jlw}8>5etGpK(P0FggXfK*%?He!0qf~I@93iQX0QRjj^`mpThleU*W)KmH_ zvk3ogncZ?x-Davv%eH8B19hh(a$VLT3ViXyO@BOmVdz?gr(f0To1&NcHa|VCFaKuq zXLt1PcaLAc?jP+{Qz7o@e~t=-p3m+gig)#n{NBR{LeUx%5o@kp*D9pGnm^4!YSE^z zty+R~)saG)EN{G)rWKP7(fUJ|M4Y(e$qmnG)j4Rqtw25qR z9N$+K4=)L&W9=$|^*}PPbH4VEW7LJ*2uM>*&U1TD@A=HrV;8-*e|}6yGc<}Ar}SPF zO?*ps%D%HzIF2-qb$HktYIGWLDc?)luwqRxVfA^QO*<7h8*kpO!adbq$*vS7S35)m z+^E3+o*d2~92+QwQ~h`XLt=v?QPiRbiwK^Dy}%b=)zfr^TA%i6B`u%4U2qKW7e6dx ztyMvzWY%L7kaR#{f5MaLry}_yM1T5_jZMHPetAaTE@a|kZIn2ObA%ybOc73BTggKl zprYp?h(^Lj54-CqU09@bM(~RON0?#7&cjK8x06E?1zPipp-&GYa6$ZqOESV0jQtSY zojKNYOGhN6yYEp7uD#%ylTYU@`!XQD)y4HBO_Wp3Dc(eSf99h_)s4Jcyj9liK`+^- zrfTEC@`jtDBYu|I7g^#+H24$5$*XFmCssDo-}qGNG~>{hOh&llYB}&c0{h9pHRK%= zk5uQ&79ic%m`oA{Of4SdJw+8Bna!Im(q%6N3)j>ihvsoX*XNl9xw%&zluM`82NZ`> zi~S;v3P|)ue>_>L<7!i{i7q7@1d5a_tkj}3Cb)dOB=OEV3Q5vUO(d0Aj-?g;gwX}a$EiI-r`Imyf zp1M@MkDfcS+1k3Y0PO4SYd zBpXudr>BYvnc z*Q*9FW`l;W8N^Y#58dE_=eb@tfH4a+n#}VthOJz6%muYB<4f_L#(d0?HLJ%g(Ga>p z)tH}y*?8r7K8MzpxgTcCfMU0nw=Ej(G_#TDp7bIcd04n2M+2e8{9BYt0P(b!f4f9+ z0|tkB6r@=+crgMLYpwoF*E&32X|n@4QG*6n6~)K|c}w zq~TT1N_QyDW+J6f#)~Jyxk#Z}GCJ`R3YYhm#<5yj`@Q}w?ex`36-HeNt(OfdH&h;6 zCR=D@sWhF!%1?*=)hDe=TF#np|55KPB1h+qQ#Jx~KenMdOZe=gc2qoK~j z-EvA`Al|5bcc-?ln@KjvhB90UR|-UW@9qy&E1y%E!+1^fc0cn=l4$Cv|9#9Rb9i}v zF&pWry69y+h`VDSw1X`rh*&Na0jD-b66w*acaKwvB7KWsF5rt?OZG0FX)^Wk_}N! zvS}hH7-U(&q#8iOf4z>YSMPWAYv}B#(5O|cp(+Gz!(~@em}a9}(T7}9XLnpvVx+M( zg-F4o5_riKBxKS8ioT(&9eb3tV@ayIjhCfteX@~Xgv#y!VwA=r)H>+g#pp?lX$-58 zA9I`3Y^_z4H|>@sdXP-z7ooF3H%4M8O6p1Waz6)5KjczV ze?HO7JyhzCI$03le^L|`CrbxTW8*+Ti-q>+YOPC?&xTNj=E!#e$=oMFZCg{TFHW>l zQ`!@OmXm1of7)2M$!PT5fF816=h2bZyC@5Tl8WXSws%K>g1CtAvAu~gbm7m(aQWu7 zGw$|Yzx3U!Y%c)<8c_jR&x={pVz3*A_)Vw~_N)_9u@ zp3BafvtfpypZ8CW1D4X%G7JI)QncA>cRYHZ z6)t_w3Z#yOBB-Ov=5EH1gbl=~!*DPDG_rD6Ubo^I$!|z+(mq9A$M{)37}2K? zubxP6kyL3EJaFo1u^i0sT^AoSZG>sp z;v(P2RH8f1N9LHlx>&N0O@DL3$$QS~_b*Bt>J^Gx+`EmJ*(e_rH`C0$;_w(DqTEpW zqBpbh3Uqbz68CGM#&hPpQ8sML=ejiqe;ABz-c|ofI^DV^3i6OmA()iR` z#Qu3}y4a*YI+}X}TJ`e@=EwOhQEGDoNcC*v*QAxUa@DJT&;@FK`qC1fMWNE?a5-kp z&|a-AAG7cf=UcH!!HsFSzGG^o8f%6W%9P6Ei4iOB$v)_Cf~M2QUOv7k+Q}2HfAqwa zGMwWnunQ@~rkTfDE&A2+LAM+=zm?cz9+EyCM;0tYIjY*|#MFyrKAmej5k%4BIioK>S;Znk{Y2! zNsZ9d5@u=Hd3|0uVl%8`Ddtkoe-WIa=(Gxp(sQ=dka+dB;HIhd) z&K_ur9G8&a+GOe6lVRVwe`F~y0o|w!4}JCqv?F(C-J&bnGDtX2o7|X}<6=UI)h*7$ zg5>HnJs)Lkjk6F-HId$dzNu}4uW-8&J(DkS1+pmNlZLlNiWfk3JMK-!xe1L2W^`aU zsJiPF+<1~*!rP_^J=WE*8fNuF%7yob8m2#AFWQmweHB9njxn~OR+HVmj!z_~h(_)! zC0h_%rm^c;f4qL~&9-+J8p{Og+e_>Ck0d{1UbwUCwe?Wf;?g@6^!o{!cEaI8upp~?AGwl%v z5d7#4biE93*e5-8B!k!WF09@G_G`vd(=FdkM}f&uI6TK#StH?>ZB=8%Ro>d>Z~f+P zx0;fQ37|TR5&P|yo-qS@4>Oszp{(~gSqD7Se>?xzR`Lyho%7eZ^YsgV{eoXR$^VsX zfApUDqb5wCCQ!sHJ;k1%ez&oad<%6u$r`}kQ@_CQY`IG_!4`4>uNlPzyQk{^d+PsZ zT|f!`wF$rAud_|bpJpN1IXdv?=wR_29oRWK@aO0N=V%GGI`F25`7)fM13g6tJVgi2 z6dmvs9XL~Tz*BTkKSc*;gbCCPr|7_$f1(3DMF)$fhy;_Gp#wET2Wo~6)C?WK3?0A> z9l#77_%j44=ymR48~GeVtCPda07>TQK##FF8J@8Nj0aKziIpnENtDmC>8Fe{ zEDzA8kfaoN4kQuMWRec^S?O;wAodj@dZN=8I_TU%RvcvKlQg+5hS_LafjR{4`rKA9 zJBnvLP=lguJv5U-0xp2DMzj;%S-S!9&`X#=`|S&Z z`cer>U}fwMHIp8su-<#C^LMi=J_YAX-es6p9mbTBf_V-hh*F*w{${{={JHnmqXevh zLx)ZI6SJ!o@`nN2DpbabnOshqjnm0IHk_H-uM3Jmfb2I;u4kiZelwawf1y}o;1=73 zpjCq_c+a1GzL_YsJsXO=2Hz%S&qD-!Y1L|x$Y3x4>|GToAj5n>j~ht0_h~DV5MzS# zBps+ie2f7P91t(rtCQN8=L(EL`IxR5sTYph58)Tni8}Wbv&$=Vr%)YSr5}08tK~IG zFChGXN+AbSY56I?$&yiae}Q!;Aapneft=5onb190>)(3pNE54y=8>W=RP-e%@+R7d zjIlS_ES$xlncxQstiOM~6`0Nxs%x>$4SBBjCvXZ^%L739dXWa8s(#}Dm01L^&Yy!60=n&n z!&Q;f4jULwNhtF5J^$kW_^TRl)rv8wUNzMM_JWm;FS(?2WF-Wi)kFA1l55ibjh|il z7#vWI5#g*rWtbKl(c#zguZf4|P*LD>z2GDFq~L!Gki z0aknLWfX=u^1K_}cwC9GHq^X33k((2&V<%p5j(EK>ABrm@GN{OTZ3gy0R<1~J2e=D z?X2%$tkoVz$zsRnsiYF6pSo5-6(-Q;bK zFM%7kc$JoTNN?QWuS#WwR~t(;Fj8|FwB8iuQuW_ve~O0rbY9nHCnhBbgeG?Ntgo-f zLEG)-_vyR)^yxiy(Q4C=Y+*O7SalK8kqga(5fj%nFg$Ir4nb5C!#=gvC~w>s0Gh`E z&6fuBB@PI6QwfIER5n)0d9i$7y*qYm#G!q5<;KT8vU~m% z6VpF?fBGc+)4Z)s=@cAkmIBmsoP_);tAYGT62#wh`SWTp`SGg*5WcMjZD^N`199kF zr-bRgaFTzi3FdbT(HRy$V)?_w>Eo0GAZkVF3@#_}Y!T7&%wd);e@j)=j#cbhCxImG zCg*B}vWlKs*WEuS=k|y38VHb62ZZLh*|30ke=kkerr9}1apt3H-$r)UpZL_JGbPG9 z!)VNhNMO`$>hg4U4i$vb?0J^QB1+Z-9rNo(y74A=eI14YrHA8T_L;8lA4dnEX{U8# z*a8LlnTSw-JB&`@fcVMu_p{{^4X#O}ou)OJ0BzHUIqp@jKZn3Tov@C*0S(Lc(rGGA ze-^P0=zL)ppx=o(_c%Te9^eH>sGyUI;UZMD7DtPONUylduGVTq6r0wfy1VH#yDTPi zK65Na=D=;!?lkS;jD&xq9-wXvdL<=kYY9blQbCy5SMz)#LK%Fj(r#7N!LdP|4nA*o zaqxGarLk)LF#e#lXB`7D)`hfMEuri-f0~S_=6YWm@I!0C_o@lsqc&Wx5jWS08_i&- zCNFH}KXj;Ddq!2{_~mzBhki=gKlZjD8oPZ^L<OgoizgMereH#0pJ#xs!5W*7ugne}n34 zZ)?0z4AGnDV3Q4fl=ouEmDIstVe^^V1`ln>==#-%|T-l6{_kX5gUm{nzJd znLU2qKi$98@b2=D$=&B)rUZ8wVL#PH?1XcrHt%^cn;5^eE@DObhyVqQOdmLQRoMS({L zOs{&tF;co9Ov?wMz;7nS#~j`I&Xr=X1I1CkNQiI&t3&uM8MxEpQ#zqkZ-6)Y;W8sB zMBX4&ra4-ZHBCmT!u|@vrsw0qXofwjoibHr!&rxy!`?Mi4XK!wP8w{6O#gldSWIRV*(Eg(wG?ubU~%WTJceD5f51<<;1phnJD}B0o}A!! z5OGhE^}i%fj*(M;+AlUYI`qMn1y7W;)`_}P1tI;YCJ_g$?*^F3wk7^+$=aIM#@5!h zDPT0Xsu}{NYBgPR4IX;pwo8u3(Dd}G_|&NO#RXJ1(-F3S>bQ0kdE!XvIiGT^iayT@ zi1)3LE32EK3>b|xi2^93wl)ET>CxFKji50zNLE#xKx!#}WnJ^S(=Y{Dn~v8sb!ptN z`3rPg91rHn;dn5C?6Cku|6Mvm@IfUPrZIZo72}MZ;i~%B%WNO|?>qJZ9rRH=RPnMZ zYsf2Z-DMfXYu%J9+Mm+0Q^OlCLHb7c0yf3E!BM+7Q!HxxinoYZzqAo7Y#8r2-WXc$ z;SN+WiJBRgy-orhf1mRfFVWT8{`~-UQN9~&oETQe<1;grj}AP*GMOVQXBlgFW5~IH z{n66sCc_!xsn>sx^k-D;@@r6$vTxNGMj#4^f2CV@IvC7U!+AG3pG}osK{92Mmmffh zK^;$T@8)SkpiC%fgskh-I?pZ&f0&rA65Za1y~LS>XB+e)e}v>4j@T7%rc(oqC4}iP zFF75E+QF6NOEGDrhU-qbI-osFcLuQ6-$M;=Gd{kphZ^6OKR$cl>Zv1_e?U{}6)I`K zX_SJL6{_{7|6uK1$b_X(Q_TO#?}L1JtrL52*y^dX_AU1=4jhV3@v7V0U0K`TH2t90 z#dZ2Qzn)z?f0T5Ta`=#aOh+>qM3a@?GiZ99Z&y5XiLIdV?aQ#Ro{JbzLZctOA?!Qh8P2ov|Wenf1h!Zv|7HtLC{n z+H0tmG+n$nPH5h489Wuh9YNU1W z_eKg_tNor z-dS5q-d!Ps;ls~TjcahF<>Y<}Z`AkZ$SBmG@sH-!YX`d ztGiVD+4W2j;RY@GY9F~!`|!CEBY&{e{hm$GuH}e|(wP#jljOw+-tuxNX8RcJtfF&j zi;iY)F%?|o*=Pt{`S^7IjBgB*z2~%Jb7O6+Hm2%-{THvNo#ZRn)_0*p_Q`9xRa$s| z->XIKYt7H#ORh?8ouK(1KffnG!bB!{@gKfDpX88??owR6@iRBxQQL04uF_(=NsH;* z^1@1KsCE@au3vrlnBo#xps`&rF@`~Dy~K5Tg|a7R`YhFUss>OdZEqXyU~DOWR1(+T zYxNxug%J-3a~tG!*y3p0^n7?cdn{Ui`ruWua0#jDf-4z!Yl~SYfOuS+#WjGb&gufJ zz)0r_aKC-UK{>Zhjt-Xayx+z!!{TL7ySTV#+{kn7Hk@g21JNo<-BjpGji$m;8XFCr zY`y!>8GT z;8xEz8WBQYzj#zIuVk8%!4B%#h7QJfTZ10lGd01_+(+FXf}0!7Fl+{)BG=8l&g5 za!?gPul2nkUHau?b04Jx9A2g4;mCv_7dI1ZPp$ACNNwzN_*g38Ra{(9RJ9Avjx6pw zdISg^0jzsws^i9Kw*c3f6K~ACe;Y;{Y~QsY`e~LQk7w8C*~Bui6ED%ma^$N`9JCr% z180W82yUo^H-n28xj84spxCCwFX^b0yn_*y=tP%Hv(Ho4Ux;zBF&MA1e4?*Kd&zD( z24uj$5HHHkbq6=I$&JEDTdhqfci&oDVIC#HVFiOhyb~lN0MqUE;rJ?>e?X{j5)k+e zKy=)E(J~7fFNBDV)wR9um@1Aupy-M^v{`9b-TjV|VneiGxpt_p%ajE3%XIvoSj^>E zvT^gLe7pL7|E-e4dxxiQ`e$lMoNOPDPp=9lQ1AWzN5$aq!8JS+Xl*Bl%%sMAPUPHv zk&hJSBK0)g*x1+m@Kk|Ce~$M~-@xsGY`_Su^lCvrpTJq#Zzru6#biGnT+ss_FGxM0 zsvEEfW=lGa41d^BK~;8#Cr^17m2K`yzue)VX(kqj|3s%Tij5HzdyR=D2Dm90hF+bsLV^0=oFouGUIw3DR$3jsYe>EKjrEX)dP+E4L z3ecf3#4GX-{9bYFKr2^3#d*rF5IGv?2N}UlOBHpTOQT46!#u%QUBjaEz#F}1@UIQ{ zx0j28_9e3+Cx=nbj-KVm^*j4)G~FT3i(-_iUBzWCJas8>%tO^XiXV~E+g1W^l(yHA zvt0y5m8|dlb}!qUe~Q6<+w1+))BXW%R`O2eNMCya54DocHZptL({QU=zj#sC+zSHg zF~j;V>ov11U$|1`KKa6d-1a(h_#!B(lzGl?$roP5MSS5?-*bLhja?Q2y?-uR)yrvB zCb@s%;XrPC9YvWW6dwN8=lqT_Nkie`Q}lkg;*Mtg| ze`aPOu}TMW6`aT*?(!hN$QkuqU(zY2(u1NTSyR#TQxmY8y+B7wu^0HvZiea*ywdqG z)Q`+7HFZe5OwkcX^=h1Qz-KlSbb@6DqewGv6mpdsRO=6MORO4$Z#y!C=<0EbYxTp5 zf^R}lu_b03fBYGr9#_~qWQUwGhL!hffQ{Fy!>aT;IS$cJ*8Wg3dNr@hWrej==I>Kf z3%G5k6Nk~ff{G)C_$4RXskA^m_YE=cymME8deHs4+5>jA`L-vt`=4_IyB%~EnR}YoZL>esM47z>SbYF=t zJP;4c6-zq&ugDjU`RzVALjrC`dy8?0Utxi@F!D9RBW|MwT%w%`8cuQrqbvz!5vx!j zjM7HJf8gUE*hW`14H~oR{n`aJ*tYD#vk1!XaYL{D(b((Wx8p(VZ3w2Zf7E~eYJV?j zqms?Hg8YzwlKoc!;e67h92L{fO7=qC&)(rFcC_j|(+f+uVAljLYu`S|8-9^&oGw3W zGmc}Gp7sbZw|GIhw7UK`2DI~zba7K~4V2(9e}z}uwdhs%>6sa8p)~Dt(KtP;RV|dJ zecI#b;(o=se<8F*{mU~~buLeT1-mGT7s7G=1SOBS6&#nTwsCQ5EZ2)l*7NM$A zje}IE7J_Nmv9teGckS-j4L~jIUSFz5`;^!36%Ato_a(kCpK=yZD(Wn6Ej|W1 ze;RrS@M6&{_FRgNS;Dx~etFtY${Zfdr~Ck&UbCycWIWCBZ6sg&X|I^WF=YxJq$R{D zgY<*h$Fu}PqP{40UWm(Q@h}*Uc!m$X_Lth@(J$dK`7ZR&`t-Yc+qQlTZK~?-)?fMK zt-Gi28?e)2HW_4lyQex=3)F>oru?bCe}PU6<~4%PLBn@hdQ*%|ZT<26e!-783<)^kxpsG+{o)6GA$yosw6q28%FII_14S#|!3`S6oL+ez6Iz<-UNs{-#SKuGiJIk(bAn9g_jvM7qe^p)o zY-;w9(e)L-p=~A39a~kp4{Bqz@IuMQL+yf64^4PA8rpdQMkTX`>F=V<--&{#%Ai+G zdO!PY{Px?#0&Zd>@dJ|D@as~x%IKj%NKYL&5FG^nShl<%%>lvOtQYaxcDcBLOId_( zzwZx+xZ7FxS63LgRe5i!tlUkWf0^eJWO-`26p!1a{UG8@Jh^$qV?*1Dnk{`#s94G* zFQ;ssudIyv>-x)zsg8J(B69Y7FUIY`x0;4VG&{clVnCh0YZ^F#szh<7gU5D5ru(6(_xX+U3D{YwwE@6-Nj<=JtxYj!xgnxb> zKbaFr za*Em#njb4nmCD7!B`o(zta%3hvy|$m-rgsZIZ%5Y5m|535&pm^!ESO|-O4=ovn$OB z>MYxkA&}xZd%a>tQc}`#7gpnwjeG~SfT=$Kb z<~E8Q1gl8L;8dx&9)o6DOn;_VyTybn zwf$+a8e9O#Plw*D^Q~l!26&S@k~pBpUUoUjvM7|bWb@zQ4UK_A09;-USKA+lN>C%v zRyXV?y6^-@VL7Z^e2z=2%pJbfHpyf;>-<&mNrdPyj>d5{)Tp1nzx_9rE+K&@zHbj` z;JT07mWybRmK{R1sZH(dG=F*1KRMGXmHPUsP#dE5wWL^ciT2XR$SN5}{N{UF7~?P- zW&X_zZlBg!A9j1|updjV7jF3?rJi@Fu~2&Ut&t;btW@Gm@8V@5rjoEq2%sf(513c6Lpg{*hD(x0l+rNPc%o&pN!Dt#BzTL$z~GJk?)|k^%(B>}4zw zi>wjn^F;mR>;s`;B7Y>qWsPBCc~gdH;pJGz4JMZKGXYZs+M!|6;)aMSV?;dH(*nap zRR*;t28xJfA~;ru$*%M%dP2{EE4G_%xV1#MDBiZyD7Tg~XnwKVUP3N*0z%)TRnA$z zjLfi5>H%Px0S53Q=(?C>y<`vc5cZG8gwzkYn4D}Jn00Hxnq1PWWBJt-SsBk{?6JRx zby4xNc7fb^dAN4JP?2kpmEFC&bJc2}pBJ!u^&<=5?PGAf7#4INMxnm7yS8+0a$aVW z-wZwtH)*}qsb=@DherpFVvi*+_;tWwHi4H+UjiLH1wB#jAL^&PQ%3~JJ&)M_RHbxq zm1me%K8Jvz$zW2HYR$rFCP#Z9rHR1++zy!J&+C6ljzjSF&A+97FPElY0v~_qq!Eu* zBX4!8BT~^xLY5&>h%9=znlSSB(xh49JSY39Jkiyk>Rd5<$FtoX&Ug24#=FOJ-aVi7 z{vpnL!FC+oJ7Qb0p(Oif`Q^4GVlPV|LT&>MKQ@IX*=r<=uoPv0y}O1hk_bEPLJ@=c zTH`Gj$)?vfFI@6tG28yzxD|f_3G&n~gj-Wc4h)*`mA9`#C@*ypyQp0&GQyZz*8Jgb zqp9MiHS<)vJ;-U2TX-3tu!!PaU*PgDV?Kr(uAA2Dzh8m$#;+yjD!t~2^NtE5) zn(ZW>5`*F0)z_F-6effNeeGtF-K3b-665=*cGtbYau;1zl(gB=mE3=c{O<_&)t%%P ztl^4ox{y#JL0)G!VxIikLVyM01L|yY7d#RUQVFDU|oNyAPI1NrS%CwAxfOv`k zzJHid?F7qu&ETBgsB@+RhiA2f0-Y?if7mt2H^*>)N0l1((H~9o>1>#pZc_fx2t3c> zQ#FU_IOS;n#SG%GDR#_8I>UwSbLosmdQRWz#9K5Jq$8XvMQ`0C$MjoplSk?0AQBui zq1y^0oPnr+6xr5_(a@;|IX|>Cp#$(0xVL~WtO-0Y!RB3^G0KJz0P&qV8RM9qe%>$x zlQXsg)uxk7iExDudgT%b9ed8uA*e~&8c*}_j85Qba;X&MF$Y#qLWCJ> znwB46&Wg!-uJ+VVS8(=@63_|n;!ZK)X|^@$17h}njq}pr=Mx)obg69#t+P_lO0HyQ z;=vb+F1K(7C@9@8+H!6t#Xt!pIEIeWK~^Q@b?Ooeze(B>e-9DqN^Fr#Rf0o)d~kC7 z!yAVPB=kvq@RW>96hv-|g9d=_VUrPWtFcibx+dYNjNcmVx z1P1+oVpJ3p^9}k?Vl^v;pl|{iPLQ#=2Z2DNzIL-5#_RNUrAU5X-Jw8DxU<*!n0}w3 zMQn_@=p4yvrb2c3;riRFCP35A0w^cAYXUTqpGL4%EH{lUZt3hZGT3It-rLm~_)?1r zhvS5tLRNzHa0tYsl6g`%V!K}cq7&c4|NgmumEis|+*;q)y>qMA-TiQDEzniDRlmB7 z&OBxi{Rz>8V10 zOKN&8rxA{DM`}aZiGbJ@h;(syT^t+!yNft?gcDU95;cx2h;E#7yB?2qefH=Wey*2zdM7qCuZ6!hcj6=WTA4+*<59$~rwOy;QEK5cdUsJl zNEZ^kR%ILc!F+3^>uP#L<>HO8l|IgYI9%$yhIZG3_!X<6-`4OZCQ9rjiE;f?3_}%8 zl#*`(;>vK=RD3*}9$$dk_u3|5mF{{KwSown<6Ipf-gB|3u0wJTAvihm_$?YLHn%o7 z)TZg^^d|i@cIk2YrvL892Joz;Mdhn&SS)@hhBlsD^Q`RfbslX8Al0H$JhIDw)Unmy z^$t|Q#Mn!_fuOT)r{FJLU;Q>J&F(>p_!ExjJrH*|#{&{#!BKtHY5qsjp_b(f-E(`1 z{j!2h8a0ix%mZ$4#`Bukseb1WWP0yO-nO}!4fT8n}P zElS)NJ@Z>EpdhkiIM1X(71bwyBi1p&p&ugJ>!jq;ws0%Wg5*I$c`F+%v8DJhzR@}E z?=Bs&o_QrFo!=#CWB#xDz4+uB)bjiyW>OOmx-Bt!*5%?5ci^s)*B?N$|CG*ez~fc6 zLlaIqjhmt_Mss++w)6{JkL{qS z-HEmm%PQ6qu8`WB9r)A8k2oOX3JhF-e0X&B{_XzmQ@$gy7{Y5Auj{T;K2PHCkrA*H zxQ6rk+_t6teA0Hpz*}T`qSgo<-g?jbC&%5z)v@lGVso`7@I!+(ePsTcR%eCfIo@?; zG2Wlr4~8oGzhZs-^jQ3V_-6BGOCgJn<}=4{N3Jn(6(3(>N5%AT49K>F6OHSW2}}&< zM73fjN|>nQ)7BnwZsHtnd!FOIphw8eDD@N@o*+cc(Vibt0K!y}xbaDyR1%POniFdf z66*vX6w$!im0Mf4ZVT&F_oMZsi1t)FTA|hX!#zId^e<2MKM_fcl5Xy|DB*yacVijpoD=R!n zVdMn;SuY>rw9;+OowM9Z*}TO|+N|fPHE)t7wSQ9sa6#)?f~lri=8GC zDlXZ(ZjE_}V?#mWqU4DX56>#`v4rqgR&*>cIGo9|pM}8iNEHtA^t$+von*syW5ME6 z&!3E=;#2Y|d!mlFmj$P17#0{%0i?qv#iWM-_Fkm{yb(<)jq?Zbm{7_ogi{jg;+Xu$ zvyqWf^DdnuiY8A?7SV|uDOh0ovV8ENo0lAD0v~@#Cv!CEqxVKN@~7rJ=@+?&N;fA{ zB@JGfluB|^$y4cl?8f}0sLsxhZL#G;gxWKplU;Q3q5KOR;fsV~g7Qri|{%Haq+hCS*A99LtBEY)lSq-Ee0Np2qHU`IM$esR=q)yIm+tpQ6BaNrTPZ0a`jlVXC>Ngv0)h7 zZ>zd~OQf9)=xg4i)!XZ&0>+%Pz#fH6Ya@S;V52`bU^8i{p#F|kxib-d&u;a|v{YcL zi`&8JmODlU1@XJ%A+Zf#v3NJ13`T0ByoZBDH+pyD8zaU<+ z5v!3i+o#jwrtL)Xs`csaA3pED>L2aypEU64R+ZBcXd#oww%z^vbqlBNx*l;LiUc%} z;e4E4=L4ntEBeE}ruM=9A?cE*$uPT7xZ!8el5O%q6Gqg6GupM{KB}gmoFF&%7QsHsQng)7^>{1ckExzIfaZUSEl2ap zzO2)@AZ|+Ox(S?hbB8o+12l`3t9XuEqJs-@#nJ;ecyZ}RuEW&#T(JpsE<6+!hQ>Ql z+&qWU zoIH;Oa??PzFv=@DU1pJ1Rzcm)pcm<&STS?o8v5f zOh-B7-s|e?&Jxr`wfY9xjj`1p>>hr*+kcakgGsKwDxf298B{cbu#}K@ovgI4kB?9H z^_n>X)?nh9hH7{~sB<_$VW#w};#2mKXM=n5M9*~rsZ;&E3K*!5j^_dEIF9Jadh!C! z+JI=XM-=^^3FHX8RG)vZF_pRR;BFU!R*n717f$T9NIF2PRr6SE66&W&My>0>uJIT( zc>244D2r_dPfyCk`e7l$(uXUaX49xswh2JQQj8T|e0ErYfsmoHM%3*fcpwmQ-xdUo zxo_KIdM0ckQgUeg*gr&l%6zHt@(y;fZgM`$M?>=Sh8XRNNpXKYDz+RI<4c|YRk5pD z94MW-YVA$4%R)(S_!~~*^e3(qG^~ljtsA!Jkv4zvW6KSu;}OBczN`+Q;}5SY z?148SMq0;k%_fs#0{&iwyLQNuisaVDUxuSjQaO$aADMu+6>Qnms^G84Wgw||t^c9b z;JGioviG(RNg1p_yMU1Jwua67cgV+0ZNaHGZ_%PbgN@g2Vhvnm-!kD|s?1V%RHz$B z-bNcEICg*0Q4o?Rj}?)#Yv`4R`j{l~t<+;;KnxS;=NMCQ`wf!WHErurE*RV*EdbBf zssixO_g}r*x43U)kQNyZn8u6LVA6|A=87XwJ2viah*GDXMI9S(jFLNX?Zs&!y4#7U zMhls&_Eh+f%UE@@EyP50roDM}rOK+4DkkcceKddWzv9=IYeWjNRN5e3#-@p%8c*RO z4cCc9;6K=_I1U>XC=R?@8FJ3z@Zmzs+^rW;CKlT>+*Mew5djZf0j0~sNJ32Q?Y&s=-7v}# zMgmQyAYpSM-N!n{5Hr4z>6IdpEhuCXf`fmSHAvA5&|c8Bv!QTbWfxy&G)KNmu_K`hVjj7?*RXe0=(I=t6ic!C4F%k_e=JuxMdsiEOl=T1a9mxr@q&nu9>>5>$ioO$< zbUn?<@(8~${#ZN)R}f*ZhJ9=-0$P6zmds(J*gGkv&jNN^mcN{xPu!e~*$6Mp-MuMB z+-f=R?;zgxP|Iu@UG1>8iw-+Gpkz7t_t2xXhaQM!9iUSsVh2|i-1RkxYdd=W;#Ncd zI0prF)UIMZ4G9!C0@kkRbpZeqsSMmclh%oJHZq=T;F#Eghx>78@nC^E5Zr%{azf`MPq`7u|D^lb3d=-Ad5)v{yHQ z+9h^NdMIzvJ`-#$;HDgYoF`SJ*ZOc#ItS2C42KE9fxt4Ftb)ZLW}r&R8ye!wRg zTFKji0PEaQu=}4H?A~sZ8s%fDApDV2kO`FNpN--ock$r}Wp3r!$jhH`{MRmDP0-~J z#Yizv3$zJWUzprXijVma^~^x{QdJ|wr3tvQ_ThM#efHm6Y<68>orQmAJFfndg!;4Z zXe2oe^bF$%LziAQ!b&LFJh^7ls**FkaUU19@9NX$PHgLUJ0<^paT+q?^{gZ8P|&4Q z(x+VU%P5AYVL>FY*JzX4KFG6t=^(C07QU3JU|3c3Zytpwz;sv*n__N!rs_TB& ztc@R(rdccVPN!jY@6~_cLRk?be8i!*MsHd?CqFw5#}yQ~M{SjbrkfKRWR^vNni9jprd+nBN?~I^5-8fP6hJ z{ey(_A??j?icvl=_GxO30n;8NzHE(WVl#B~o6h$dVLm@kr?cza{>*<$#Wm*VK{ise`Q1>*s-+wL6XOO4Sewd`?xEQ9BT_xAR8_WOxCM~Z%6d%hEb5S3q;bjTqRZ24oPNmv zT8uti*?xad3)Qec9Enk!7MJOGG`qAPW*@E;1)rzqGw>v|$eo?5-M`FV0gSPQ`8NHM zr}P`YJzccKj`rX7_xgW_3ycu|Cs|%c*+|W{Wx>I2I+=)-v^&YN!IfP_lR}+tUMUm| zBSgGw(VI3QZ`ySzME}nJnSjYtJ3(5X%I#I0C|!RaO36`LJutVA>^bRExVZ;q#HA3N z3VnbT&V_z8ax#>TU+~-neGD}t2A_^_2KDD#J2>C_Fu+Or(%XC3pxQ6!AIPUGXfc^V zG7L76IU7BrVJOnKK1te&awHrct(RZL$iz*vUnB7PGo9YM+n>F`K;7FgN;lg5Z4$li z_C0?E>BwwZNB>D}!6A>ofHR&Zo|jh24%xWlX`34;@6SQmf>+;sg{7$XTbEopH@eA|`gebC z4~+5gayPuA3=&5+G^ zJyFgmvh2~#*q*gCnUi(AURrEfyrxJko3cmo(ft|s^Yh!DU-F;|Zv~*x-K2kL*_q7j zW=3oPg+ifFC=?1+$u64}G3q==9#}(kKqasYdw7eeCkWf_GAR-z z&~onwMGbXXEB4)=^4S^O;ER9r1?pLrQ{i5==>(?+W1>weAS54c(R?OrNzdFJX_*uo z6a>21IXFGsiFz2twzlwUFhY8%w&C3uUabonPggg8e)gS>yrdE{aC4#St5{>!Nm3Vt zz9()&WHSn-`E&ALNtXQ1{dmKD{5yW!75{EEdoTAqU>*!!|gYvpmsy{5R1db6IMED;tFl2ogvL1c}9=93+WMREwIG>)+mY`>IQuasHUTm zZlJZw5)6#uS>+xQz>I%jtX^JumH)7xbV|@>q!Yvofai^c9=x&`s%wqkBqw0mm@Ga* zuqtU1R9ctJ+CI_F0pKeur|&lqgkWi+D1XrNtHqh0(Dt`kvvrQ^NQflE4ZCU5vxJi^ zgYlwg%kPs;l&rOm0s+|H#XY{_PF9Jxzn(Vk0ZRRnM#G)BVDV`4RA0RA@|W*<0v{S!tppG;YrjdN99x6H{I*@2 zy6qDGDaL~;LYF3b0w8}YxK+>@K2OMVi66Iru^0$Wc~D(xH5me%1G;WtKZTUhq`sI zH|#ZK+jh6>Htvwdonforl(jm#ZD-Wa)VFNdAG(dZq;W6nhCO$OC3yEp>+WFKX&MYQ z-u+HXlcG25xUKsx;>@U9{phF%F2RMcm;baa!L!J)?O(1n-=9(UgFne_hFE<;gxGMs zY0lciVc5QxnSp<_+HJo*o#}2n>-sa@?R2{(&FNfsTm50sJR3!)+MDdi=!Y$*d)u5N zzIPk&$t@w1Vhfz($bQmmsS;g7I&N&RX#4F!uRk!nW|mH`*J*e3=U!~lilyBiwl(u- zwsjY{T74bGR;_5=YO#H{Gs8{&*s>KpXPuU9)E~9Gc4mJft5q}_WL%d5mws%K0*wZl zVQ)&K7K`T=)|tSk+s!NqZJ(`CgtFeu47)wUsRJ@lvC`tp3z~^3QLWmv*|5t`i9acw ze!DqR|Iqfmczb7lI$4(?b-yNBcucIG;rIS@ssA=Q9nxcq05tO z^IjbL5uJbZUa!YcznArfQR=l@4%Z9~qC*N`*y0xl0C3r^&CySAjTu+KKkdxrTaE#G z?LnLbZAU0{TRebn2?kvsgHR@km6P!_xGy1%?sOXn>0vi(4YOb|x4Xk`w^u2og&-hI znUU80&WNWdkqFqDHW~=kgk4BP3<$ikti>QaYMX!E*WVX$#EU-Ltu8yOeL}mah=-ZUdhERR`f(AD%|aapW>#=)RI&qw zMnh&&1X<&vvFDA}8zkH8o>rsvX5HkhGa3l-7^vR8cDrXq&nSA<8I4-qZg}Dt4Kkuf zMh|~Gqv25OvHrYwN4;URb(gkoGxOLTi3zeAeDt(48jPs^Va_Whx}S9d?Xo=*ho4yMK5ac> zsU{S&*tiES&AGk>%|F)>O7z(p@$*c#-RRmCBOVT0!C3@GTyg|+=6cx90%aWNoeh6$ zoTUK&4v&8)j(^V`{~*w$ar^^bjk>3`t6zz1Vz1~3)si`&ii6jvdhzxy zZgmQ`cXr8uo|v)q5_jCh2-dF~JT19qO<{bu#H3++cO+HBB^o;|w` z+MPDj?XV-Hdu+GvIqln_hNFhf_J3b1)_umc!NTqI`~6`sHle#&yG}QYty^epRYcnu z&OedH9nx5A*aKgO5gN?U3g_su?Fgj9Lsq-hp*u@t(TWv6HAEAY))J^vo0-5v~M^Xtv9Xg&()+!h}q!}aF44~!Hv z7dx<#BAxic#pXkXIeR6@_gv(IGZl<|$TP`(uUh-aa+8%i><@Z+Li^o;W&Bnrx?qQ+ zcF2Ik0lAs#_M;1SI2?9@#-o4MFy3>QVmlHWs<+w)gO=s|Vbt=}5$n|$DMnzi)RRBx zMi;8UwG*)3pi_eFfHocx+Lp8#QX~h1p1;cssjlaba74*7G$R~z+VK$*v$AN^340V< zWW2|=%Rl1e;H?h9ND*GxhAv3;y7+$^zETiM~__u%Cwg(3@V8KsD zn6w8a!btR;1y`ivz;Ct3PMI)Td+gpCE~RUq2xK#|{-8uiXRaU#EVKQ7RyHt7)=?{@ z%Wy!9{exZ^S%;%xU^W{HJ+Fi(dj8P;S%INflJzqSd3Y_qkquLvTZVyc3w2#%+8V@d zn7#IB6#6}MJJFkX$F6@R4$+}qsZS(n+qc$Jt5&z&%lun>A&29L_ncvO!>$LtQQunR zvTd6YJH)|(kh(vJVeApR(;I|d69aIqspT~>>Ve5CFk|+tpa_psU1zR`W|4Mm^KM!9 zJ$LvWhUE4#$oJ2=uf-!{OKf6@7z&jyJYtHytk=#W)--ZJ9rjr|Y-^#3##(o1Yq11^ z^StPLG#FG{B8FnG=UVrBtzNYyLX4a5yO*(jZ^Ilg%=(?7?W&ixvN}ExLouN6I1Q+_ z22m@5LuO*8@6+~!ItDyIyT?s(FY9+}m!W|I6MtQ?_6PlNY=eIEdO0JF2g6p2ESgdD z>Z(H;jl^L-WL%-z+ON@Lg=pPnE?YApS8H6Gb%jdS4-G?EcVro1IEbAvkX8)lf>!(` z%odiMAaSeJ3o#ab4&xV3bc#?}3)W7rUGL%x+A}T>g5K?ZynWZfzi(M&0JZw3JxO(cfvdr${Q45?nKa1vpMONs5ZEInPZdjrw^z4%b-2SfifPWj6~L5S&?}!|jvS{eBiMxn5S%_kgq(BV+n*59}Sq za2Pw^!>tdq+{IoLC-ummW`E(zY34BBf`70OeU4g$qlC5>k*GdT^+w?y&~p@wPFzMn zd(LHCh=>@WJiZb-Sm;R6Yjuh5Qpu4Jhn=~@<~i%WWCVsf+r+`FleM$(UQB4Ct?1rJ z9FaQW91+grume|C5q9s;*4;rjY~5`STGjiqz#Jq{fO)qYZ{4G|OEIf(+sJr?6$Ovt!1E!y+c?zdafGpsw;tv<&o zomTt|>&$k?UdMDv`z&^)?anuhzwZn?`=WPhG+yW?M%TfxKL|}}-FCl6o_`=kjjjW+ zu?6qky6tYS6FHqwBj8{-vX`;e^RopHva0u%`3 zDO!tzusz?#?6yKF?PQEWuSL8+<96FFS|LJnVmq^p+Kt+6yKqk(9Kl8Fj`dp|Mm@;7 zV02_mq2Nsz!F=G-oW&fT&N7&9!)7e{?1$c)&}XOQjuM*nSoY|5B7YO?P4SSt+aoq> zcv#1i*_&i|{lc{$wTE>c(7~|TX&%H}gzszJn(*11n2W`z7_#DXx7fNP#>jp)%otDJ zey7tPR^GOB`<*`L@hw_+V=kv%+M0OKcM0aLXzL!uyvMeN2aOSBx$jVIK#0}Lx`WDt zDKZd?@NlqYHx1F%kbm))6o|HmtrFX^XvP@GV0^vE=_Qy@W{rpm9gI5B_Rf^{Imd2r z@f}2U@?M=`$4+1MT{M2B>H0MN%yLiW2-w6dC(b^d7UOx{A7#VHo=vPZBOx+Fl7iQF z7CA#vTg|8sFU=XR?@=e}m?02uwMU%awa||uV!G!Jx5IdSi+|yMwnvzv&#cGx*)P%V z1$8?8P8df((AF9fO0Ks=`;0g~llFs{`Nk)7w@;c0y)v$yxD?L1%tHV%+793TzE+8s zIAfD{U1G27hNgzBJsS41>UUlJtUqE~cSgNRlUT3c?PUE>KW>kDS%k3krB7dU&Rlk8 zZE?b_G>M7Ut$%*_SVFY!4O`Xkxti^zQarHEhnnU>VC` zy?&iDv>Lx}=g0B)H&n|7%zmrK!5n0D9-{(H!&e4ClXkaM&k)SVJ$M9lzlK^4X!lwU z*R@IsCx5mgZ)}#{_b%vEcC8I0+^ChcGeTuB|1_(t^P&;wxB<@xM^Sk;Y6)KD6wpDp zRUK$WwC=XqE{9Z^i3qk)OROVKjt>XjSeTYRZQUdBb;Q2bZB@!M(WpflWv#e|GjwNz zeOKA;cR$nXanUE-P%#>MdlbJo%bbA{pEE+H(SInu;5$zHJ`)K`w2#I7@i-&ne1rl2 zZi(!JW?lAub#&PGvY2mmhOVmoTqYuBb&f7m^{dO%#itMP@QozP(6zh_TzAjARU^rl zpKHVRDI?PRX(x4&N}=tPmM3BIqLn(Sm^4&3Q<2-EMoOkw7ikR9O>%L`1WjdhC{5Jl z;eY6)YpP9#su2h+RG|M>Orw*Mx`vStmY@Ij_NBAYP* zKf2PXSD4h8ax%E8srG@U9wwViZG2)?dRcGi__bnUWhA{i6N@$LOsoeo^%2?9#3Io0 ziItetnOHlx%8B)WrXD6+omiSE*d4wtDSw|}5}d8ZB-`3GCYl4Ca)@nb!Y$y^Nhes< zn0Rl%Rg=#FPZ`X%%LF<#v_}v5xnlZNaIZRZFRRv>cpqpQAhV?@mr%>+TOm_tvdIW5 zXW9pw`q*rBj%~Vcmje2G`7E0-P&KAm*R3(nA=orTXgf2l!In<7!KcPtJHxD+?0*n& z8ep{D>GqSkd(?1?D<R$5#a5b$H98PHq0;x=YKj-|W0t zNtSGFvSfE4OSV2)vNEz{+g^SUC4VHO9ek}?WEfwh*1Ce4wU&|#GW8MJ)`9_`6>9`B zskJQ3;Hp=G3pDjG+3v*32o}NE<>!j2Rnaf%%&n|iXL5a@X@Ja@rdL8OpI?Pcoe3r* ztejyVZ0cjP)j9UUE%=1+ip%HR58Y5>?!6{8cp%zI8bx+HJmEnv(QKE`h zqTQr1Y`4ohPTYWiUQTHR=eQ9F>M)Meq7L6gx=3S)Zi#Cg;N?u?VpxY~LLyYMOr(=E zg6Vb{1|N|JB*L#13?iSO*I^8-S%)D!kg1QzmKXqmmQTFIq|Suf!BtMQ2Q>9C+3Liq zH^%VL39w3{@J@Rr!a17FT2KAv^hg(F8L+eAA`qZn_Mjoeo=KGEFL zau)L5-_&3rw?z&1iFJ|25Z#vVq6@y1bvztvFb+e8Dz=Grl14DybW5#AiEJH8WSdbU zTZX0G<;z9C;;6AsRMi>_BmkNQ$ZSWNDu1Y@D?uYuV+qg^R`GrS zHubT&`y5LLZ}|@h$^7zaHeThyVo={J1f!}|OC+kgb%bF6HVqNlngCRQ%SE5YsE*L1 z1FaNy0pK*iXx&K%#MFZX`S9T1{xzRZ>M8}YE!sebMH2-1X-J_o%)EI!YGHh+Q#pUgN15ap0>K6ILQS(N+3 zRy0sD5O>J8>a}C9m`PG3(SE>BPQTNNO-|ckt&Gl2KZ}yLO*u+XbU>0N+xGovJfSva zDTwGuQWslNcU#f+l&41UE|KBrQ8$Z@nzGjj#>wJMYw*{1w89JJV36L>}Yr>r&< zL*?iT0t{1?zHYw@(|^lJqFge4-D0yEit zk}ansObG}R!By?xHDLE36hPH6R4Yc#M(?|?h~;O8r8!L$RLRw=qQ z3*p~qSW`6bj-t6lA3>C2X8DU&oo=@>gyW;E3lwKM?TtvP$>`oJdZ#gCdrns0*p4h3 z4F?>ZdhKZQ0c}3)uqbzj{eC0}6_J-&zuy~@;IyQ9Y}L_CI_r;|5ob2wdKTZBMXd;# zNq-)zc6@7wd?Cyx*M$%zv00{@bcT9Mu;9AwPB)G}<+5R)r#U>BZ}O>Q=_W0f#)#$8 zUbhn+f6rf94F0&ZK2LNY=v3_B+|p{b`!&LjC;;s+s)76G(LcwDl)GZW^ZDHp}5E7m-3!np$1Y-#F>XM+6zTFD?irtUh@JX`WekuQsM(j@5@7fbzyNtdAh<`@O z(zoA@V$gLqmi~Yf5S^}#8#pK>FwlTykzs^I?n#w9T;}=N#U_f}pbN<{1FnPQGeeVc zHyY+rG;favq*2etZ;r;o7md0tu1|5Uv5~5y0sBPrerG@$WrJ8FGIx+!b;O*edLvl1 z3YBQ?3ekKhcqBZ2^hP8kc^1{<27i!|jX6d1FdiMtdQ^`a^jjkid5G*C8#~eC27^wU zL%!e2+Og(TqZ!VYEGEvVM2aX3PeHN8|WYe(OVS z6t4eKH%9%o%W#7>Oa75fpBz`{+$Gj^me`OJ8{1Im2HmVP;Aw7y<(G(SXn)`3=X}(u z5sO_+I>cP#Cap6X)zRkwz>t+p-B`eWZ^2k5S{C@mC-(*W1 z4k4r3Aj~r{YTMMez37$*QGbLxA!i8Xp)(v-hJJoD#C&eS!V}H=gGh8^I;le;EPJ7j z0ttrugK8tkaMO~X4LJ0X@0j-6*ncO6m>neSRIppC z*1=!!Yrhu?0RaH=^RYzt7+&c08Ow70PB$v&9hM*T`aFPs9D|-q17@dDG^jAqR_+wT z{*W`RfINsC%RB=22c3+?uG5VlTRQHd%s6@0>2)L8x<}xVPyj>y5davAUca2cLt-52 z)F8pv71vPRgjYv3RDTu!L1G#@*hV56$~s?7JVS8OfTuR-Qy_n;M<)2C;%SZaL~tBj z0D||b1Z&lKO5=e`eI(X*YXPqwe@x|1W+V=dD@*B+Dxb)B08IRJtQGcs=uBq?dDwt8%tMgnl0GWo!+<%oNHNf)cni`V|wsZzp z`CKyqng-b1jX7OtS-r7k>x?a{IkqeqTej7)b?S|+v(DH$HOJNo#@5;D*t+$`)?H_8 z-I`E*+};QZ8DQEx1Ck$XYeTiVrUJk+`c!k>E-Nio&5noRA?^2@@Y+ z>S1v=hNQ5vI)5X|>W?U^HKNQL(VccVG^QkAO`(G{CYFk5lExBK!H}k6ooJFF(3C;w zuAFHgmPeB`$W-vBJHpCnk|EeM#OQ8}>VeB@4lrA5fLYxEX5j#{4IHM8f3$iTV*Z3v z-sy!<1uqv9aa7W$tpd+WkB?4|4o{C?JWtNR(OHZJM@ESM+TY)|Kia#=x1S#8s~k^D z4@^5e#N5i(X`+3uzy0(lL7E3xreSUP4>ms)s8)8LO!D*TJeLDI6UW6wAMWrRIi6fj zmz*@SmvKlYn<#39` z<1Bn>jCXapIFw%ItBb{?QS6dwX*b1OxVXWb7OUm;nK&e4ok!*WV4XwVMxFgVkb*;2 zr>p6BHh=y57$DWbSEKx=8<^wq^-P?TEs=0W z)KTA`S)7_=EGobNY?~sV7mH<)j6aO0v+>)RvKV}~_?UmlmuYgdxK7T5hBGeSCl|`l zU_j=;S|DvD^x`T%o1Rb4f_W>Nk_gA=2=BTOFa%pDQ^ngHEEuY0>fwiD`nXuWmtDAp zvVV>4H&^ujt3p|RzCo71x|-c2mx5l?t69$AXD8}MfgJQ=Inh}>%lvAwTsdTzt}L}J zk0{8=U#7+Nc$OwFu2$1C_~YeRY))x%{PN^f{P^i*nmqpg`1zmrEDA~TO&Nq(m;D4}SO<+JU4i5@gze{Q4oj)Prkgpp3ZO&I>}eJXc_doZi1{Ya#`g8m?lxe z7r1`j<{(U#Qy~aK#xA4J(|o1LA;1=bK6YPGt*NL|J40gy;%8Ux5Dq%-?i6mxS$}Lx zK6rU_aKh}#(XH){Tl6OTHpS7|Q?KXKd)ZM_NyPD7%?ff&>7@Ml{P+}jMCwm=Q~Xh2 z{6G~N4l~1-BNXb_JHVAla^NmHRv4hDHT{qSZ)i?Jm2y0PmnUdb&mr_O!UhBOO70Gx zfER!zEIBTL0^0b+;7~OZo7Y(U50@;L0wWnuNl(xbtR}Ou0PfREG1^NA96b@*gvVir zmuQy)D1R-)Bu?@rXl!z{<4d8R;3C0EQXG(?4zdYehX4+7qZ-Y}vFJl+2Ma0T$5s^r z-o&dBN*);B<|u%EDYZGZNW@_+|2Z}#LQ-7j03GK0TRexUB|3)7u4P_`MWadVV67ri zV(iIc`Ek6Qz?=0%jRJyUiT~((bJ@q^!+(=gfkpA)@l=@tOg~a1flikA*9woT7t^x~ zqkzFAkI#U-VqWDnzN=!WUVMR}S>}b(+5O+psa61aJB|h{_fW6>@kH$Ocxq0bjj!|u z;CC`LwOo+%>&v%-RF)3cb2r$4F?;>nzj|}a#S?I=$VdX=T<$0H>C7#Y^!E!a{(r_B zgkmbbwz%4aB&yx58)Kn-h9m)SO8!tjnSWdZtV8xfEN8Jh1KFJKZx@1R#gR=M`=?^A zxi-MT>=Y`VX5m?i1Phg#+FX|yZ@w94n9sOkWgnk=#cF>VcBsaa#x0d*?_j*CkJ_Dm zYfNXmGO!8!4-NI)iVD7kwe=cvKYvijd%j7kk(`fbMK1gIYA91uSrs%7l~(xaWmz$p zgv!d%I^$Fb;;g7!1Y~)69;YU?nyhZt5v9!?I4C4D{iQjF%$_RP_YepN=h*i)8=V5N zmEjp4ael%8{{^S=N!ADlt84B12!+EX_Q%ZzivYGVRK@G$2Vq%uoU+osi+@QtMr#+{ zY>3c)D@oI8CP?nvR;#LVxSYaR3TcM@f#-wQxaO)vDojN~zrO3!A7iE7D6ACExnLOP3o%7>jwP-)ZvC?P z-SYHqGJ9(B2+4n(>w$dzB)7O$6a5}(imGLlsOFM~+|S>QuGQo@m=n#sK`;V5b`-|& zl8z<*-FW$S{Fj}Z0v~^C)@31LUDv!r_`>R(9sj#LHi(UYd={tFx%4;UI4vSzUlHcf z?n+`{FXpqGs6Pk38o`t5Oi=STT%UM1wV!~w-|^`3M}cE-8qvEf_#k$#1Ly_}mu7(2 zOrEUP*xO*ckOwacDY7T%V&p9Hhx!lMOXky=&w#E))xYAd$$5VjyoHxs^hYf=gNhAC zAjxBR;d6yb#+We06Sx&(Z=4@u2LpKy2 z6CG6g)jOO$4_xs$klu|ScNvx^*WV?5tiZPOJUZ?r@n z=dGV=sSSRocFe`Ead4djfAIDS4Y|@i1P;V5Vm-zsCK!v}RAj2`Ff5iR(k)34& z@kxMF-w0fAhjCy+PZ#5>D=~qxSw5NF+{JjS3D&SfOnoGyr(@mNQfJ?S32J#z_q}c+ zCSBDS3_~Y}-yJ>v@%y8fXa$x=VXXZotez&Hl-YR46z(LqY1J?}W2t1VCyV7J^*JLu zFm(`uOLp=9%cbCu(B-SRe}m4;jIv=EereSnJyU`LZs~2zZZ7YR!f(Gz+KxHAT$i z;%}kL_aIizd-|iU8z_ugljC}F&R+(CKoix~UEMSSLeuY5-QIA3f4n*kt9Ay)@{}#p z-n(p}^yp=a(94%^^wUeeK$*?t_h9f28@R)A_`xkAOcAA<tFJ-4i6OLcr#!N$S&O7j<0>N_ttLPsZK4AS*f6&BVBPkae0NA9Hcs_>dEy zeBbm5*3Kd!e}uzii0Ubk^w5G=l%oDT(Gy0i%kyd>TJXrF<`mmV#%c0aNHYDt!t%%; zlDGDU7{18AC!vj;aKZLczIW2z~z0QL@D856f$Mw|cc-S%fgpvo1j=6rh8f2Af7^R70j}YA@_}Q~t(ZXRqkXI1 zz@337{!$lH_iJztO;O!Behd)uu^1S1p+X6b$=f2;Y|Id->3ZzNJeR2hpPZ<24?e83 zL~W^r5S>wGt_^ z!)!Q=e*yZ};(HLy6lhSwT}iUsAi#k=$~tjc}J`X-AR5C`2o19IxQP2)yE1VEJ_+IF%a$Q~;yWHC~M@3e&|d>rV=_7d)*9WA4?L zYmn#3aw^z)HqDFY`FQ!K8?d&DquVOibW+cLf6h%ky0qdQ7~4exaPwmI#%YCD9gxZXO35#oba;DrsV&0v;%&p<)xuJV0 z<0k0I;P9hk4toPWg ze^k10+%~zDx9NT$q1<6V2<_J)%89$1MLAgzg4VH*}ZK`#6%EnAPyR)cl-J z&3UAW1@4}?8shR6`F4Zf`py(#b4)XkVvf`i`2u?J-@^UK8t^o202=aj2` zYu_Kfckkv-3gJfye;t-|T`7*s?lk!gf5f@eM81=MIgAG^1JYHB@1>-I$A)dyQ!Bkk!^+BG!?T+lw{60zU zDF#&asYi7Tl~qP|wb8|CuD3u!bZir)xNcBQ?mvys-h-+R!6p{V_ksduNMetfX#eo& zQ4J1QZ*QbsYz=bT!`n+d*-HK>f7!I4l7EtHsK)j()W7hTLuYpyuc@o7C66A(4WAo&P?~Yy`sY_IxXBztc3a-FB^w5>jy<du*>medJXOSf322@5-$IP z|CWE_zaBYfSIJsHiYLI<>WiwV>yaySN^P%}d#0E5sIQYE5x8KjLQ=ezUw(IaM-@1%<6ZFcI2&IyBpKWT3Bqj`RLbGR7K@Cq`*oTfbH z`GC_4wJ9Vz=WvtEK=E2Df6J}k(Dk|S+E8~5zoByFi^0ILlYY6Om-c-;k5Jz@w!p%W zSa2BcCjZB|Dsr!UI&i1ELl#tlB4}?Xbt#rICwhd)x!| z{}STxUe;9-SwG})e6o$0j$E2KmpqOZAZ4~BrS^YP?&rl5Kyu!~AEzSAt853pv^Yr>MImn)hpK#Zg%<>QU%nufE;2XE9)!%K{t_-fHh}-vo znm{#1Kou-}{Igpfe=el7f1O>y@R#RJ=Wk7MKRVfgetn@Kg}Vx^P0+E;p$k&nu&*_| zMt@|+MHfCdYWD+w4{Vq&1Q1yJnp`KrH#r-G<6xyDH_>~Pdu1@PtnE3$zuCDiKQY)l zp>p;kcSmM3Qj9sB6IYJ5&dAi#d?T+pz}e^gdI{17E_>&?e`50fG8sb)H2l{9x&PmfWUz`K+HBT+k;*~AaR-)?2obvIGGA8gJ($^ zrgD$dwL`x+TGjv|6HxJ_q?}_WqSKV>RJ7ej-qWxZ+ z5q@3`#eD)T-FFTbll)Fp6JwH)-TwVmen3;b1?Xncw{r8o z!A9r*0z2ipS}3BfwyAJ4e=N~7dCya;Kz_Qd();f%49b?y^wH;K-k~0E5y0F39h_yB zi|cn6Zj7}8^KHO|>Dfho_TIg(v(fdnWhqTA$E!05BZtT3HZYsXSpdMMI-naQt)Qh% zh1JVk=DNW+{_{MaK<0|#YBpWztTUvf=|*{w^K0Mq%imh}nTFPSf9-{7r4SbrjB$L` zmi7nbFQU9SVF&WPnHFMYP98C*4z~@I;!}R(;h*S)jpwuRJDVFxdWf;FjYQo7ABgB_ z^PZ`ax0jM*R&c+g-cKo&1ZEHb8OV+=zjg&|2JQoa_la>RX&SeO-MB}J6CjR@Nw*e! z>eTSqrj3*nZk>~QEK*kC=MMDAso>4f92)P4j-FCl69_}WK=dX zqSi}iSr`_1TL%LZDmD5Rj92Q;_amu$CWPvA0?r4Q*9FW3ZH?8I>fNfwgNjRgc~8GY z4vXCVeJtvR38Wan<*p|yLO+(RY=A|kEur&fSlNTB67|ieGr5=eS-3!w0b65~NWsWA zc%Ttvvqj#Wf17^meL22rdi@`thwwj=n9Vj2a_Zx$RSJP<(~o@N)(oQp)Djt?xQL1j zFY3WTfoW@CWHR_=VR(}+(8!A{3XFYEq+Pk`13eX0)4063Jex+}=I#3ES1)Hgxu+$9 z0`D-zDMDfrb|EPAlNk15{&0nwicBFqVR%N{l5wdPfAEk@2*)v`$db&a*+lAIK@5qv zqV)ZSaT^4Z&oKHBZh$2;6*aWi({`X{SJ{%rwyf~IT zR}X=&jem^P{>ZjL`@&3a4%?wjg;(i8iaV@}?tDV`d?#~89@9AQu2>w~2|^bg9IM^; zQoZZ@8@)@g9=N5fCSiPP@^r?nU?s!WSu_oJKXE@)4=^=`=Jel|w&N zf6N=fLiDu}f4}B#BEd?T2At_`&?T{sDq}pIqO6yK$|LjIRH!?e9p7}glSGzm6*alx z_{;()IWK#NqltLiW1xo!s2gjXQI*A+3Ixx+jCM^w6m$(m4=z4-YeYaGWbDPfTrcwklhtB973vO#2)e-uCUN&lWI^9@!E=)P zK&$2^e6Ws6QF-d72R{}`?&+Cxmu0R36Mv?(sKf#jo|Z~&FD6KAV9-8V(>@S}xI>W}P@}^1Qp%>#^-$jn$W8>|18$@J9k|#sU(2DnmXEpDRbwz<=J# zz3s_V%{X}mYhuvrke~G)-N}Qmc}(a(`XB(yUQSu{z#~8Rk^mB^$6gswNyNVSmVWUs z#kR5QIV(`X{baE`651>-Q>&aXQ6^~bX2Un{^J5FVzCFa-adV7PS+d`P8^)efT4wU# z2+lllFa&4H!oAb!7+XAD3Ng~CGJm#gapoCButgOj+$Oji=S}8y0Z%Gf7UNAxB&nX* z_hU`O_;4{>ESo=Fh(jENyF-VCi{?_u-Nhw@z#Fyf8#04(B{e-U_wTVt=|E&*%A4xy?+yc&{F@UDncbvjXqQNj~#3^SsT4 z4l!L^FUb_On_^y=*|lL(_kXb7qw0UKUXpxMi>6JX5#Ba-_Y&!9q|VVHB7y<&Ev18U z#-VJ+SrcKb^;nTJ!r!PGiLF_}E__3T3`&LX_4C!mVp4D9k^}T27DUi@5faAq@@kgL z@WNM%8C)P;T};o|$pZ#nK~f_wGn2aCvD2^E?M(T#`N$Xbuov5%9Djh$N`wQD=wQTQ z2x^;L2;CGcGGbmW0+)GFK;}wt@tGD^v+)hs2f;SDUeAwZV@Qz%ojVh$r%fA{3^muD zrl)*&0Q9=ZwGdchW>#54&IM@7W_WtcCd;U%2bsl}{tO9@#}LqH=UOeUuI?~f0LpZ5 zt8%r}Y`VB|S_)un<w0Tt_>eRW$HcTcdb2l$DhPAh^HEE-rNQgSywYVcDXuRs$4hxB0p_;iY&m@k{lL~B zuWgFy>Uu0~a0pZOEo7MFrOBRU4A7{)`R4H8B!MA(^UYpzY^r}5&okvjl0%@XM1^^D zJS$`fccl{uI;Mb#%tGl#Pr7lvaDe1jVsE{Kd1?}b{&aBsnGt&aLOEi1m=R-D6uXn7 zgU6eqcd_Ep!?4fOrFOksLhiXjcZ&acA)~|S`BaQ@F680jqYiW>7rQIC>;l_J@pc6m zyKrktEP_Z4k@bJz!2_AaVEXnNk3oCMvmDgRX|YO^AC`;rH{S^{FQn;Nnmn0J-(9R! z(u3p?G=%pU$-gJ3Io=r=kO$WS$K_to;>mJ+nM3SSv2xLG@ZS^YtT{slQ`BE?>4X4v+`Jt$c4xR3s@E;?0G~9pXL$+`mzPnr0u+B0B&pCdP$M~o zNlCFQZOh!S2+!&+Wg=WVKTh~l&wNcYRroOWf&*fM*q+=P` zD*rakJ4_G4oA3xpfK0HL`EZ!?HEt#PIEUxWLML01PvEw|D-10?>?mDnC}bUQ#jC#W zwf86+r>;J`)1&U)iA;YOK%VdDLHbM4+TqF&&w3Q6E%rkw3`alx3^ezGY;B$BCFJDH zCyhAm$ZBUu$grs<+e>Adb9S@oD!(kW$<&7QN9ljN3ESS&-r$-B8~*0-F9G3vsYsK{ z!?sCNy>Gl$H%u*GQ?_m+x%@VkeG$W4c>7=3fsMfUMIPCBJ)a{})#=UD+;I=&p05>)dMGj14Vb*?@Dt zQxR8ZUk0V)U*hl$xUau+z{CSI+X>QbVRFpC?WEYbr87Z81UX^KOvoV%mgzSh!Uw|u z-P6ITOr8hoiLz6FwCb8Y!gBnSMuyV%fqCtg~O1vvF)aV?HN^Hq3I zfxFP@{5o%nIh1bMs4tv>Ro!>Zf%h-XN)Z$&VEmp$v{a+^IorXJQ{YR3XT>`bm8-7f zuOoQ`Dop;u5(!k|0bDQLk?$KP2Jz|v`JrNPC6Cm|Lyt{=?W`#mo&TPO5&0&osr9RK z<-Lx`~_kLF(WCbq~`yBe-oXsl_Y$ zr7qFZmmatRD}Oo0=h=|!Y9{9Mx2;&#*Hgqv>oAJEjLNF8{Ijt&}n=uM!B zdV-!>2}60JNfCGnjdf;WdYt-hTp&g0BNg3$@sNNq zD(w~JE3hIv5BPLlm;1zjK_7cNE^_wF>(_rWorzf?w2D`H0cm>lD<}{UFisitNGKTi zs`vwVnt%P~H@``awAUH%wS0puB+2!72F^j?XXZY6Z@MQ-wL%;u*igbWg)WI~>&g=F z$oh&!2{=DJI6XM|^Be6uP;a@5VSSaW{WGukr%RjpkzMq*f>GF&mLKMFba%yCsRoTK z@eu)mf(a*sZse2XP}&>{0iwf(e5sZH`4{yX%YQn$s5un@{2G6O%UTS}f3vJOiX=Au zb)|o;E1f>;|8=R~X{qZKQ79AKbC&aRhX)XxJI z2w6uCKY0FFvh509xX~U&rJWyU@Xl|tv!N)dup`JyuC&0PUyDGJL71R5IT*vwl+`|L z{(n{Ut|r$2>YG@5CqQRK)`lB&#Et0g?3^}-BOj8COtWJ0` zu&YN6>cQ3rjO7~4wfq(Q!+e)K=$1Hlgnz~cLQNueQ`E7nN$f0_C-knkp7nmZp85VW z8}ZkPOH@~{AmJO1FgW|EO)W#KSA}N>B?0$qFqp3v@@hN?!a>5cJ7Fwl*TqF+Hy(z+ zEQOgDbyo}0#JqLEM-UVulz8F_E?!Yq?Lc7@uMParP^6ipSDDDxm3gskE^`e`?thlp zS5}-}Ir~#WQrnIb-Ox!c;hMYv2pUDX4gG;7mHm%`?1HwXbirv@#!Dow&)Ji)Dsw~I=Jn58y;S#sXz2pTJlIAe1>1WwoO;FRDTJp@wOZ<-P}!;TLt=oT(Ag4L39m>GJXlzM%61o z)vZ_n8sggg&lqIxh5bXix!iw{J*{AVkGk^9PWci@qj|3Hv^Ml$U}>not5$}tw)w)) z5cqWg3>#Y(s^#5Rg?^>1%*6Mv>g)Yc9C z9_q6%oRGBZE*x(H7LKmA`NGi<_=N*JE$Z?sM>XAS<%scWl*mO6 z7vht|VDr5@Dzeg-#asMKe%2xSAI5hQ5Pw)q=PM|qGz?ptY664}1?7fb)V(x+PQ!0E z%(uTM)qQ^dy{EJqO!iI}a)0i=|GtrQQhBiR@(Lj%s6QklNS(SV&MueW$AWwz!7CO zhkJj+aJt}D_uJxc^S{lP2)+U(f0PJ>3Qt1ID-L;fYk5>xIvMgD?Aei_GR~?fCqp(OL{pA><*W@<9jWoPojwdDs!^9^L~ zv|*{-wki~ojG7L5FVP8tswH{@81g;X9({1LZN+xe;^)d5QzMF-9kGgOk_*f0`ll$yVs&>Z0XTRnC}>D<-V~E||BvlS3zZiv!2l*NNfZ zsSu`yb+Iyra{5{7kc%rB>(b_a9EqQG$QlU|;s7pJ4TW`6UPA7=y^*Z<5Keq`nXkqu2Q{jqf5NUVg~zaOrt?Yu zvpS4QSXiKGv#+USO3tpA7&*$6G^gSuGKDM0W%6(?_Wy^buEf!xgx`d2;(dYjv_b~3 zsfS?;dy~qWR$|Y02M;}dy0v?=q`_RW8&+w;4}0VB3Vyy;qg6jtklMXB^4jOkYB`?E zBx(()K<-OauMQCRe;eQf*$(Bis9SQ?@@71lyjd;Yh{8tDt|`8%vHA~OfDUsEEiZ;P zdpm~oPg+^g#Ly)HJ)9xyTG0-~#1KVD1IOt?xQPpR?~N#e;n-FqF#WgV+Qz4@=7oVB2sZ|M!vX}_C&l~nKk8}(1scB+|7w@8zycnBtd{zN zH9)9GWA$6S!d_n66~1oE=6P``hY5de211Gr2?YP%42)zVBgCMh^GOJ8u{#&JatJXO zm_k{4H~o;~{r34(rz%vjRrMvWl5L>9;CW)M8p*U{SMmlIlzY4bhOnv4%|=0Zb>j(< zU(7a9wV*)Oyb*fb1mX^;|E39lZr=qi7BOP=nVgK&n$_*D=T|~pHB>7rPgLizuH#bN zu4HEUs?JS-0l8g@*>OCkgenY|q}q5Ze)^jP#M1v{M7FGb>$pjQI!%v*5;AHW`_2~Y zR*-anb)~j)*$1?PP7v4R*0KGmeT_~AZV5Y!`P*CwMsM$ybw^_TZQ-Jso4U;P006pYyZpRqmy+aq9?ibT=1vY3 zrER!7plR+N{={5H+EXwIJs7&AY)dMU!y|Y1Pxx9{$4lqH|Ipk(#Xk2 z;PIeFQ7c}WaMMC>vOqnTOu_;Ye~tm51(-dsGX&k;203bYrbBy)d?2_m3LF&ZZ;q^~ z1UEj{;2e$60CX2fEs!`!Y4+OTnwp`%((`BO$j)GV!H7A~)i8;#XI-S!m(>)rnpN=6 zLUqQN=^11k%KDP3#@?gki=0P9CRIO(R4_|T%OYB=_^pC|&;)1wQ795wbM z@NeB0<3!xj9K&{p*3b;@?q02tv{iTA0>r|R^SjosO`mshReC0Aw@vWuT%&C9Qs+wp z@U`zT=516G4yrb?#E5e^f1SQEBf^Dhzd!uu;cFs^Ac5o@b>Mw9L6QC66aDs>&L%jO z4sK_dHVl(ugHo-SM`DXK10rIlJE@%ltAUlIGbI`z&U!iDle4^cat9LJT;&Z(Aix1N zp@-6X|Bw&^h-YiPHUvZ^&WSXj`h?-l*S0*$c(n5f?`bK%fXg#@nT=Xw6?%e?8qP3HCP z1GmaZZz`4>>n$DFC8vv}@m>)m^{x2oWm9pfD%{=nu3`S5wVB{1L7}j#?dR0&ZZSzU z1mqz5ofzTmM+wUQ4%A!liBaue%#jh~4W|d&Qo#YN$WQw#lXiymuHKz^rk$j=ZAb0{ zhA_(1W56VVf3+uASm1e9zN(-GT~h4Zp-q+(c3ruapjO?5gb*uN5!R|$LUPdjBtn^7 zJkpU7{w)*L#@3K>`}@)hNTTbl9`uTHmX4fJQp_zHY{sn^-N1NlklOm2SM}uj4EWc9 zD?fP^pNz$3;XssM^yP>ij-t1INku;^pHcm%zl>#*f6_IK)4$yXEGNeNDVeEiF+U>L z(L^XcQoaYV9h2E4O~Bd*CUCHZXo_fkn^jfSl9c4b6samU0IiO9l+UjpJUe>+qYB2O zd|}{oEoq}rkI5Rj%Adf^hpqiU1*}v!J;&2hB&$W)X0K&Z5|D|+GemjBm zkn(<(e~PqYT;r=Lk!y%PpwmoFWS(a@wJzaK1Y%1y6AZk3K0eDOYOcHs6$MogBS6Ws zP%fKpFz;H3x*eMZ#Hk!I)3=gI79O>I)b*@PT2=9vGdohU(OU6HZQte!gPOP-9QRL zcyQX>WXO=!5V0v5bc|pxfg(L8U~dm*>^3PIx@hg4Yz~({#{w9Cye@zy+T-imr2W(& z%p-M)1{gR$s1` zix1O@3tWxFfm2id2+Ct04VP4@9hv%*GCBrzuc~kECZ*>q%K2ALBjf7A7g{fdXq~2| z|GvebP8(gnOg7Met<-wS7!m@Xj$Q4cf-B>Dvu6q^m(#@MO7Q8=NkfxptIO z)PK`N$(tv5*?YPOJX5@t9W+RTouvu?vQmItLF^qDr_1q~8v*&5!0h0ua-7>SLPbXw zb;JrjUs1PAN6N`T{L*=P#bT9~VXOeSwR_3#-s$3CHf!vEIwo0tDWW*!bn!ZP!lE7q ziwZ!{eY;hmQ?~rvuqoDcMQV{!-}c^!Ri<`B0H79}0oZh(HxXKb$PwS z<%&z*9GsjSKYd;+M>!z>Mjm0Lk|f){IXFB#`r(xEY<1PCEv9oNS^a7u1rj*FS{2}` ztW)Kx{E~7pS!2fAWixrcP=BhlD}w1@Nr6O$T!lKOsS0(P>mWK#KHL~asO#T9t?Cji zM9)xd?3V<|0wXy2^-RUCn4p;zW8WavX`fKzVzGdb78f~Yl>~pl#VQe1KH@P!V&%;a z4BhW`JS!HmgQ9#^qyN&EUdaL|e_x0#O2L5FAUaM2JtzY7413G)7|0F-e2kb-x@_f0 zCJoovMM9n6JUY(&GOM+6c$4049xssP4{wXur7oMG;^{<*;YS#Ti7~Ul1QoIqKq>F3 zZjm1R?cJ?L&2i3#0-yf0oaX1SS#2qiqH~zt@n(@b(5jfZQ66@J<~kVfIXeRg(>|e# zXWY0i@%>~jmX(9~e)8}~V~cxeUjJAxb9--OeEc3_#`uS6qCM;!5Q)5ad}zKTw<(uK z$^s{Uw`(P$tfNjF0kXkLtEpZ)uw4b4E0QWUS8}?ju@0?`w@l-vI(K~y+aW)n5XwV} z?CH@hht#RwcN~CU>E=TRRBs0&Ek4UOw9NnKIxklC9Tw_EQ;g2&D^aO>6)N4guQ3cm zp&oQiEQDfuc|995f#6Gs@fkNOuAA6OA$7lh{I=yxef_=5TL-_O4Gc0?%6HSTJxn&D zfM}!ITj5L8w=2QOYSlaH+CXaTsYo%qrQ*+YjZGJ-$gXV_`Fq2C*^J|>*qO^^OP!mk zd)k|+#8K1u*>-2f%oR!nuw(sget=iJGTQ2RGF`}R0bC!wBW&{5#dUHq{*Xg<2b}|d zR);>Y+Ys(GZ*vvrQ7CW_nW57%5d@&p4qC`mWB`9GL*v-c zyi29BPP{%KGLX3tb?(Y^CMS%%jfS_X{H$B# zwoPw}O(j{sB=VAqn;@^|Z1*dayQ&8gwsrZWc zwGgcKJ+p_*qab4lnN;)pLaXzC3w6$hutcZSwa+(cDFh~h%&;xMv2E4MsZM~3=JKF7 z!A0*DOUU%F^WA^!VE9O69=BOCk=2ECmGzxruXXr!kG{+0^n*S5hUeb$Q?G8VkB9aX zIgiDUm<$BpWUK$n9xc^tX9LC_4!?K4r?vsWx7Fa6*AaVqd}#4>O}esw6_L_0p9s`d zkJPi_HQXeW2&*j+zO1W+5*j}Jd%)8uz-bLi!U&sCs^eK^G_qzUSFeT%5jD-!29X^QSs)gT7vT!ei-J?fk zeCeO8;7}!val5)Xm)FL?63ZSQ2E&uCx#)c>m%Qmt4JgV77hh5>KtU-P;*=xD^*OPUTw(bK*TBM8oC6S{r$gn{jyH()Ah=}Q$j_qkPUtz& z9$w%tr&!T|K#(8m-Uxk}8=or4^)+1fxI*oI?s*h|9Z1Dg0B7PhvJ+&(s(&ZK zBY^{P@aiUfaN}Lg7@9$STuBZ5^9Q8wt{>WiDRN}GDR0t$Kvc?;8lWF8W{ag$(SCOH zNT)VmhqbihV%zmJ%uTM9WYp`k|GA#ZNB&~#q15+pauCHo&8~CtvG!HYHW8Cs9?Pdoh^M|LmhZ69w$YTRMDTpE z1h@V*@l=g}wvDI)8^QEtjtN25!xS2A8%=;2!SSbysZf8{#8EcdHkK0V?dQ1;+k9iX z*V~t1Z<3q-XZ8`~bl|-wi{+7^2i~*TWDLwX1LxDK$#zr?TSP4=;*4}TUR^ZB+LNzKzdRmsGma zCf8Jd0hyHCsUVYoaCcD%M|6|>HnPjO| z_g;6t_43_4>s)%7le6_9lt@{?36j4nWth+y1-hN>YZNzAcbMAHkL2;_uy_ z!P`ekUxJEm5~9MvP_=RBF$gNw3LxQz^$ZMu2~m26ri5r)pOkAf^$}n5)=`@-c-zHd zt}wHv3H5IcmmT$z@jV%=HSQ~f4hChU^8n0WaD zx!Y*9@;sBep_CD`{CuUNZ%p*!2k+%xDtB6PooPuz55C-erM<0O>#j1q1;p%(WOa{c zAICR^y;E#DH-x_^Hnv}}`2#5~4^9t%G`JEzHyZ=b>FR@(#oS@l=7py`?p2!3C(>;Y z0XC24QxlY8*9-mZHI*KT@MrO-8{}vX1~s9su_Bqpm0V0^_zPd?Xj(}!zkjWyO@ zox9O>*{)ugCiq5t;xw9WhDC0pp!&paxVHW3R;Sp&l7(%bIZRVFqmAJ%zdVF|N zEwrRwxxOm%Qm~v-X8y@|O^tkglIfe3e=>R*;Z#O3LJ`5X-g%<#kk{R^&2gtew`P>y zW>*y0c*)(Pmvi=DXu)wYihOJ4|2jQ5^i7%YEo#S<4ery3iRhGCFrmghnJ*EUd$e8p zV4fTt#toYUxq$DWtgiQw>SWu0Zu@eYYFIKQkJUG0YHOcDd*4#2#K77Ahc-5Ty%t2QOwpr_H-dO_wb(AeLwqK@Cde2-8EtV3 zzaBw<V;mv)`OZhv4&&5_uu994EpK`XJD_4BHc`D#Q z0m+>vxlo)gr}FIx1fLWcqCS}krD0ZJ@@!1}(6j_Y8yD94URK&KGna_e0wjM+rJ@Q= zcM@t1g?B6ls6CRAetgfz7;NL^6?#2_2qEK2>5o%PK4_E71vChA%xUCEwR(8^-8Q|> zu{9y(sEpuUQOitHe;JwXEEW2fLwwS13>ny%~BpketC)|XRe}ZOV!$v+9Q8^>8ddF&fej;eV5wR0v&%oJP}Lg z#akNEPOl*`G*K+e8oNF@{*s?{0#fML&>fw&rV?TJ-aNxI{qg*KIewYz(@1Mg ztCc2T)M}^6-{BW{dPPwQTz6Zd^+^1qQ&8El8EI8TO5Yy%WhKt9(qJ=bpbuF8sPaIl z0TnCE9wEvr?oD*uB|uC4Q0aemMEyD-Y+`J4t}S07kVxn%p8)Guu9Ph@e?=Y*`}+RO zrXjOAGe}B@Fz18Gwzht{2iZeY^dYl41pOq_%d1&_i3W7}ynS`C5WQl$C19t;^6UcU zZHyeRqB%W#@#5sDDe-^wnf@^e86=)E!x7$iorx)$VSXcx?P{@S37UT--#8;xK>`(r z+%|UiRMbF~uL9#&N$;A5fGMN>7rV^}u*pQR3PPGpMB{Hi=}!`vO$-q@ut_q=T{*O>!ApN#(VXqj8o=WKe!AIz;2g zx%AAkgHqH%WE3E^B(q((K$Y3Y6*aJhN<-Foo8K%TwfzP8ZUR;4i(bJKichz`@(yIT zCgGVOUV`&)E_4@ zmmXNqZgR8&xk!qNP-a~6L?zmlzHhAP%QQ#cm79>2MbeHLG;(ken@&!46*YaA;~Sa4 z#!g@Pj1kmz6zP8o)R(FnWKGc-lG5bk#UlAQE_D19H8L*ej_mAE2P3YU6kQLx`|dwv zcw?1LNN7dxrf8|2uCCR7b&)SYw5nzSE6D-i@{>6PDe*)jLbprEqa|0T!c4C7g%FU* z;(9fk!u*Zj3Mtu4ewgK8ViPJr0oULtY8GeL0La0ixXORersvZ$>=4)4^<2m#NOLOJ z{$zT7o-cKznrl=pWYA`V?Wvo*5CH+|LgE#IV+Eb)#x52j%=S0mC?=4Ubi~Yc(M%42 zx5hAdAklwg8eA zwnkmGxDo3~>?*6(7_w)kHsxbsVtdQHS`ab}Xs^I10FqWe7CD)iP?rFBw?N(n05kA` zmSZ!dLJnHz-&FMP?#h6GZ&e)gD0S&#)0G`cm;`^B#l_;|T#rZPV?tama$kHhnYo-C zbq^jqNDi*WpqIs7@?JvKHJ8=fQ#C!}~DleKv+Eed| z^O-5B$|Nmw05WN$6&E~sc6@Sj{NlO(T`Rq_pV?XaY{^{GC&~{UhXC4$SjG4uq-dDF zp5z&Twxf?_fyUCER3AgRJkUlbwq9^Q$n7thmyp^593zyhLEMm(@_A}Dpm$TTq?cYA z+mg8|h;EHz_I%^*@2JI55WZa(d!MzZrbdmV%HIHEQ{+uiLK%ULQl#OHG=WkIxzl*old7rUx$BK{B7Cs zV?$gtNeNK^72&gV<A|iZ;}!RU&4%+IV;}xyvHvl z6|4-8S>{l5D3r4~rs@<`P`d(5@T6cxLgiZ5T#42xh83}wAFs5{nsJJf;thkzs*vZz zBP!xVRjcCRa$H>0s`o4>z@Bqus?b|JxP1Fb+F0sJO04}r3OMsF zh^3}u!wDV{nFQ44J9!Jm7)`N?F|!6usKrzkofxMJ^UdT-$tOF$D;we?NeVmElf|+G zZvk5aDy{4eLHZnc=;KF;KWqx{^Z2p!IALx}eYXV{INXxzcAzh6Q@DQoTSvqc29&{W61*p?{X`rY*}u!FxOj}8!XNZ zm#3PEFfWI{NLfD|XT83}ou<$hVa-I$*$%0C#|?(=-)|QS*r{0tEMHFS3;gO`#jaJ( zlwsDh1EGu)1*7cC3qWUb$fNX-@?7+4G8_E%F3!ae8%umbi+6wBIiQ!Qk}cL&>&vzP zDGVA8hxQ}PbKR3toFT6yhf`X4q}qbA;M%p!qB+%bv=YCXca$ zBephq)2XGJF(r9b(P(6=p*6`nq4Is}I_f9B><>&`&Q~VmlVnA2X#+_ET0q9der9^8 zXu^#`ktFzp&=YU(;OjcChUx1M{FjX00yTfMj4sYVVM_TyawX0HnF_D;x+?)f>QoO% zn!xLmh$@%R${zrU6c(0p*)=&J@i5ekL`AJshl48vJRryYFk2tSNDBwUpmfj>3(Hn z-9I8nHZp@%DT=ih{FJ8!VDz14T4rzCLnRe}Ylt5{5c zDc{I-L(Y7$c4b|6G~7+w{^=V*^cpovMD$>zi|B0h9-^1%HHiEWf?!vTh!&j{Aw)}p zRaVVLCx{X~2&=5G+h$DH|N=3FzDhglAhW?XDK>`ICN zzdmRz@_eE#I@e)lhcAE5blHxOmMapUdnV-e?g>lGXcD~M)`8dSX zCa{{twBP2Ld+4lxd8Ndx{5*t!im(wOhEic3NmG{*v(NZ({_3GwW|6y0$6(@Gk%kqD zer>fng(};ogRd#jhRwS7En!{asq2 zb*094-^rz~DoQxt3&q`I zZHrIfXz=6mXgWSbfBmUOwM$YW`PER-cY%=vmt;VP|I5y-E-*jM=%3!EmaU+aG%b$AT)Er*I~pq8!=>xQUC1m@DapN>m}Ty5TZ{@%!CbTy`Zz@5nM~d{9NkyJ*3N93 z4zMn3_xh;M0-M3s=@y~n*dinREp`@G;98|A+pNA@oqy9J{)lgxDTJN|BCc+j(OWOe zlV~y#H70V$w;Azv)P7;QPidcl&L2v3wMfi zEehdf7MHKt`Z+z@HL=K7n0%xgi-TfN1^3@_cx(7_yH8UubC5E%MlAQ%_kIU#N89V~IM?SR zjz8Xez_RZzDOcOR?3*RXvg<+2gq-og)R~MKj+lI0)r;FY5X?7zdC81j)uCl>&M->k zJ>RbtK;vLV1~XxOs^k1Dya@~7XcA2##(j!G1+z&I>sRLFUAbuhvvJ%{Pf)n@q@}7A zgZ5V?arsx%qKAH3<+~EVUbx17o!O&=xI83zvS`v$?G-9C5z|NGf1`^f>Xki5FGYUM zZTO3(9>k93MBIWAqTh)U5x3ac>NY^*rB}?Hibj7xz2u{7k4bjbZP^1&eCTO+-On3~ zYn&=qDc4HGuAJxHd#AelnCv1sFgSHq!mP;Qk9NS-IHZgR6|y*ZKj60`U!&U-f5&7> zi=?t5_-;K5i_@m1ak_~N?_#(4EmlK$A+2(;DjgL%tcN5ma<$&Ie8Wq#3qxaK9H3vt zTcq9~!tQye5d(3X51jzt@`fxlUlH7ic#M^(XB4Mw4XikxwQmsTEASM)bq`G`*u?$p zWhi5(c0V^fu;Ngn?bD3AG~hlwI5w9Y7p!7tFrwO&n#1U1X%_h_~$PD8UK3AUF zrr5-~U?>BG*emSP%m_buc1A9{+4J%vW;h3_mPKrPss`{<2Hl{l(XM%WQ!0opG8$*w zkB?Vw^m|UdAtEN56QAg^^!g1~*mXw=(HDcihqQs}6E1h$l-~*Cb0=1x=>~zKv82JS zp7@IPn(?!fJJa#Y!MKBeCPK zzd=m#s&4$|M{3a2)v|DB1}?<5-e0ogzd>9?)-gN$y!e);g2eJUZ}p-7AIpX(;xebr zn+=9}4%_8+GKc28B}aNCY-$y^dvzyH-u+BFGn%Q+QMs#{KR@9}5uWTX07p#MG0s4p zZ4?=w=bSEJDv2-`p4{S>$ypS-FSTzdC;#@D9eEuFvsH z!Ak7q6J?S2q3%M(k08yPRVEViB@Ti0}43=;)?0Z`-k;v{s><`bPVE2 z*CLdAWZ9buoJNYA45F=j&9@*yWJ}JZ3yxn8DtBjo>3Y~UZ_^s(5a0Oddn)|1G$xH@ zf!EAmGGK0RA2kc|EjMTM0ejYoAmNByukNpn^so}!A3jre#YCD5vXzZl zg5F3hwob)4vZgd5KAOe$z1>&$g7Sa<-rm=@JG$mgMRg7F42s4^k#HGhrkU z!TcCL=2(+t7nuca?kDz6u2rnBD1iu;vlSkn;Y|5GO)f3JR%NxG0%B+$bp{lRjko>b z3n*#FR121nBDMY!jw;X+jI}M%T~T_Fz!0yUi^#0Yrq72$WQj;bVhjaLi>#^5u13-p ziwC8~$JvGd;l!T_K&sKp&OX9x&`|Jr!}0qC(li93pTL_1ailvQ*Sn%T@+0?k!0eTz zqn&4Lkq|+y)na2{CHNboP1_(^A3wIfu{+%NYk&zX$#qld2tx9y7tar%Pv1Dmha4^r zzCCGu8X6F4c-QpC{i3C{{1Ny2_B*;n3OPN?bog61QG_$$q3RHd6D;kTVz+ge!B}1F zYi--=+j^W|Gm1+hl;$FefX*bK`4xrK&g}fMO#zAIfV44!V%##6H!FkcO@`{o)FxnP z$Rs!#uB{?CE(51qFAM~U6WnxC~j%y79JqFIo=FOUx|98hm!zv!}KS$)yMq$(r#B?`?^D{d|TnU$MYEcsZgzM)3e4Y0}`iHq27ulVeYz{$c2V0cPwv z2|DfoKlC-g+NPh@G}K8N0`de8&hG^YT5WBMQPfTN$Xm&zL7c4^cO(?&iE*eC%;I** zR*eji#N&Vr7VGOp162!!9W5;Fp0-`2&z|@YPUeELP}9DZK^=;p5D72Jc_jCB%|Dgw zXJ&Fo)j`s7&;abpukH3iN6e2J8r7zubtWJ`9og#P+TLt{QsdQ5$m);dm#KOc98Y=3d*76WDDwAo%*xq?hv;qMCnN`)Ked5?AGK_e>7+WJ zJJEncqKMg-wrw4PiBC3)Z>x_i-_q}5L_v~X)jP+at1T*)U369S+aQ}nS>sc`i-Nnk z`J$|So!+g-{P_KZ?lbT^JHwuGa&q!%3~~x!aRt<#mxtAtO8PnkgaAMf5CRYY0Jwl0 zwd^fA3II^H1pqR<<*!EY_ky22-TeLCyuJPlBKdz*CWe%VjR;BsWNwlYaq;~VOO|7? z10M!Rzz^pC#P~ZFBPEb2Mn=R9{^L4-T(#heC!&V;C;yjgB_#j^+xa=UdI$PD`TsZb z-*m-4az)Bxb^6M$#?KOhHD&8GwSB<=*X0C4kP*}Uj8^+}27Gs$-d y88cf+i6FfHFx)(Pc=axd5FjO>1{nW^0U7?nu+o!|{$(b^_a1zeTjTh0fd2y`Kxo$h From 92f1f08d5fda7b99f34d3522f02b17074d22e3db Mon Sep 17 00:00:00 2001 From: Sven Van de Velde Date: Fri, 5 Aug 2016 09:26:34 +0200 Subject: [PATCH 06/16] Progress --- Moose Development/Moose/Cargo.lua | 529 +- Moose Development/Moose/Object.lua | 2 +- Moose Development/Moose/Point.lua | 27 +- Moose Mission Setup/Moose.lua | 29473 +--------------- .../MOOSE_Test_CARGO_Board.miz | Bin 0 -> 16744 bytes .../Moose_Test_CARGO_Board.lua | 13 + .../MOOSE_Test_CARGO_Pickup.miz | Bin 198657 -> 0 bytes .../Moose_Test_CARGO_Pickup.lua | 8 - .../MOOSE_Test_CARGO_UnBoard.miz | Bin 0 -> 16755 bytes .../Moose_Test_CARGO_UnBoard.lua | 14 + 10 files changed, 354 insertions(+), 29712 deletions(-) create mode 100644 Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_Board/MOOSE_Test_CARGO_Board.miz create mode 100644 Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_Board/Moose_Test_CARGO_Board.lua delete mode 100644 Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_Pickup/MOOSE_Test_CARGO_Pickup.miz delete mode 100644 Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_Pickup/Moose_Test_CARGO_Pickup.lua create mode 100644 Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_UnBoard/MOOSE_Test_CARGO_UnBoard.miz create mode 100644 Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_UnBoard/Moose_Test_CARGO_UnBoard.lua diff --git a/Moose Development/Moose/Cargo.lua b/Moose Development/Moose/Cargo.lua index 158494e9b..6e5fcbb92 100644 --- a/Moose Development/Moose/Cargo.lua +++ b/Moose Development/Moose/Cargo.lua @@ -49,167 +49,146 @@ do -- CARGO Containable = false, } - --- @type CARGO.CargoObjects - -- @map < #string, Positionable#POSITIONABLE > The alive POSITIONABLE objects representing the the cargo. +--- @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.CargoObjects = 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 + +--- Board Cargo to a Carrier with a defined Speed. +-- @param #CARGO self +-- @param Unit#UNIT CargoCarrier +-- @param #number Speed +function CARGO:Board( CargoCarrier, Speed ) + self:F() + self:_NextEvent( self.FsmP.Board, CargoCarrier, Speed ) +end + +--- UnBoard Cargo from a Carrier with a defined Speed. +-- @param #CARGO self +-- @param Unit#UNIT CargoCarrier +-- @param #number Speed +function CARGO:UnBoard( Speed ) + self:F() - --- 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:_NextEvent( self.FsmP.UnBoard, Speed ) +end + +--- Load Cargo to a Carrier. +-- @param #CARGO self +-- @param Unit#UNIT CargoCarrier +-- @param #number Speed +function CARGO:Load( CargoCarrier ) + self:F() + self:_NextEvent( self.FsmP.Load, CargoCarrier ) +end + +--- UnLoad Cargo from a Carrier. +-- @param #CARGO self +-- @param Unit#UNIT CargoCarrier +-- @param #number Speed +function CARGO:UnLoad() + self:F() - self.Type = Type - self.Name = Name - self.Weight = Weight - self.ReportRadius = ReportRadius - self.NearRadius = NearRadius - self.CargoObjects = nil - self.CargoCarrier = nil - self.Representable = false - self.Slingloadable = false - self.Moveable = false - self.Containable = false - - local Process = 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 = 'UnBoard', from = 'UnBoarding', to = 'UnBoarding' }, - { name = 'UnBoarded', from = 'UnBoarding', to = 'UnLoaded' }, - { name = 'UnLoad', from = 'Loaded', to = 'UnLoaded' }, - }, - callbacks = { - OnBoard = self.Board, - OnBoarded = self.OnBoarded, - OnLoad = self.Load, - OnUnBoard = self.UnBoard, - OnUnBoarded = self.UnBoarded, - OnUnLoad = self.UnLoad, - OnLoaded = self.Loaded, - OnUnLoaded = self.UnLoaded, - }, - } ) - - self.CargoScheduler = SCHEDULER:New() - - CARGOS[self.CargoName] = self + self:_NextEvent( self.FsmP.UnLoad ) +end + + +--- Check if CargoCarrier is near the Cargo to be Loaded. +-- @param #CARGO self +-- @param Unit#UNIT CargoCarrier +-- @return #boolean +function CARGO:IsNear( CargoCarrier ) + self:F() + + local CargoCarrierPoint = CargoCarrier:GetPointVec2() - return self - end - - --- @param #CARGO self - function CARGO:NextEvent( NextEvent, ... ) - self:F( self.Name ) - self.CargoScheduler:Schedule( self.Fsm, NextEvent, arg, 1 ) -- This schedules the next event, but only if scheduling is activated. + local Distance = CargoCarrierPoint:DistanceFromPointVec2( self.CargoObject:GetPointVec2() ) + self:T( Distance ) + + if Distance <= self.NearRadius then + return true + else + return false end +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 Event. - -- @param #CARGO self - -- @param StateMachine#STATEMACHINE_PROCESS FsmP - -- @param #string Event - -- @param #string From - -- @param #string To - -- @param Unit#UNIT CargoCarrier - function CARGO:Load( FsmP, Event, From, To, CargoCarrier ) - self:F() - - end +--- Loaded State. +-- @param #CARGO self +-- @param StateMachine#STATEMACHINE_PROCESS FsmP +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Unit#UNIT CargoCarrier +function CARGO:OnLoaded( FsmP, Event, From, To, CargoCarrier ) + self:F() + self.CargoCarrier = CargoCarrier + self:T( "Cargo " .. self.Name .. " loaded in " .. self.CargoCarrier:GetName() ) +end - --- UnLoad Event. - -- @param #CARGO self - -- @param StateMachine#STATEMACHINE_PROCESS FsmP - -- @param #string Event - -- @param #string From - -- @param #string To - function CARGO:UnLoad( FsmP, Event, From, To ) - self:F() - - end +--- UnLoaded State. +-- @param #CARGO self +-- @param StateMachine#STATEMACHINE_PROCESS FsmP +-- @param #string Event +-- @param #string From +-- @param #string To +function CARGO:OnUnLoaded( FsmP, Event, From, To ) + self:F() - --- Board Event. - -- @param #CARGO self - -- @param StateMachine#STATEMACHINE_PROCESS FsmP - -- @param #string Event - -- @param #string From - -- @param #string To - -- @param Unit#UNIT CargoCarrier - function CARGO:Board( FsmP, Event, From, To, CargoCarrier ) - self:F() + self:T( "Cargo " .. self.Name .. " unloaded from " .. self.CargoCarrier:GetName() ) + self.CargoCarrier = nil +end - end - - --- Boarded Event. - -- @param #CARGO self - -- @param StateMachine#STATEMACHINE_PROCESS FsmP - -- @param #string Event - -- @param #string From - -- @param #string To - -- @param Unit#UNIT CargoCarrier - function CARGO:Boarded( FsmP, Event, From, To, CargoCarrier ) - self:F() - - end - - - function CARGO:IsNear( CargoCarrier, CargoObject, Radius ) - self:F() - - local Near = true - - return Near - - end - - - --- Loaded State. - -- @param #CARGO self - -- @param StateMachine#STATEMACHINE_PROCESS FsmP - -- @param #string Event - -- @param #string From - -- @param #string To - -- @param Unit#UNIT CargoCarrier - function CARGO:Loaded( FsmP, Event, From, To, CargoCarrier ) - self:F() - - self.CargoCarrier = CargoCarrier - self:T( "Cargo " .. self.Name .. " loaded in " .. self.CargoCarrier:GetName() ) - end - - --- UnLoaded State. - -- @param #CARGO self - -- @param StateMachine#STATEMACHINE_PROCESS FsmP - -- @param #string Event - -- @param #string From - -- @param #string To - function CARGO:StatusUnLoaded( FsmP, Event, From, To ) - self:F() - - self:T( "Cargo " .. self.Name .. " unloaded from " .. self.CargoCarrier:GetName() ) - self.CargoCarrier = nil - end +--- @param #CARGO self +function CARGO:_NextEvent( NextEvent, ... ) + self:F( self.Name ) + self.CargoScheduler:Schedule( self.FsmP, NextEvent, arg, 1 ) -- This schedules the next event, but only if scheduling is activated. +end end @@ -221,90 +200,133 @@ do -- 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 } ) - - self.CargoObject = CargoObject - - return self - end - - --- Board Event. - -- @param #CARGO_REPRESENTABLE self - -- @param StateMachine#STATEMACHINE_PROCESS FsmP - -- @param #string Event - -- @param #string From - -- @param #string To - -- @param Unit#UNIT CargoCarrier - function CARGO_REPRESENTABLE:Onboard( 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 - - local Points = {} - - local PointStartVec2 = self.CargoObject:GetPointVec2() - local PointEndVec2 = Carrier:GetPointVec2() - - - Points[#Points+1] = PointStartVec2:RoutePointGround( "Cone", 10 ) - Points[#Points+1] = PointEndVec2:RoutePointGround( "Cone", 10 ) - - local TaskRoute = self.CargoObject:TaskRoute( Points ) - self.CargoObject:SetTask( TaskRoute, 4 ) - end +--- 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 } ) - self:NextEvent( FsmP.Boarded, CargoCarrier ) + self:T( CargoObject ) + self.CargoObject = CargoObject - end + 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, + }, + } ) - --- Boarded Event. - -- @param #CARGO self - -- @param StateMachine#STATEMACHINE_PROCESS FsmP - -- @param #string Event - -- @param #string From - -- @param #string To - -- @param Unit#UNIT CargoCarrier - function CARGO:Boarded( FsmP, Event, From, To, CargoCarrier ) - self:F() - if self:IsNear( CargoCarrier ) then - self:NextEvent( FsmP.Boarded, CargoCarrier ) - else - self:NextEvent( FsmP.Load, CargoCarrier ) - end + return self +end + +--- Board Event. +-- @param #CARGO_REPRESENTABLE self +-- @param StateMachine#STATEMACHINE_PROCESS FsmP +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Unit#UNIT CargoCarrier +function CARGO_REPRESENTABLE:OnBoard( FsmP, Event, From, To, CargoCarrier, Speed ) + 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 + + local Points = {} + + local PointStartVec2 = self.CargoObject:GetPointVec2() + local PointEndVec2 = CargoCarrier:GetPointVec2() + + + Points[#Points+1] = PointStartVec2:RoutePointGround( Speed ) + Points[#Points+1] = PointEndVec2:RoutePointGround( Speed ) + + local TaskRoute = self.CargoObject:TaskRoute( Points ) + self.CargoObject:SetTask( TaskRoute, 4 ) end - - --- Load Event. - -- @param #CARGO self - -- @param StateMachine#STATEMACHINE_PROCESS FsmP - -- @param #string Event - -- @param #string From - -- @param #string To - -- @param Unit#UNIT CargoCarrier - function CARGO:Load( FsmP, Event, From, To, CargoCarrier ) - self:F() - - self.CargoCarrier = CargoCarrier - self.CargoObject:Destroy() + + self:_NextEvent( FsmP.Boarded, CargoCarrier ) + +end + +--- Boarded Event. +-- @param #CARGO self +-- @param StateMachine#STATEMACHINE_PROCESS FsmP +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Unit#UNIT CargoCarrier +function CARGO_REPRESENTABLE:OnBoarded( FsmP, Event, From, To, CargoCarrier ) + self:F() + + if self:IsNear( CargoCarrier ) then + self:_NextEvent( FsmP.Load, CargoCarrier ) + else + self:_NextEvent( FsmP.Boarded, CargoCarrier ) end +end + +--- UnBoarded Event. +-- @param #CARGO self +-- @param StateMachine#STATEMACHINE_PROCESS FsmP +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Unit#UNIT CargoCarrier +function CARGO_REPRESENTABLE:OnUnBoarded( FsmP, Event, From, To ) + self:F() + + if self.CargoObject:GetVelocityKMH() <= 0.1 then + self:_NextEvent( FsmP.UnLoad ) + else + self:_NextEvent( FsmP.Boarded, CargoCarrier ) + end +end + +--- Load Event. +-- @param #CARGO self +-- @param StateMachine#STATEMACHINE_PROCESS FsmP +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Unit#UNIT CargoCarrier +function CARGO_REPRESENTABLE:OnLoad( FsmP, Event, From, To, CargoCarrier ) + self:F() + + self.CargoCarrier = CargoCarrier + self.CargoObject:Destroy() +end end @@ -316,22 +338,22 @@ do -- 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 } ) - - return self - end +--- 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 } ) + + return self +end end @@ -591,31 +613,49 @@ function CARGO_SLINGLOAD:Spawn( Client ) + + + -- This does not work in 1.5.2. + + + CargoStatic = StaticObject.getByName( self.CargoName ) + + + if CargoStatic then + + + CargoStatic:destroy() + + + end + + + --]] CargoStatic = StaticObject.getByName( self.CargoStaticName ) @@ -1028,3 +1068,6 @@ end + + + diff --git a/Moose Development/Moose/Object.lua b/Moose Development/Moose/Object.lua index eb546fa22..a2f804e5f 100644 --- a/Moose Development/Moose/Object.lua +++ b/Moose Development/Moose/Object.lua @@ -71,7 +71,7 @@ end --- Destroys the OBJECT. -- @param #OBJECT self -- @return #nil The DCS Unit is not existing or alive. -function UNIT:Destroy() +function OBJECT:Destroy() self:F2( self.ObjectName ) local DCSObject = self:GetDCSObject() diff --git a/Moose Development/Moose/Point.lua b/Moose Development/Moose/Point.lua index 2372135c1..2c1adb920 100644 --- a/Moose Development/Moose/Point.lua +++ b/Moose Development/Moose/Point.lua @@ -383,17 +383,18 @@ end --- Build an ground type route point. -- @param #POINT_VEC3 self --- @param #POINT_VEC3.RoutePointAction Formation The route point Formation. -- @param 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( Formation, Speed ) +function POINT_VEC3:RoutePointGround( Speed, Formation ) self:F2( { Formation, Speed } ) local RoutePoint = {} RoutePoint.x = self.PointVec3.x RoutePoint.y = self.PointVec3.z - RoutePoint.action = Formation + RoutePoint.action = Formation or "" + RoutePoint.speed = Speed / 3.6 RoutePoint.speed_locked = true @@ -555,6 +556,26 @@ function POINT_VEC2:NewFromVec2( Vec2, LandHeightAdd ) 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 +function POINT_VEC2:NewFromVec3( Vec3 ) + + local self = BASE:Inherit( self, BASE:New() ) + local Vec2 = { x = Vec3.x, y = Vec3.z } + + 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.PointVec2 = Vec2 + self:F2( self.PointVec3 ) + + 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}. diff --git a/Moose Mission Setup/Moose.lua b/Moose Mission Setup/Moose.lua index bffc3ce0e..f943793d3 100644 --- a/Moose Mission Setup/Moose.lua +++ b/Moose Mission Setup/Moose.lua @@ -1,29472 +1,31 @@ -env.info( '*** MOOSE STATIC INCLUDE START *** ' ) -env.info( 'Moose Generation Timestamp: 20160803_2050' ) +env.info( '*** MOOSE DYNAMIC INCLUDE START *** ' ) +env.info( 'Moose Generation Timestamp: 20160805_0653' ) + 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) + if not Include.Files[ IncludeFile ] then + Include.Files[IncludeFile] = IncludeFile + env.info( "Include:" .. IncludeFile .. " from " .. Include.ProgramPath ) + local f = assert( base.loadfile( Include.ProgramPath .. IncludeFile .. ".lua" ) ) + if f == nil then + error ("Could not load MOOSE file " .. IncludeFile .. ".lua" ) 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 + env.info( "Include:" .. IncludeFile .. " loaded from " .. Include.ProgramPath ) + return f() end end end +Include.ProgramPath = "Scripts/Moose/" -routines.utils.toDegree = function(angle) - return angle*180/math.pi -end +env.info( "Include.ProgramPath = " .. Include.ProgramPath) -routines.utils.toRadian = function(angle) - return angle*math.pi/180 -end +Include.Files = {} -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 ) - - 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. --- --- 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 - - - - - - - - - - - - - - - - ---- 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.initiator, Event.IniDCSUnitName, Event.target, Event.TgtDCSUnitName, Event.weapon, 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 - ---- This module contains the MENU classes. --- --- There is a small note... When you see a class like MENU_COMMAND_COALITION with COMMAND in italic, it acutally represents it like this: `MENU_COMMAND_COALITION`. --- --- === --- --- DCS Menus can be managed using the MENU classes. --- The advantage of using MENU classes is that it hides the complexity of dealing with menu management in more advanced scanerios where you need to --- set menus and later remove them, and later set them again. You'll find while using use normal DCS scripting functions, that setting and removing --- menus is not a easy feat if you have complex menu hierarchies defined. --- Using the MOOSE menu classes, the removal and refreshing of menus are nicely being handled within these classes, and becomes much more easy. --- On top, MOOSE implements **variable parameter** passing for command menus. --- --- There are basically two different MENU class types that you need to use: --- --- ### 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". --- --- ### 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". --- --- === --- --- The above menus classes **are derived** from 2 main **abstract** classes defined within the MOOSE framework (so don't use these): --- --- 1) MENU_ BASE abstract base classes (don't use them) --- ==================================================== --- The underlying base menu classes are **NOT** to be used within your missions. --- 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} --- -------------------------------------------------- --- 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} --- ---------------------------------------------------------- --- The @{#MENU_COMMAND_BASE} class defines the main MENU class where other MENU COMMAND_ classes are derived from, in order to set commands. --- --- === --- --- **The next menus define the MENU classes that you can use within your missions.** --- --- 2) MENU MISSION classes --- ====================== --- The underlying classes manage the menus for a complete mission file. --- --- 2.1) @{Menu#MENU_MISSION} class, extends @{Menu#MENU_BASE} --- --------------------------------------------------------- --- The @{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} --- ------------------------------------------------------------------------- --- The @{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}. --- --- === --- --- 3) MENU COALITION classes --- ========================= --- The underlying classes manage the menus for whole coalitions. --- --- 3.1) @{Menu#MENU_COALITION} class, extends @{Menu#MENU_BASE} --- ------------------------------------------------------------ --- The @{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} --- ---------------------------------------------------------------------------- --- The @{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}. --- --- === --- --- 4) MENU GROUP classes --- ===================== --- 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} --- -------------------------------------------------------- --- The @{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} --- ------------------------------------------------------------------------ --- The @{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}. --- --- === --- --- 5) MENU CLIENT classes --- ====================== --- The underlying classes manage the menus for units with skill level client or player. --- --- 5.1) @{Menu#MENU_CLIENT} class, extends @{Menu#MENU_BASE} --- --------------------------------------------------------- --- The @{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} --- ------------------------------------------------------------------------- --- The @{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}. --- --- === --- --- ### Contributions: - --- ### Authors: FlightControl : Design & Programming --- --- @module Menu - - -do -- MENU_BASE - - --- The MENU_BASE class - -- @type MENU_BASE - -- @extends Base#BASE - MENU_BASE = { - ClassName = "MENU_BASE", - MenuPath = nil, - MenuText = "", - MenuParentPath = nil - } - - --- Consructor - function MENU_BASE:New( MenuText, ParentMenu ) - - local MenuParentPath = {} - if ParentMenu ~= nil then - MenuParentPath = ParentMenu.MenuPath - end - - local self = BASE:Inherit( self, BASE:New() ) - - self.MenuPath = nil - self.MenuText = MenuText - self.MenuParentPath = MenuParentPath - - return self - end - -end - -do -- MENU_COMMAND_BASE - - --- The MENU_COMMAND_BASE class - -- @type MENU_COMMAND_BASE - -- @field #function MenuCallHandler - -- @extends Menu#MENU_BASE - MENU_COMMAND_BASE = { - ClassName = "MENU_COMMAND_BASE", - CommandMenuFunction = nil, - CommandMenuArgument = nil, - MenuCallHandler = nil, - } - - --- Constructor - function MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, CommandMenuArguments ) - - local self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) ) - - self.CommandMenuFunction = CommandMenuFunction - self.MenuCallHandler = function( CommandMenuArguments ) - self.CommandMenuFunction( unpack( CommandMenuArguments ) ) - end - - return self - end - -end - - -do -- MENU_MISSION - - --- The MENU_MISSION class - -- @type MENU_MISSION - -- @extends Menu#MENU_BASE - MENU_MISSION = { - ClassName = "MENU_MISSION" - } - - --- MENU_MISSION constructor. Creates a new MENU_MISSION object and creates the menu for a complete mission file. - -- @param #MENU_MISSION self - -- @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_MISSION self - function MENU_MISSION:New( MenuText, ParentMenu ) - - local self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) ) - - self:F( { MenuText, ParentMenu } ) - - self.MenuText = MenuText - self.ParentMenu = ParentMenu - - self.Menus = {} - - self:T( { MenuText } ) - - self.MenuPath = missionCommands.addSubMenu( MenuText, self.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_MISSION. Note that the main menu is kept! - -- @param #MENU_MISSION self - -- @return #MENU_MISSION self - function MENU_MISSION:RemoveSubMenus() - self:F( self.MenuPath ) - - for MenuID, Menu in pairs( self.Menus ) do - Menu:Remove() - end - - end - - --- Removes the main menu and the sub menus recursively of this MENU_MISSION. - -- @param #MENU_MISSION self - -- @return #nil - function MENU_MISSION:Remove() - self:F( self.MenuPath ) - - self:RemoveSubMenus() - missionCommands.removeItem( self.MenuPath ) - if self.ParentMenu then - self.ParentMenu.Menus[self.MenuPath] = nil - end - - return nil - end - -end - -do -- MENU_MISSION_COMMAND - - --- The MENU_MISSION_COMMAND class - -- @type MENU_MISSION_COMMAND - -- @extends Menu#MENU_COMMAND_BASE - MENU_MISSION_COMMAND = { - ClassName = "MENU_MISSION_COMMAND" - } - - --- MENU_MISSION constructor. Creates a new radio command item for a complete mission file, which can invoke a function with parameters. - -- @param #MENU_MISSION_COMMAND self - -- @param #string MenuText The text for the menu. - -- @param Menu#MENU_MISSION ParentMenu The parent menu. - -- @param CommandMenuFunction A function that is called when the menu key is pressed. - -- @param CommandMenuArgument An argument for the function. There can only be ONE argument given. So multiple arguments must be wrapped into a table. See the below example how to do this. - -- @return #MENU_MISSION_COMMAND self - function MENU_MISSION_COMMAND:New( MenuText, ParentMenu, CommandMenuFunction, ... ) - - local self = BASE:Inherit( self, MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, arg ) ) - - self.MenuText = MenuText - self.ParentMenu = ParentMenu - - self:T( { MenuText, CommandMenuFunction, arg } ) - - - self.MenuPath = missionCommands.addCommand( MenuText, self.MenuParentPath, self.MenuCallHandler, arg ) - - ParentMenu.Menus[self.MenuPath] = self - - return self - end - - --- Removes a radio command item for a coalition - -- @param #MENU_MISSION_COMMAND self - -- @return #nil - function MENU_MISSION_COMMAND:Remove() - self:F( self.MenuPath ) - - missionCommands.removeItem( self.MenuPath ) - if self.ParentMenu then - self.ParentMenu.Menus[self.MenuPath] = nil - end - return nil - end - -end - - - -do -- MENU_COALITION - - --- The MENU_COALITION class - -- @type MENU_COALITION - -- @extends Menu#MENU_BASE - -- @usage - -- -- This demo creates a menu structure for the planes within the red coalition. - -- -- To test, join the planes, then look at the other radio menus (Option F10). - -- -- Then switch planes and check if the menu is still there. - -- - -- local Plane1 = CLIENT:FindByName( "Plane 1" ) - -- local Plane2 = CLIENT:FindByName( "Plane 2" ) - -- - -- - -- -- This would create a menu for the red coalition under the main DCS "Others" menu. - -- local MenuCoalitionRed = MENU_COALITION:New( coalition.side.RED, "Manage Menus" ) - -- - -- - -- local function ShowStatus( StatusText, Coalition ) - -- - -- MESSAGE:New( Coalition, 15 ):ToRed() - -- Plane1:Message( StatusText, 15 ) - -- Plane2:Message( StatusText, 15 ) - -- end - -- - -- local MenuStatus -- Menu#MENU_COALITION - -- local MenuStatusShow -- Menu#MENU_COALITION_COMMAND - -- - -- local function RemoveStatusMenu() - -- MenuStatus:Remove() - -- end - -- - -- local function AddStatusMenu() - -- - -- -- This would create a menu for the red coalition under the MenuCoalitionRed menu object. - -- MenuStatus = MENU_COALITION:New( coalition.side.RED, "Status for Planes" ) - -- MenuStatusShow = MENU_COALITION_COMMAND:New( coalition.side.RED, "Show Status", MenuStatus, ShowStatus, "Status of planes is ok!", "Message to Red Coalition" ) - -- end - -- - -- local MenuAdd = MENU_COALITION_COMMAND:New( coalition.side.RED, "Add Status Menu", MenuCoalitionRed, AddStatusMenu ) - -- local MenuRemove = MENU_COALITION_COMMAND:New( coalition.side.RED, "Remove Status Menu", MenuCoalitionRed, RemoveStatusMenu ) - MENU_COALITION = { - ClassName = "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 #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 - function MENU_COALITION:New( Coalition, MenuText, ParentMenu ) - - local self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) ) - - self:F( { Coalition, MenuText, ParentMenu } ) - - self.Coalition = Coalition - self.MenuText = MenuText - self.ParentMenu = ParentMenu - - self.Menus = {} - - self:T( { MenuText } ) - - self.MenuPath = missionCommands.addSubMenuForCoalition( Coalition, MenuText, self.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. Note that the main menu is kept! - -- @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 main menu and the sub menus recursively of this MENU_COALITION. - -- @param #MENU_COALITION self - -- @return #nil - function MENU_COALITION:Remove() - self:F( self.MenuPath ) - - self:RemoveSubMenus() - missionCommands.removeItemForCoalition( self.Coalition, self.MenuPath ) - if self.ParentMenu then - self.ParentMenu.Menus[self.MenuPath] = nil - end - - return nil - end - -end - -do -- MENU_COALITION_COMMAND - - --- The MENU_COALITION_COMMAND class - -- @type MENU_COALITION_COMMAND - -- @extends Menu#MENU_COMMAND_BASE - MENU_COALITION_COMMAND = { - ClassName = "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 #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. - -- @param CommandMenuArgument An argument for the function. There can only be ONE argument given. So multiple arguments must be wrapped into a table. See the below example how to do this. - -- @return #MENU_COALITION_COMMAND self - function MENU_COALITION_COMMAND:New( Coalition, MenuText, ParentMenu, CommandMenuFunction, ... ) - - local self = BASE:Inherit( self, MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, arg ) ) - - self.MenuCoalition = Coalition - self.MenuText = MenuText - self.ParentMenu = ParentMenu - - self:T( { MenuText, CommandMenuFunction, arg } ) - - - self.MenuPath = missionCommands.addCommandForCoalition( self.MenuCoalition, MenuText, self.MenuParentPath, self.MenuCallHandler, arg ) - - ParentMenu.Menus[self.MenuPath] = self - - return self - end - - --- Removes a radio command item for a coalition - -- @param #MENU_COALITION_COMMAND self - -- @return #nil - function MENU_COALITION_COMMAND:Remove() - self:F( self.MenuPath ) - - missionCommands.removeItemForCoalition( self.MenuCoalition, self.MenuPath ) - if self.ParentMenu then - self.ParentMenu.Menus[self.MenuPath] = nil - end - return nil - end - -end - -do -- MENU_CLIENT - - -- 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 = {} - - --- MENU_COALITION constructor. Creates a new radio command item for a coalition, which can invoke a function with parameters. - -- @type MENU_CLIENT - -- @extends Menu#MENU_BASE - -- @usage - -- -- This demo creates a menu structure for the two clients of planes. - -- -- Each client will receive a different menu structure. - -- -- To test, join the planes, then look at the other radio menus (Option F10). - -- -- Then switch planes and check if the menu is still there. - -- -- And play with the Add and Remove menu options. - -- - -- -- Note that in multi player, this will only work after the DCS clients bug is solved. - -- - -- local function ShowStatus( PlaneClient, StatusText, Coalition ) - -- - -- MESSAGE:New( Coalition, 15 ):ToRed() - -- PlaneClient:Message( StatusText, 15 ) - -- end - -- - -- local MenuStatus = {} - -- - -- local function RemoveStatusMenu( MenuClient ) - -- local MenuClientName = MenuClient:GetName() - -- MenuStatus[MenuClientName]:Remove() - -- end - -- - -- --- @param 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. - -- MenuStatus[MenuClientName] = MENU_CLIENT:New( MenuClient, "Status for Planes" ) - -- MENU_CLIENT_COMMAND:New( MenuClient, "Show Status", MenuStatus[MenuClientName], ShowStatus, MenuClient, "Status of planes is ok!", "Message to Red Coalition" ) - -- end - -- - -- SCHEDULER:New( nil, - -- function() - -- local PlaneClient = CLIENT:FindByName( "Plane 1" ) - -- if PlaneClient and PlaneClient:IsAlive() then - -- local MenuManage = MENU_CLIENT:New( PlaneClient, "Manage Menus" ) - -- MENU_CLIENT_COMMAND:New( PlaneClient, "Add Status Menu Plane 1", MenuManage, AddStatusMenu, PlaneClient ) - -- MENU_CLIENT_COMMAND:New( PlaneClient, "Remove Status Menu Plane 1", MenuManage, RemoveStatusMenu, PlaneClient ) - -- end - -- end, {}, 10, 10 ) - -- - -- SCHEDULER:New( nil, - -- function() - -- local PlaneClient = CLIENT:FindByName( "Plane 2" ) - -- if PlaneClient and PlaneClient:IsAlive() then - -- local MenuManage = MENU_CLIENT:New( PlaneClient, "Manage Menus" ) - -- MENU_CLIENT_COMMAND:New( PlaneClient, "Add Status Menu Plane 2", MenuManage, AddStatusMenu, PlaneClient ) - -- MENU_CLIENT_COMMAND:New( PlaneClient, "Remove Status Menu Plane 2", MenuManage, RemoveStatusMenu, PlaneClient ) - -- end - -- end, {}, 10, 10 ) - MENU_CLIENT = { - ClassName = "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 #string MenuText The text for the menu. - -- @param #table ParentMenu The parent menu. - -- @return #MENU_CLIENT self - function MENU_CLIENT:New( Client, 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( { Client, MenuText, ParentMenu } ) - - self.MenuClient = Client - self.MenuClientGroupID = Client: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( { Client: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( { Client: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 #nil - 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_COMMAND - MENU_CLIENT_COMMAND = { - ClassName = "MENU_CLIENT_COMMAND" - } - - --- 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 #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. - -- @param CommandMenuArgument An argument for the function. - -- @return Menu#MENU_CLIENT_COMMAND self - function MENU_CLIENT_COMMAND:New( MenuClient, MenuText, ParentMenu, CommandMenuFunction, ... ) - - -- Arrange meta tables - - local MenuParentPath = {} - if ParentMenu ~= nil then - MenuParentPath = ParentMenu.MenuPath - end - - local self = BASE:Inherit( self, MENU_COMMAND_BASE:New( MenuText, MenuParentPath, CommandMenuFunction, arg ) ) -- Menu#MENU_CLIENT_COMMAND - - 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, arg } ) - - 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, self.MenuCallHandler, arg ) - MenuPath[MenuPathID] = self.MenuPath - - ParentMenu.Menus[self.MenuPath] = self - - return self - end - - --- Removes a menu structure for a client. - -- @param #MENU_CLIENT_COMMAND self - -- @return #nil - 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 groups. - -- Menus don't dissapear when groups for players 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_BASE - -- @usage - -- -- This demo creates a menu structure for the two groups of planes. - -- -- Each group will receive a different menu structure. - -- -- To test, join the planes, then look at the other radio menus (Option F10). - -- -- Then switch planes and check if the menu is still there. - -- -- And play with the Add and Remove menu options. - -- - -- -- Note that in multi player, this will only work after the DCS groups bug is solved. - -- - -- local function ShowStatus( PlaneGroup, StatusText, Coalition ) - -- - -- MESSAGE:New( Coalition, 15 ):ToRed() - -- PlaneGroup:Message( StatusText, 15 ) - -- end - -- - -- local MenuStatus = {} - -- - -- local function RemoveStatusMenu( MenuGroup ) - -- local MenuGroupName = MenuGroup:GetName() - -- MenuStatus[MenuGroupName]:Remove() - -- end - -- - -- --- @param 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. - -- MenuStatus[MenuGroupName] = MENU_GROUP:New( MenuGroup, "Status for Planes" ) - -- MENU_GROUP_COMMAND:New( MenuGroup, "Show Status", MenuStatus[MenuGroupName], ShowStatus, MenuGroup, "Status of planes is ok!", "Message to Red Coalition" ) - -- end - -- - -- SCHEDULER:New( nil, - -- function() - -- local PlaneGroup = GROUP:FindByName( "Plane 1" ) - -- if PlaneGroup and PlaneGroup:IsAlive() then - -- local MenuManage = MENU_GROUP:New( PlaneGroup, "Manage Menus" ) - -- MENU_GROUP_COMMAND:New( PlaneGroup, "Add Status Menu Plane 1", MenuManage, AddStatusMenu, PlaneGroup ) - -- MENU_GROUP_COMMAND:New( PlaneGroup, "Remove Status Menu Plane 1", MenuManage, RemoveStatusMenu, PlaneGroup ) - -- end - -- end, {}, 10, 10 ) - -- - -- SCHEDULER:New( nil, - -- function() - -- local PlaneGroup = GROUP:FindByName( "Plane 2" ) - -- if PlaneGroup and PlaneGroup:IsAlive() then - -- local MenuManage = MENU_GROUP:New( PlaneGroup, "Manage Menus" ) - -- MENU_GROUP_COMMAND:New( PlaneGroup, "Add Status Menu Plane 2", MenuManage, AddStatusMenu, PlaneGroup ) - -- MENU_GROUP_COMMAND:New( PlaneGroup, "Remove Status Menu Plane 2", MenuManage, RemoveStatusMenu, PlaneGroup ) - -- end - -- end, {}, 10, 10 ) - -- - MENU_GROUP = { - ClassName = "MENU_GROUP" - } - - --- 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 #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 - - 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 main menu and sub menus recursively of this MENU_GROUP. - -- @param #MENU_GROUP self - -- @return #nil - 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_BASE - 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, ... ) - - 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] = {} - 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 - - return self - end - - --- Removes a menu structure for a group. - -- @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] - - - 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 - ---- 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 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, Height } ) - - Height = Height or 0 - local Vec2 = self:GetVec2() - - local Vec3 = { x = Vec2.x, y = land.getHeight( self:GetVec2() ) + Height, z = Vec2.y } - - self:T2( { Vec3 } ) - - return Vec3 -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 ) - - 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} --- =================================================== --- 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. --- --- ==== --- --- ### Contributions: --- --- * Mechanist : Concept & Testing --- --- ### Authors: --- --- * FlightControl : Design & Programming --- --- @module Set - - ---- 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:F( ObjectName ) - - local t = self.Set[ObjectName] - - self:E( { ObjectName, t } ) - - 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:F3( { Event } ) - - if Event.IniDCSUnit then - local ObjectName, Object = self:FindInDatabase( Event ) - 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 ) - 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 ) - - return self:New( Vec3.x, Vec3.y, Vec3.z ) -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 coordinates of the POINT_VEC3 in Vec2 format. --- @param #POINT_VEC3 self --- @return DCSTypes#Vec2 The Vec2 coodinate. -function POINT_VEC3:GetVec2() - return { x = self.PointVec3.x, y = self.PointVec3.z } -end - - ---- Return the x coordinate of the POINT_VEC3. --- @param #POINT_VEC3 self --- @return #number The x coodinate. -function POINT_VEC3:GetX() - self:F2(self.PointVec3.x) - 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() - self:F2(self.PointVec3.y) - 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() - self:F2(self.PointVec3.z) - return self.PointVec3.z -end - ---- Return a random Vec3 point 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 -function POINT_VEC3:GetRandomVec2InRadius( OuterRadius, InnerRadius ) - self:F2( { self.PointVec3, OuterRadius, InnerRadius } ) - - local Theta = 2 * math.pi * math.random() - local Radials = math.random() + math.random() - if Radials > 1 then - Radials = 2 - Radials - end - - local RadialMultiplier - if InnerRadius and InnerRadius <= OuterRadius then - RadialMultiplier = ( OuterRadius - InnerRadius ) * Radials + InnerRadius - else - RadialMultiplier = OuterRadius * Radials - end - - local RandomVec3 - if OuterRadius > 0 then - RandomVec3 = { x = math.cos( Theta ) * RadialMultiplier + self:GetX(), y = math.sin( Theta ) * RadialMultiplier + self:GetZ() } - else - RandomVec3 = { x = self:GetX(), y = self:GetZ() } - end - - return RandomVec3 -end - ---- Return a random Vec3 point 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 -function POINT_VEC3:GetRandomVec3InRadius( OuterRadius, InnerRadius ) - - local RandomVec2 = self:GetRandomVec2InRadius( OuterRadius, InnerRadius ) - local y = self:GetY() + math.random( InnerRadius, OuterRadius ) - local RandomVec3 = { x = RandomVec2.x, y = y, z = RandomVec2.z } - - return RandomVec3 -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 - ---- 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 -function POINT_VEC2:NewFromVec2( Vec2, LandHeightAdd ) - - local self = BASE:Inherit( self, BASE:New() ) - - local LandHeight = land.getHeight( Vec2 ) - if LandHeightAdd then - LandHeight = LandHeight + LandHeightAdd - end - - local self = BASE:Inherit( self, POINT_VEC3:New( Vec2.x, LandHeight, Vec2.y ) ) - self:F2( { Vec2.x, Vec2.y, LandHeightAdd } ) - - self.PointVec2 = Vec2 - self:F2( self.PointVec3 ) - - 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 = MENU_MISSION:New( 'Scoring' ) - self.AllScoresMenu = MENU_MISSION_COMMAND: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 - 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 methods (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 represents the GROUP Template (definition). --- --- 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. --- 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, 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}. --- --- 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}. --- --- 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) Retrieve alive GROUPs spawned by the SPAWN object --- ------------------------------------------------------ --- The SPAWN class administers which GROUPS it has reserved (in stock) or has created during mission execution. --- 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. --- --- 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. --- 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() ) -- #SPAWN - 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 ... --- @param #number SpawnHeight (optional) Specifies the **additional** height in meters that can be added to the base height specified at each waypoint in the ME. --- @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, SpawnHeight ) - self:F( { self.SpawnTemplatePrefix, SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight } ) - - self.SpawnRandomizeRoute = true - self.SpawnRandomizeRouteStartPoint = SpawnStartPoint - self.SpawnRandomizeRouteEndPoint = SpawnEndPoint - self.SpawnRandomizeRouteRadius = SpawnRadius - self.SpawnRandomizeRouteHeight = SpawnHeight - - 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 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. --- 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. --- @return #nil Nothing was spawned. -function SPAWN:SpawnFromVec3( Vec3, OuterRadius, InnerRadius, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, Vec3, OuterRadius, InnerRadius, SpawnIndex } ) - - local PointVec3 = POINT_VEC3:NewFromVec3( Vec3 ) - self:T2(PointVec3) - - if SpawnIndex then - else - SpawnIndex = self.SpawnIndex + 1 - end - - if self:_GetSpawnIndex( SpawnIndex ) then - - local SpawnTemplate = self.SpawnGroups[self.SpawnIndex].SpawnTemplate - - if SpawnTemplate then - - self:T( { "Current point of ", self.SpawnTemplatePrefix, Vec3 } ) - - 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 ) - - return self:SpawnWithIndex( self.SpawnIndex ) - end - end - - return nil -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. --- 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. --- @return #nil Nothing was spawned. -function SPAWN:SpawnFromVec2( Vec2, OuterRadius, InnerRadius, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, Vec2, OuterRadius, InnerRadius, SpawnIndex } ) - - local PointVec2 = POINT_VEC2:NewFromVec2( Vec2 ) - return self:SpawnFromVec3( PointVec2:GetVec3(), OuterRadius, InnerRadius, 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. --- 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. --- @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 - return self:SpawnFromVec3( HostUnit:GetPointVec3(), OuterRadius, InnerRadius, 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). --- 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. --- @return #nil Nothing was spawned. -function SPAWN:SpawnFromStatic( HostStatic, OuterRadius, InnerRadius, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, HostStatic, OuterRadius, InnerRadius, SpawnIndex } ) - - if HostStatic and HostStatic:IsAlive() then - return self:SpawnFromVec3( HostStatic:GetPointVec3(), OuterRadius, InnerRadius, 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. --- @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 ZoneRandomize then - return self:SpawnFromVec2( Zone:GetVec2(), Zone:GetRadius(), 0, SpawnIndex ) - else - return self:SpawnFromVec2( Zone:GetVec2(), 0, 0, SpawnIndex ) - 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 - ---- 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 #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. --- local GroupPlane, Index = SpawnPlanes:GetFirstAliveGroup() --- while GroupPlane ~= nil do --- -- Do actions with the GroupPlane object. --- GroupPlane, Index = SpawnPlanes:GetNextAliveGroup( Index ) --- end -function SPAWN:GetFirstAliveGroup() - self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) - - for SpawnIndex = 1, self.SpawnCount do - local SpawnGroup = self:GetGroupFromIndex( SpawnIndex ) - if SpawnGroup and SpawnGroup:IsAlive() then - return SpawnGroup, SpawnIndex - end - end - - return nil, nil -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. --- @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. --- @usage --- -- 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. --- GroupPlane, Index = SpawnPlanes:GetNextAliveGroup( Index ) --- end -function SPAWN:GetNextAliveGroup( SpawnIndexStart ) - self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnIndexStart } ) - - SpawnIndexStart = SpawnIndexStart + 1 - for SpawnIndex = SpawnIndexStart, self.SpawnCount do - local SpawnGroup = self:GetGroupFromIndex( SpawnIndex ) - if SpawnGroup and SpawnGroup:IsAlive() then - return SpawnGroup, SpawnIndex - end - end - - 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. --- @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. --- @usage --- -- 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. --- end -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 ) - - -- Manage randomization of altitude for airborne units ... - if SpawnTemplate.SpawnCategoryID == Group.Category.AIRPLANE or SpawnTemplate.SpawnCategoryID == Group.Category.HELICOPTER then - if SpawnTemplate.route.points[t].alt and self.SpawnRandomizeRouteHeight then - SpawnTemplate.route.points[t].alt = SpawnTemplate.route.points[t].alt + math.random( 1, self.SpawnRandomizeRouteHeight ) - end - else - SpawnTemplate.route.points[t].alt = nil - end - - 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 - self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].x = self.SpawnTemplate.units[1].x - self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].y = self.SpawnTemplate.units[1].y - self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].alt = self.SpawnTemplate.units[1].alt - 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 - if TrainerTargetDCSUnit then - 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 - else - -- TODO: some weapons don't know the target unit... Need to develop a workaround for this. - SCHEDULER:New( TrainerWeapon, TrainerWeapon.destroy, {}, 2 ) - 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. --- --- === --- --- ### 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} --- ================================================================== --- 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 --- --- ### Contributions: Dutch Baron - Concept & Testing --- ### Author: FlightControl - Framework Design & Programming --- --- @module AirbasePolice - - - - - ---- @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, 30 ) - - 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() ) ) - if Distance < MinDistance then - MinDistance = Distance - NearestFAC = FACUnit - end - end - end - end - - 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." - 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', "Dummy" ) - - -- 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.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: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 - 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 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 ---- 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 - - - - -BASE:TraceOnOff( false ) +BASE:TraceOnOff( true ) env.info( '*** MOOSE INCLUDE END *** ' ) diff --git a/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_Board/MOOSE_Test_CARGO_Board.miz b/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_Board/MOOSE_Test_CARGO_Board.miz new file mode 100644 index 0000000000000000000000000000000000000000..3437b5289c851f9b3b5e439d74f98d29d99e00f9 GIT binary patch literal 16744 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

P2r-@IY;maWMignmk^4iE22`mdi-#@# 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

o1T`j91bVtDlP5klyq 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

5ZKX3fgrO_mgEc# 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 T0RTXMzYo4UI|L~3ckBNEG=p~9 diff --git a/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_Pickup/Moose_Test_CARGO_Pickup.lua b/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_Pickup/Moose_Test_CARGO_Pickup.lua deleted file mode 100644 index 4d5165081..000000000 --- a/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_Pickup/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 Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_UnBoard/MOOSE_Test_CARGO_UnBoard.miz b/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_UnBoard/MOOSE_Test_CARGO_UnBoard.miz new file mode 100644 index 0000000000000000000000000000000000000000..de9c5fc2f8dc4b27851ec75f3244217d784a7c54 GIT binary patch literal 16755 zcmaL81z23ovMxNhySr;}cXxLS!9BRU6N0+N*3V;Rx0K|a!AP#XecmUwZ2mpZkkl0u{Ia%7-0(IBzR=H69 zCiO`kN+GN@=R!??+aoE=Gzh12q_m6!U9lkPrXLYtue0oX}f&!a=!fWSiB2Vbt8#dGPb2{?wp%R$f(;5k<$UX^g+|=>5OT+M*!#=~Nu9RS*$my%)VjHEa*>7Zsgq?> zbu|21SQn_lz^x5I?r*ymk-qpdvh}*)rDa8onwv&>7e8cRevpM5MT4A`Bdh1r13xSu z0~!oyeMYjN5~}9}&NW10%g$reE8b~5anWNS=8~&2X)L$1Pa>y@gn!v$7vi0H~<+xtMjHL&9}$4NPJVZy{^2@g0T-NH;e zzLq}M84FH1lZda1s_F~j%A3)C`~>=++}tTBJMm7w6-P3Xt>Eq0oF9wI@N&A{lfq6=OpTDalllf0NLN=#7!H{8A0O4w8A2;Iv9HptuOalT zTkyfUV{;;ve#t9|8PMryIS+{Q6i}If35j2OvYUVwx9)nfBQE9$x!xMT?e-+w!|ZiA zjuW$|-|Gw6K%Iuvkt&w0O!M{K1UpZZXy)%E6sl-*FjkeT zVXGa~0^0c|otJTTWP491-n-M2NMNr_A^ohF_+>S?V=IX>n{-;poApVEfJT#Fw60e^ z!V-<1yzUCSWYA}FEZv=R3?vNWoCOoUCoSUO`l;6uZf9a9MYf!uSf>v2jhHZg9I2?m z6mO+4p<&T3%rt5DIuPS>u>w~e0Cn%A6s6VmQdXd7JDItyGD61R{I zDWP`sd@-$4{(7+0*zNr`9z2|ViKdCOY1JT`FYds-<|tzexMs{K)KFd{k9C9Y*Lt-3 zJYu3M;M?@&Gs{_*l(;@`%+e6+=?<|pfhDEl7V+eyRpk{r=CXV+W!@cmA?v;_-)AQC z)@a#FUM|O4notXcq!Y$TmTJV5;OI>;ne;M^uskL1Am2bmMvj56;ioQJ7`v07g~L^L z8JZze9}giuNdX@%>BpLG2x-@+?7iKno5KPdm*7^Z>-zQMBlD`FE0i~nDrPSCd}>)0=s;G zCnWd|Wr6MOz$a2LWy&X3*)ve)imBLU6%Q((2qiWGCREswC*`)HRLE;XJVP)g-o_eI zhWA~K6WIzeRbI0)v00cCE`Ptgbodp#uCsv zC8^eY!B~h4{3(RBBu=Dl?eMvt4ShIbJ%z~tgunoxMI<5%@o(#lQZD?*N} z&SXMhO1nV?ybyd>XfA|-%Ixk@T0z{gjKv9#6^mqcE7R@caTCxL={;9xA*Y%T-n!=q z6`Gy~Q}rRuLIUR*VJ8uwgcpRYlISM=QAbDR8z{svf)E&dgD)FEiU2vt3R!yx9)XIe#It ziPdFc7#4>2HwB0$Mm=X+*EFIid4LLtZQhN7Ty6b!mbc%gH2`Lga?)Llq3YBMwL#nl z>j`l(GNstp0k_7Td(#f^Z#p?6=BY^1Obx?^94P{kX6~Q7iuOePWtZt8+%UFiaHM z;e+w*6V>IYKR^!`WMRK&@>m?qH+m`&6?~FM{UN4D(BOFTE%=A+ zcQ`n=T!__8vpBm|xt>8(BWrz#jj?m?^*!vyjsgzTF3=+1+Yq7T}yWVf?*xod6 zODEv`C55xnRu4scl=T+@usCRI;&c>G{Bt+&E-A0nBuqG?;{d{PF4$Zt1Ot>n`#C^- z_35BC3EiG|kUo|3+`#<36f8I36kO}P$>7Gv3zbi)Qb0F(}~4& z?N}I2Sy7Y8Q8jlF1WZc7L)h z>KT*DizA^e)Rv7!!$S$SAu=Iczzz?twz`)l@C7Le#2cW{l(spL$xB&V#5`sgzfop6 zB(tApSPJZT96_IW9DUE>vth-xe7#BQ+e)*{SU?rrw~Kzv=+=6ZAULA0&*H5TXVoY6 z1M6W9aFW}?N8=g-pMWE`Z!iWuNbPyV`$!3r1&NpgmyFi-XIGNkiHrWPNCsoR__4qvyx3jDHjUU7i?jMBf z$r*0zJ^XrxklKPX8J6SyT2ekzu~l10ExKa*cHC>_P(IWympV@@mm7KlQfn)n2tN8o zcR=7?5Q&54J>v8-36X$*+^Ok>Vpog_;q&SFYY>UywHqGoZ=w797vY`9r2~qPX}2F4 zU49mAXN!IVM$aZQ=Bmg}B)Tdb>`nS!2CLA5vQDN*v|3tXu3cATFBhUR37wPhG~#-5 zlBlJK6_Hq25V%iw{fZ7Ea}pCJjTafBKdWc=Em;wf+es>4p=Z&ShlyIlZ$4j9y+bMe zV=3YZH+1pyV+leA3IJezd|NX!*)ocWO9-pWsWF;Z8asciOAH-7WQXM#hQ}sl>B`h< z)VIp^RRDn&6j-@G+^kH!-G#&SjclztT2<(jbfl%B|;?2P=ZjO>lj zF*2&(Wex`N^X2GZMXy;%Tyb%cnd-Q@e~`1XZ-9rcs;ERHM6kdKbvcdD$RkM8O+<4J z=3(wDijwtx3n|n{9Kb_m`5p@jhks>%p0SNYIb;(2IhW*k7yHx-LnSda^Efr3Kv_jC zy)0~!NVt?{i%HnMRYO=)lZ2g7oJ3Hb1R|TE1@O<3gN-P(8+?>F^5cX3cgbxG?Nv;j z>|7j;O-JSFpk?SM80lpc)c;fV+|-YvcNjZLoB8T>Td8Lv|BWzn6Y^etg?4u=YEA(^gUsi2CR&H5*)P=Y{ z008@8m7AfXsfC@3lc^JMP1Y_$2>si3ryqn7P>q9Jq3%~-gj!i*lgfNm{)mWKrd&vX zW2u+&n->hSBQ}*K8HM?6oe843Sqope;%R=LuDOe)%=uWxOPc$*!;xguulju2*Xm|- zyGa)^;2|IOM6_hv*Xn#0S!=5o<#}II%~yuT`PCJ2n_F*=jA$#{;*H(_^O>blj)H}W zOqcYf%1XJHF59PNI-rIgMq?UJ(Pz~!l9tI8O?T$%Sk<*-uQZwL-edA8>KvVIOoDQC zIxG3-Ckz}-#ivw0%M=^zuKS;yjCZPfcgFMi*C>+inmydZWxJP?$BjNllj;fH0thWjkoSi`}x^bjQjKHR|d(?xOSd=UxT8Nsl|xF zMTo&?(5Vwa2}qGaNsvJ;VV$WVcs_*xk{?RJHVFM!+JXdvN8-QbTCmQ6LeRlN(1kQp zVd#hG0m#q+$Wp4gsw7HGqW^kG1^tJxBs81ke`_;9kfKHXQ7Ho*mU9-O#Qme91`(fj z=r70iC+AXFa@J{(G>bnut6)f(<%6+I|0@UD*fB9ElYEpvIj9q-g(3bDBm1y5woHfx zjUYDcUpA=+m{9VN|2u`I!L=evv?0tZ;Beeh5wqEWy}G2lj|3Wl{KF7%xSUu=_ebs= zcm+GgS#Bs%sC|_2lvOf)DC1bvmnKeE5J<+5Dmt~Q36=#nO)`>a^kzlH@8~<#XlgY{ z!uydiVxM0L^~}cb%H>r`QO~Jaqeo!f_$W#3oey5d2$7(Wr0*O*EH)5<m*hM-VBpeOBi?#9pzK5jeeyYdC$pwR$z3*!$Hk{fnmRmw|Y4W`m+jJ?j2XwgkB0A zD`7*qsH5*$L<=f>n>|L(Y7z+L<7An9&2+(=2bpdTG;hAoh$&f{&5@O#HI_~lvE|wq zdS%1GSFfCpE*Kv@_3e6CLeW}{;@!FS6Sb7{DACNmE4m)fA!hgl?_jElf}`B?o^=yz~qjq6s25BN24kpQvcC%cgCFvvwi z_O5AEn1-29&jOPM)_jSIsl+~hoEmY|QfgTneD9u~1O{2fRYa>8X*_wMxfH_PV|JS| zPzGs6%N_%9;u$r}z~3uj`+G^+qU(H2Hd#MrC{KRq?Y1sf7xWM0 zK41^xM|29knsfnFu+sG?GP%6J(?e%6u*qV!QN!!!EP^cF zx`~J>gv<_GRFt#3x6sG-X{(FrDR4|^E=(TXHoO` zS>|{_rP1pG3>9r}kPgkLO)sOVO9P>;Js8%Px%E0?FM=j9bP;nsp$ zfsa1L)SMPyAdfIICl9n0c=^6owIoiA0*A#H_|f-k<1*k8duE8U8#r{8LX0JNTf53L zKEtiKm(DYkc^O0wM&v)H>=ExirnVW8EPgXgCcC1ajtfahFY!t~i|Lzke+ccH3ulny zo)ZcYFiDDCG5tIq<5Q%k=aq)`=p%EZeeNpM zS+Pp!X9(WQ|B+!>dq1qd|1MU1PtiAoOO$p zp?OB*Z*oFcFwb-{UMEst6P{E^3!;Cz#ap29!Q;av=3oE-=Kt!JKR$?3RsPz?gzRgZ z_r84?F(0~OLoIsIYf07DQ0Izd{N2Qc1j!Isn_0K)trZ8k0$>jTx|in$qA@alcrgB*&0tZ-ff< zzjPy59f4tD$O~iCS5U%>*&xPmY%K*p+=d!tUH=0G z4{=sjYAxS2$H$mk9B8jP z>7LD|pQKk(g6uyE63Y-rWknK3?sn(v*NMB)q zWzQa(Mcc84E(U_8I&e1C$qn~AmC16}8Rms9ISCx}ne9(SZCVsy()8xzv0lvpzau{3 zMXL)ZiVL5fr{)%Lnst&U>UN;5YJH=f3D!zUE zP4FfP%n)Ni-e%KkR}XUJ-{D6B;T?aXwN`C;v!lfD>C-mn5QsnAUg)Sv6qZ(9=hr1lRpFuF4sbw`yoiTe`9YW?e4Gf-*kx*Mh11F55g#H zH>tuT2@wj1lJc)X{G1Mz9A$XKIp{qx#|c|_mKNn|(lUL@b|yv*{=;aWxg#11_86p`bmw|NIE=mT~TGd#tx&v z;_`Xlkb|esHk(G0Rqf7qDqYeRe-wwSLogB*SP9=37$(H>O7rYet>kzOuE&)BXd_Gw z4Z=%NRn1%3>Uy$_q20-RKtsasNb3R-4B^_g9QlH*T+^peYd|AtM$%SsTR0nxCscGn z(ziqsW0GtTpz5Qwt47Vgt`9dubv6u#tB#?O;k zyDeqph_PYBkRVH~xeYGvXc;krqpHL)PQ=#@V4jD5np+U(G2Ce~tCe-Vb1&h|+EnAJ zkXhbaT^V)853;^g$Nt3nXKFE$5&glgwf} zG$!$^)+|(h9+k!UM?WaK_{nuI3o3PX(Kuu)mDpLvoPSOpSw9KI938o9>Q+|4v$v+XH4RQqL##oW|x?!78ePmDIm zxU)3F%&GZ?opSX!ble-U#kkk&a|oJ zvPV@(r;D0(jSMebjXSWWTH zwMb>rufGB*6npy-9Hcw1`XL(B^&`FQAh?{316bo%s!zQ1^4s=lC6Km3uF&wAN4-wH9L?1XpzT{*+Y;S=`SqUs?St}{^D4~MW24$k&8K^- zFZ7G$qmRWadX?2F2S-8I*Wzs@8=dJ{IgA=9^Hy(FZCV>+ddHz=5##0Kr$(ASsy_Fp z73p8j7pw+$=B-Qb{ao1nZk=weI@8YSJ-AmV?(di1CvGOrG0kkt+%%nD!!mANTm-tc zD()CLiOxH;e7rkX-)H>3*txk0KF=DxomqFSmKv^FHSttWXw-8ajRT*FHr@nED>$x! zw~m^Xj5n?+^^51G6wFq`u6G&`R;*b-eZIxfRVu%S=jEKYry(lZzTc9J2Ja#W9`%{)g?C@7+%WijZ{~p* zKDYCqU$_6i8mog) zwmKW{Zlli`w~ij>3nf^-^6y^Tb^EGp8;{7IX+)Km&9z#P*oQI9B+pk)J;v! zHPA2eJ!oZ;IP=oaxtBMkb`!#ZV;BEB^;(;5Gav^mw=0)lwpJ4go6z&+8x4nF%tDIy z1A3I;PX6R_HI_?a{#RIlC<*aTkX6CE=I)|bb`x$aQg}=LrMLY>0c>an$aInC{`IjA z-D+_r=*Z2*o*}XV z*8(VL@?~3agwh2FH){N8ivicxTp(ZTFYfJZrmf*Tlq{I!*gEC@+RN$sudu{uf&M>9 zAPoFKY_cVLp=+;M3ST`e#O!)JWiye$jmV*)HV;AU0<{nxL}`B+7kpQq!pzSERf4b< z=Ody9s6|4^`bvR#Sd%kN!$_X)iG%}_zp)G6*u8t|5D>p&Kw~Qg3TzOq5ly@;ZN70h z7B=+UNr|R!21c(Fk}nKkry9Z%Ou-Q>sKgSpL@*F~uEl5%eBk4tZIQGm7U=Kf+2e)1ZxnpYGIoqbL7w9FW8;Km z+hEyED55FT_Nv;WHX{R!VFeGZCW=6fcrwFld4Ur=SNb%901+|)L>uI^ox&65d+fe_`80j;v<}^|J)XSgIkr@%BIl zOUNp;Q_Q~LntTtH<#e1&MjvKOxB8NFG9_feL{@I`DY+!whL4#=AoB-v*yXm`IvjSp zgzwj^U<64lElAZ_@IfN1$*)*3eud1`^<2P`tYtXW& z{UgxOdD(tjUjNVhcs!5ujAEJ^KafF?+#>3=ZXh{#;HUKM}o%Cv9pgc zcpFddEay1A511N!6CQ&#-8DP!=%1d91J6vfE-OqhWzA~;x=a}j)0tUZGP3u(N!I7% zCoSzP{KRR&T*fB?#o!%(UmJ@94$ZRrVfT6;+xhaB-r24HHk!?yJevVQJVH6y%J@p5 zJQtO@s&B_HV~+!}BUab0+XFT{UGZzkU1?=yOUrTfx&chYHU$nU0BQ+u{YdD-1N6y8 zZ}9+1)n4#DfKlvc!OkS~8IdBUZx5oK58fw2EMEzWH@%vNjH4uh8fq#5rGr1M0#h+V z$D5d0UW>22ihxKzc)_DTG*%J5NR)?E)S@uNdL`@>YF#BcR2(bHNh)(aHMloc%r%OC znpy();Y7%=!^gXB6V~|n6g~^lNh$- z_N>LU3p>{@;XYs`+|;d z!lBb(Gd7uul{BIy*bUC+Ds!13HD^eC?7gYW8-V7K@qW-n%0%2A^@dz6dH}K9Qaw$T z_PqjtPj5h3mWT~wS&UkF&f;raV=yk1X`CI8uFkge#oHS zSXI$LmF56jcP1KfnL0pvN(O3xT?_x3jp;|%qGj)ZuPcVoJx1>!WH`f$YVI(d?@s+8 zuH7=~{TUbmfd$8a7#OkrK)_(YR`|G7o)|KL9PtaBqs4&dRh#KEbkU6z?vNQt`V83B zDO4qS)7dvltd@h}k=T)zX+;A+IGjkt>*t904Er(_&gemL4hExKCIMPVQ9b0(9N6N< zy^s4?;jxCW4pTa|qi@M-TC&TlzEn<?|Q z_#5on6~O9VkE9B`V^u>;rqLv~dW7 z1U8L6^^(b5uf?Ao+#1jKPx*V}_0{9QS^#i%y+BDDOy+yO+!! z&9S&Mv>(aHM~a0s$vvq@zL`>3#UR+-^>k2v(*1g=DA3u_=6S1G-8YJ*N#yr13xvA3 z`ZbbtHOMWFrpNuQ{SQ81BE;G1q?y@VXd$4;1!*!#os!D5PH7DygCA+Ks@kU72H zCZF#6ED+eu|Lu@_HB=gbrq%|OqKXg=K(aSdl(?FmBBQL5D7~F1hDye059~dG;HuC%T{q=KB@`w;Um8! z_?q2Ct*6~N?{5M72iQtcAo2!6UhwYvb^TkRA13t@XW{%Z68eNaMU0bcRV9GW6AdD7 zC=_IZT$ebpl>8aHhnqC4H-5N+$1{oP6i&d?M5h)XvLL)BZhi+qB4zTxF!NNrzkwMH-CaLs~ zZ6hY%*zkrB;mJmxQ4^6OK-{W`PpqG{_sT;?gEFETC=3z-Ht996XP{fi2Pi1zdcj_G z6-oy@9SPASNJF)W1h~?Cj}1OC6pr~g{6e=#2~3oELoV6ATLX;BkmREoQ4>!5nW%XCgxtW%qn5w6*SGuvF}^Xf8n?vbiT_+rBNP-zXei+o zEaM%lmBhX$Y2bHiu$Z8ON}N|MBr!HXsm&-A%TyvPq8~gb=6}k*K86-E43YNX6;_nE z5xl!}y91a4vA#%jm?TV?QrfSpi91MyJsVrKg_0urv5|&O7sr!M!g1hf#zwi)Dxeb| z7Vr5X%sNhAg1Xrt18L2aP@S|v>K6fxCe`lWv$o&(0mLB$BC z=W7Y*4k((_F**FMbbLj=o*xfd_%9rw6@_P5KAnnn{vm9l0lG|T9Ch9dHHoGvP%Q4H zmkd&}IG7>}^+}}2!h?o&R9A`CP@ycC&EF_P`TL@)WWzUT5>v(>6X@V1fuHLULQv=o zC2E6Z>IRgO8d*c03M5DLinZeDg`v^{jX4Tx?@|aoSXo3mZ&DK&grC)ZBx97i6IfMC zmU`KPJI>lh)sr*L*Lg6q??I_vb0G_C?Oqarg>4byS8fU5N2y=RxZ zo~`)wu-D4CK2*7~9td`{gE=|)S2<;k9d5H~!K_QN;wy%!6D5A%#BI@^mCN7y{teP8 z9$!{>cgJNRwC$18QfOwRKOWgy0T@TD|2M>Gc^Wh2$nuH8=fbVbdnNA(4Keq^6*SWeX`;@T#2RH$n?5OxnE)5St6GqVVAM2VPz5!=;q zzC7~~7C4Y|dF<7Kibk=Xuif$?ajXs!69Y06Vx=SLXI$}uB|_4!&F~C1c>{5oFd*{z zTCuDS3KL?-Ub|ZRT)XV+IDF9cl+A*W;pk3c&95Bj5|aZs{Ai!}(Tvp>2>F*n^Wo!$ zMQRJpjr1q!;KBwdzg+h`P!EXstbrbY%kOsKV9M@?>-p)Z9X63-)CUkQ^E%|g?_qS5 z*-%I3hOq`xY6MaimsMoZ41|*Uoqsb_f8;{<5mt3fv>1$m5Jo$=)5^bL4XMFsb*LGx zi&UYtRpKv!#s^C1ox-MDfXip1u+^fnDVNt3Uk22+lB$mm#OH=N)O6A&KDv(&1b(EW z!aCqeV#7owYnnVFeM8M@L4?RgY@HN-cGuVbjL?jP)gYg!D-pl<`jZ1H zSVw$t@2j)^*LI}Eb_mY7*9%CY+X<38ie*eYereZLACzVMu|5cC%qVia!WVYkdva~= z1DgzstMoH#NMUE^?qOk;E{0fSRn`ur*dv~{2%bUleP?xN{Aupyg!|%AvvNFN0 z=i|HDozU|zPuGoci*|HM?vg1!pj_Vgqp^01Q^~h}ZX{5?3DI>VNktQvE6xy%2^x4V z&E(UcY?{Hfa?@}Ox|Ps0asdi?NS+GmJtvC10VS~^YuH|pg2fgSyUG?pZ(3%;LsV0b zYU;os+Z(Gu?WsJE+>Bf>9(dtUm??H7Q2Z(B6JM^uuZEK#1j$07Acv{qov8((;fB#p zhfai|ED4B#y@&`D90h=Y@=#N$z!H0|4Hkds*3?FefeFHr?TpKwUJ!lmj%+ zgo(3uYU^p2>kX?XviZJ93ph_SOWP9J9-J#+8~>0?>JS&bqj(i2%PH{Cb*N&7Np((V zhwRion(!bshij9<8Q;;C(KJ_kqSS*j=SM#`0(S|_1PCOEt;i7VFW!frL@^;wA5H#% zP3F+*4sU9N9%a}5OczZU4yLpF3$Mek-N96Pv_MnlF}-=rgIx#28#aeLRrSWn)>@hG z=G+JSjbH-=6CS^~73~QPkhk`ossb~;v-3Gc4=5%)5+|om=So8O4+oqcGbPb(@Yl{7 zu3Nv=btvjZZfDlgiA|TU>}0NcL?lm+?>Z4W+O84BYflt;RDw!}I^j4nus-5LfmH2? zsK+Kua+=TVJ8AFeN`%8PdofFLr*Kc07PiD@3CVKi*hxO;;~7`E|7wZS|6}HkWA+5H z)z7_Js~o4_7Rs4>e24*2<6achuGzt@-$Gd**r4rmz$*efaJ+A_gIjLM2c{Ehvb0=` zJkuj(v|m??K?*b*Z>a|?FE4A$o~d7i{3tSF`Tc{e_EOG^^FtGxQy0q9+n~6veE*b6&LkKqx6Idlus>H};mB z(VlDc36E$9YC}|B#F?{1-pZf&@XPDZ_OnQ57#D)P=){j^_TxnNY7yiZLi{5A3_59N zBkf1FQY*nw{I0r9Z56}G75P?AY1v8+G)_d48NJ2`u)s2rw-AY6BmGLt1$k@2Nd;q9 z>I$S&Rv?8>1J}JG*y2n+A0j4cpHE}NXC?{k$D*@jF9=2XlZo4)B6{t{m;ymel5Pyf zCCtr7+jSK-|6O}kF*K6rvlcCi=S-Mh!Dm`GL);^KH0efcZ=tKcPq4B@k&q-w`Bj0E z;HpoWF#B4I8(zgz&54X6RN&Awm5mm?eGyf-_}6gx3LxuCOwtz2&4CKVhlL!3pC&yk z;DKbp@B~w#l%FP3D{!Dd7!Fk+xMy#G*ad0D*YU$6WC`r%tf|+5!y`_VpZWneg%ahv zRm17colNNCmHk_lxiUR#D6-0l z?KX`cq}tSyViKbJ6K$K|SrOTA5$S4YEZir|O!4z328oPLN0V})w|;6phk*`!xHs-> zwSwVDG$Sc$e59V^lj?H?m1^is-sFg?z$YZoHEqwTz>dvPVLb)?EA2FidS|A3XT1yU zw0)tHUb-x{567~W%yKI+xG!*Lbr2YRm)XZWA%tIVvrWy=bc__TWRY96Jre}|&6 zk9Z_nl4PE#kt^&k+omDB;W^hXkeHIGv5#^jXFH}mbMUX^GQOwbSwc2)0T@}6%?a09V_WyWiiX^-oTg*hqDDP~s;Ca{(!9|pts zv_B;qKhM}k6ZC{Cd>Z=gKSa^<8>J^@Od}f3pE1*UmkY|B57GTpZy(FJ3MA&*-_2Vi z8qI%*WNVOfPkS>_^_js@;X}8JrsTJO2}RHBhi-+c!iTO_37>hEBM+kcv0nV75sP~| zREKefV>*Bj0ff_*X0okFx4lR!sS!ZbR#Y(rvPjW$3%w=#jphV&3-05JzvYR4k*%+@ zkZHowG6A-6DWgpVAWAQ^mQ%@Qm*#Hva{wG5Ziyx&UOV($qi#t~(j36_gzo>qc2YANSB3%V2NCBXw7`EPLqSq%KwE;r{72&j zX}7ty>b7D=GCxvGTQl{N{n|AXTd2h#Dbbv?hVU)$^2*G`KgQDYL4o>5#8V*N7U8h? zQ25D!P%?kU$9&L_LOca0l*7Jd)U1rCG5hFGg z^FZ8p6F!tf{voB_OG+Ye(iF6^*s{-*Paj$!O@^zJ4{WIE3)sfA^sg-|lxZ9y3XO7) zpJ+Vr%cgvIDy7!s2;LKha{a*|I<0qGj4g?zY}K6_*XxSIVq*P@{pZ%?2{D(4P&%<} zxXeQIM!$`hE6x0HH#b$74X<)6@I{TsEy_Kl&E=*(Q-beJVU;193V9ZP4u`yHdBoh&SsAbDFPQGRpPYXJFyVCr?*}PgPgY8!FIS; z1bj8Pu-BGkR}E*@0?hoV?8`O|Hit+xR?vMQ)MiNJmzLYIn6a#hf0$TI zG^;xYMj0~~sN~;e!mviQ%(W34W3#0G1R;+QtHZ%Ppo8rTDbCEi0w2UD>z$WbfcOMgum}0wShx zh)r4sdc?v| znTI3&W*foAHSzA}@9!`&VnR9!3{wqLiDDX*60QNizeKq&nEv&>s=~6(XO}g4xU`sn zJlz+52eZGZc4qjfRR&>hdU;l;4ZBliwW))B-3{)Jdp|CC9Lhzxch&OpzxUX$TnYkl2+7~R#|3MrSGIOR_dj$2S4orA z=$rChkN0ftq-#SwEG!n7+RHo1Grm2iyUX1?f+d%X(LoyfkECgP4;f%X!HE;^{sPM> za)fnblf&qbQ&H(0DnqFWGoTZWkbU`W9J`$8nD=8WN~_j7noTwq7J0y`I6&@Uq&rXc z6{8+pu#n5uP$c4y z-#P?F$fHM92KuyEQJF5PNs3B3`l<|f>FD-Nl?uJVx8t@J{_**w<~*OQ)z-fx=;6W! zjaBaZV^8}NT-l*c*0rxB+clm@*f05=v5Rs@8WoV{hV1Q^S5sk8)-oz|?bX`uTqXUu zzl|HlD!CZ0monT{tB@B2l%s3vFJiT@E~)V)Zbkv&@7>^cC-W26rJo3vLsb@NPR)W~ zmphm$)W7Ye#gACVpVzL~j;Cf)V$jhTNhUw3KR=+y+d4QWD?G&A_kf_#m?y|8fP^T0}Anj_NrcF>_p5~sSWz12J zn(B~r#&Cq)>7-coDbMh@FQakdYudcHFP+@>DFyEn7uwTY!n(j zjqnP)ccgFVgH8gcFC8L&e0guYaPYMKrnvY{baBaYzA*u+50R5$Ovr5OA*aU4IrKWM z@|xN0&@*SfrNN)G?6JUY++7i{%8lmhrD3hOYaZekT%R9Y@#b@8VJ+Bo-fw-5--_*0 zk-a#2cox6$Ntfcw=rDdm?If_%g+@e8H~tI7diuuKAXN~>_R8(j;L7&i{t+H?`+K2xT90hX0B1zY%@@w&(9- voByEUQ~kdVasI~nd*1RN9AmD(tor|EF-o!!kbjL<#7 literal 0 HcmV?d00001 diff --git a/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_UnBoard/Moose_Test_CARGO_UnBoard.lua b/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_UnBoard/Moose_Test_CARGO_UnBoard.lua new file mode 100644 index 000000000..0c61e07d8 --- /dev/null +++ b/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_UnBoard/Moose_Test_CARGO_UnBoard.lua @@ -0,0 +1,14 @@ + +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 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. +-- The Cargo will run from the Carrier to a point in the NearRadius around the Carrier. +InfantryCargo:UnBoard( CargoCarrier, 10 ) \ No newline at end of file From b9a94271b2b660b69dc48298ae211bdac7bda46e Mon Sep 17 00:00:00 2001 From: Sven Van de Velde Date: Sat, 6 Aug 2016 11:33:50 +0200 Subject: [PATCH 07/16] Progress --- Moose Development/Moose/Cargo.lua | 51 ++++++- Moose Development/Moose/Controllable.lua | 56 +++---- Moose Development/Moose/Detection.lua | 14 +- Moose Development/Moose/DetectionManager.lua | 2 +- Moose Development/Moose/Escort.lua | 42 ++--- Moose Development/Moose/Group.lua | 23 +-- Moose Development/Moose/MissileTrainer.lua | 22 +-- Moose Development/Moose/Point.lua | 143 +++++++++++++----- Moose Development/Moose/Positionable.lua | 86 +++++------ Moose Development/Moose/Process_JTAC.lua | 4 +- Moose Development/Moose/Spawn.lua | 4 +- Moose Development/Moose/Unit.lua | 36 ++--- Moose Development/Moose/Zone.lua | 26 ++-- .../Release Notes/2016-07 - ReleaseNotes.txt | 6 + 14 files changed, 310 insertions(+), 205 deletions(-) diff --git a/Moose Development/Moose/Cargo.lua b/Moose Development/Moose/Cargo.lua index 6e5fcbb92..1e6e9d7e0 100644 --- a/Moose Development/Moose/Cargo.lua +++ b/Moose Development/Moose/Cargo.lua @@ -297,6 +297,43 @@ function CARGO_REPRESENTABLE:OnBoarded( FsmP, Event, From, To, CargoCarrier ) end end +--- UnBoard Event. +-- @param #CARGO_REPRESENTABLE self +-- @param StateMachine#STATEMACHINE_PROCESS FsmP +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Unit#UNIT CargoCarrier +function CARGO_REPRESENTABLE:OnUnBoard( FsmP, Event, From, To, Speed, Angle, Distance ) + 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 + + local Points = {} + + local PointStartVec2 = 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 PointEndVec2 = CargoCarrier:GetPointVec2() + + + Points[#Points+1] = PointStartVec2:RoutePointGround( Speed ) + Points[#Points+1] = PointEndVec2:RoutePointGround( Speed ) + + local TaskRoute = self.CargoObject:TaskRoute( Points ) + self.CargoObject:SetTask( TaskRoute, 4 ) + end + + self:_NextEvent( FsmP.Boarded, CargoCarrier ) + +end + --- UnBoarded Event. -- @param #CARGO self -- @param StateMachine#STATEMACHINE_PROCESS FsmP @@ -886,17 +923,17 @@ function CARGO_ZONE:Signal() if SignalUnit then self:T( 'Signalling Unit' ) - local SignalVehiclePos = SignalUnit:GetPointVec3() - SignalVehiclePos.y = SignalVehiclePos.y + 2 + local SignalVehicleVec3 = SignalUnit:GetVec3() + SignalVehicleVec3.y = SignalVehicleVec3.y + 2 if self.SignalType.ID == CARGO_ZONE.SIGNAL.TYPE.SMOKE.ID then - trigger.action.smoke( SignalVehiclePos, self.SignalColor.TRIGGERCOLOR ) + trigger.action.smoke( SignalVehicleVec3, self.SignalColor.TRIGGERCOLOR ) Signalled = true elseif self.SignalType.ID == CARGO_ZONE.SIGNAL.TYPE.FLARE.ID then - trigger.action.signalFlare( SignalVehiclePos, self.SignalColor.TRIGGERCOLOR , 0 ) + trigger.action.signalFlare( SignalVehicleVec3, self.SignalColor.TRIGGERCOLOR , 0 ) Signalled = false end @@ -904,15 +941,15 @@ function CARGO_ZONE:Signal() else - local ZonePointVec3 = self:GetPointVec3( self.SignalHeight ) -- Get the zone position + the landheight + 2 meters + 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( ZonePointVec3, self.SignalColor.TRIGGERCOLOR ) + trigger.action.smoke( ZoneVec3, self.SignalColor.TRIGGERCOLOR ) Signalled = true elseif self.SignalType.ID == CARGO_ZONE.SIGNAL.TYPE.FLARE.ID then - trigger.action.signalFlare( ZonePointVec3, self.SignalColor.TRIGGERCOLOR, 0 ) + trigger.action.signalFlare( ZoneVec3, self.SignalColor.TRIGGERCOLOR, 0 ) Signalled = false end diff --git a/Moose Development/Moose/Controllable.lua b/Moose Development/Moose/Controllable.lua index 3268958bc..289bace82 100644 --- a/Moose Development/Moose/Controllable.lua +++ b/Moose Development/Moose/Controllable.lua @@ -506,15 +506,15 @@ 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 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( PointVec2, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack ) - self:F2( { self.ControllableName, PointVec2, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack } ) +function CONTROLLABLE:TaskBombing( Vec2, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack ) + self:F2( { self.ControllableName, Vec2, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack } ) -- Bombing = { -- id = 'Bombing', @@ -531,7 +531,7 @@ function CONTROLLABLE:TaskBombing( PointVec2, WeaponType, WeaponExpend, AttackQt local DCSTask DCSTask = { id = 'Bombing', params = { - point = PointVec2, + point = Vec2, weaponType = WeaponType, expend = WeaponExpend, attackQty = AttackQty, @@ -624,15 +624,15 @@ 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 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( PointVec2, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack ) - self:F2( { self.ControllableName, PointVec2, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack } ) +function CONTROLLABLE:TaskAttackMapObject( Vec2, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack ) + self:F2( { self.ControllableName, Vec2, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack } ) -- AttackMapObject = { -- id = 'AttackMapObject', @@ -649,7 +649,7 @@ function CONTROLLABLE:TaskAttackMapObject( PointVec2, WeaponType, WeaponExpend, local DCSTask DCSTask = { id = 'AttackMapObject', params = { - point = PointVec2, + point = Vec2, weaponType = WeaponType, expend = WeaponExpend, attackQty = AttackQty, @@ -793,11 +793,11 @@ end -- 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 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, PointVec3, LastWaypointIndex ) - self:F2( { self.ControllableName, FollowControllable, PointVec3, LastWaypointIndex } ) +function CONTROLLABLE:TaskFollow( FollowControllable, Vec3, LastWaypointIndex ) + self:F2( { self.ControllableName, FollowControllable, Vec3, LastWaypointIndex } ) -- Follow = { -- id = 'Follow', @@ -818,7 +818,7 @@ function CONTROLLABLE:TaskFollow( FollowControllable, PointVec3, LastWaypointInd DCSTask = { id = 'Follow', params = { controllableId = FollowControllable:GetID(), - pos = PointVec3, + pos = Vec3, lastWptIndexFlag = LastWaypointIndexFlag, lastWptIndex = LastWaypointIndex, }, @@ -834,13 +834,13 @@ end -- 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 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, PointVec3, LastWaypointIndex, EngagementDistance, TargetTypes ) - self:F2( { self.ControllableName, FollowControllable, PointVec3, LastWaypointIndex, EngagementDistance, TargetTypes } ) +function CONTROLLABLE:TaskEscort( FollowControllable, Vec3, LastWaypointIndex, EngagementDistance, TargetTypes ) + self:F2( { self.ControllableName, FollowControllable, Vec3, LastWaypointIndex, EngagementDistance, TargetTypes } ) -- Escort = { -- id = 'Escort', @@ -863,7 +863,7 @@ function CONTROLLABLE:TaskEscort( FollowControllable, PointVec3, LastWaypointInd DCSTask = { id = 'Follow', params = { controllableId = FollowControllable:GetID(), - pos = PointVec3, + pos = Vec3, lastWptIndexFlag = LastWaypointIndexFlag, lastWptIndex = LastWaypointIndex, engagementDistMax = EngagementDistance, @@ -880,11 +880,11 @@ end --- (GROUND) Fire at a VEC2 point until ammunition is finished. -- @param #CONTROLLABLE self --- @param DCSTypes#Vec2 PointVec2 The point to fire at. +-- @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( PointVec2, Radius ) - self:F2( { self.ControllableName, PointVec2, Radius } ) +function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius ) + self:F2( { self.ControllableName, Vec2, Radius } ) -- FireAtPoint = { -- id = 'FireAtPoint', @@ -897,7 +897,7 @@ function CONTROLLABLE:TaskFireAtPoint( PointVec2, Radius ) local DCSTask DCSTask = { id = 'FireAtPoint', params = { - point = PointVec2, + point = Vec2, radius = Radius, } } @@ -1004,13 +1004,13 @@ 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#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( PointVec2, Radius, TargetTypes, Priority ) - self:F2( { self.ControllableName, PointVec2, Radius, TargetTypes, Priority } ) +function CONTROLLABLE:EnRouteTaskEngageTargets( Vec2, Radius, TargetTypes, Priority ) + self:F2( { self.ControllableName, Vec2, Radius, TargetTypes, Priority } ) -- EngageTargetsInZone = { -- id = 'EngageTargetsInZone', @@ -1025,7 +1025,7 @@ function CONTROLLABLE:EnRouteTaskEngageTargets( PointVec2, Radius, TargetTypes, local DCSTask DCSTask = { id = 'EngageTargetsInZone', params = { - point = PointVec2, + point = Vec2, zoneRadius = Radius, targetTypes = TargetTypes, priority = Priority @@ -1428,12 +1428,12 @@ end function CONTROLLABLE:TaskRouteToVec3( Point, Speed ) self:F2( { Point, Speed } ) - local ControllablePoint = self:GetUnit( 1 ):GetPointVec3() + local ControllableVec3 = self:GetUnit( 1 ):GetVec3() local PointFrom = {} - PointFrom.x = ControllablePoint.x - PointFrom.y = ControllablePoint.z - PointFrom.alt = ControllablePoint.y + PointFrom.x = ControllableVec3.x + PointFrom.y = ControllableVec3.z + PointFrom.alt = ControllableVec3.y PointFrom.alt_type = "BARO" PointFrom.type = "Turning Point" PointFrom.action = "Turning Point" diff --git a/Moose Development/Moose/Detection.lua b/Moose Development/Moose/Detection.lua index 2e98f9ce0..a62b8e664 100644 --- a/Moose Development/Moose/Detection.lua +++ b/Moose Development/Moose/Detection.lua @@ -347,11 +347,11 @@ function DETECTION_BASE:_DetectionScheduler( SchedulerName ) local DetectionDetectedObjectName = DetectionObject:getName() local DetectionDetectedObjectPositionVec3 = DetectionObject:getPoint() - local DetectionGroupPositionVec3 = DetectionGroup:GetPointVec3() + local DetectionGroupVec3 = DetectionGroup:GetVec3() - local Distance = ( ( DetectionDetectedObjectPositionVec3.x - DetectionGroupPositionVec3.x )^2 + - ( DetectionDetectedObjectPositionVec3.y - DetectionGroupPositionVec3.y )^2 + - ( DetectionDetectedObjectPositionVec3.z - DetectionGroupPositionVec3.z )^2 + local Distance = ( ( DetectionDetectedObjectPositionVec3.x - DetectionGroupVec3.x )^2 + + ( DetectionDetectedObjectPositionVec3.y - DetectionGroupVec3.y )^2 + + ( DetectionDetectedObjectPositionVec3.z - DetectionGroupVec3.z )^2 ) ^ 0.5 / 1000 self:T2( { DetectionGroupName, DetectionDetectedObjectName, Distance } ) @@ -531,7 +531,7 @@ function DETECTION_AREAS:ReportFriendliesNearBy( ReportGroupData ) local SphereSearch = { id = world.VolumeType.SPHERE, params = { - point = DetectedZoneUnit:GetPointVec3(), + point = DetectedZoneUnit:GetVec3(), radius = 6000, } @@ -613,9 +613,9 @@ function DETECTION_AREAS:NearestFAC( DetectedArea ) for FACUnit, FACUnitData in pairs( FACGroupData:GetUnits() ) do local FACUnit = FACUnitData -- Unit#UNIT if FACUnit:IsActive() then - local Vec3 = FACUnit:GetPointVec3() + local Vec3 = FACUnit:GetVec3() local PointVec3 = POINT_VEC3:NewFromVec3( Vec3 ) - local Distance = PointVec3:Get2DDistance(POINT_VEC3:NewFromVec3( FACUnit:GetPointVec3() ) ) + local Distance = PointVec3:Get2DDistance(POINT_VEC3:NewFromVec3( FACUnit:GetVec3() ) ) if Distance < MinDistance then MinDistance = Distance NearestFAC = FACUnit diff --git a/Moose Development/Moose/DetectionManager.lua b/Moose Development/Moose/DetectionManager.lua index 29e4138b6..17aad91d8 100644 --- a/Moose Development/Moose/DetectionManager.lua +++ b/Moose Development/Moose/DetectionManager.lua @@ -460,7 +460,7 @@ do -- DETECTION_DISPATCHER local ThreatLevel = Detection:GetTreatLevelA2G( DetectedArea ) - local DetectedAreaVec3 = DetectedZone:GetPointVec3() + local DetectedAreaVec3 = DetectedZone:GetVec3() 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)", diff --git a/Moose Development/Moose/Escort.lua b/Moose Development/Moose/Escort.lua index 4143efd82..430456d7e 100644 --- a/Moose Development/Moose/Escort.lua +++ b/Moose Development/Moose/Escort.lua @@ -665,13 +665,13 @@ function ESCORT._HoldPosition( MenuParam ) self.FollowScheduler:Stop() local PointFrom = {} - local GroupPoint = EscortGroup:GetUnit(1):GetPointVec3() + local GroupVec3 = EscortGroup:GetUnit(1):GetVec3() PointFrom = {} - PointFrom.x = GroupPoint.x - PointFrom.y = GroupPoint.z + PointFrom.x = GroupVec3.x + PointFrom.y = GroupVec3.z PointFrom.speed = 250 PointFrom.type = AI.Task.WaypointType.TURNING_POINT - PointFrom.alt = GroupPoint.y + PointFrom.alt = GroupVec3.y PointFrom.alt_type = AI.Task.AltitudeType.BARO local OrbitPoint = OrbitUnit:GetVec2() @@ -997,16 +997,16 @@ function ESCORT:_FollowScheduler() self:T( {ClientUnit.UnitName, GroupUnit.UnitName } ) if self.CT1 == 0 and self.GT1 == 0 then - self.CV1 = ClientUnit:GetPointVec3() + self.CV1 = ClientUnit:GetVec3() self:T( { "self.CV1", self.CV1 } ) self.CT1 = timer.getTime() - self.GV1 = GroupUnit:GetPointVec3() + self.GV1 = GroupUnit:GetVec3() self.GT1 = timer.getTime() else local CT1 = self.CT1 local CT2 = timer.getTime() local CV1 = self.CV1 - local CV2 = ClientUnit:GetPointVec3() + local CV2 = ClientUnit:GetVec3() self.CT1 = CT2 self.CV1 = CV2 @@ -1020,7 +1020,7 @@ function ESCORT:_FollowScheduler() local GT1 = self.GT1 local GT2 = timer.getTime() local GV1 = self.GV1 - local GV2 = GroupUnit:GetPointVec3() + local GV2 = GroupUnit:GetVec3() self.GT1 = GT2 self.GV1 = GV2 @@ -1132,11 +1132,11 @@ function ESCORT:_ReportTargetsScheduler() -- 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 + local EscortTargetUnitVec3 = EscortTargetUnit:GetVec3() + local EscortVec3 = self.EscortGroup:GetVec3() + local Distance = ( ( EscortTargetUnitVec3.x - EscortVec3.x )^2 + + ( EscortTargetUnitVec3.y - EscortVec3.y )^2 + + ( EscortTargetUnitVec3.z - EscortVec3.z )^2 ) ^ 0.5 / 1000 self:T( { self.EscortGroup:GetName(), EscortTargetUnit:GetName(), Distance, EscortTarget } ) @@ -1191,11 +1191,11 @@ function ESCORT:_ReportTargetsScheduler() 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 + local EscortTargetUnitVec3 = ClientEscortTargetData.AttackUnit:GetVec3() + local EscortVec3 = self.EscortGroup:GetVec3() + local Distance = ( ( EscortTargetUnitVec3.x - EscortVec3.x )^2 + + ( EscortTargetUnitVec3.y - EscortVec3.y )^2 + + ( EscortTargetUnitVec3.z - EscortVec3.z )^2 ) ^ 0.5 / 1000 self:T( { self.EscortGroup:GetName(), ClientEscortTargetData.AttackUnit:GetName(), Distance, ClientEscortTargetData.AttackUnit } ) @@ -1259,9 +1259,9 @@ function ESCORT:_ReportTargetsScheduler() 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 + local EscortVec3 = self.EscortGroup:GetVec3() + local Distance = ( ( WayPoint.x - EscortVec3.x )^2 + + ( WayPoint.y - EscortVec3.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 diff --git a/Moose Development/Moose/Group.lua b/Moose Development/Moose/Group.lua index e1dc301dd..9e3a82bbb 100644 --- a/Moose Development/Moose/Group.lua +++ b/Moose Development/Moose/Group.lua @@ -485,14 +485,14 @@ function GROUP:GetVec2() 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() +--- 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 GroupPointVec3 = self:GetUnit(1):GetPointVec3() - self:T3( GroupPointVec3 ) - return GroupPointVec3 + local GroupVec3 = self:GetUnit(1):GetVec3() + self:T3( GroupVec3 ) + return GroupVec3 end @@ -508,7 +508,8 @@ function GROUP:IsCompletelyInZone( Zone ) for UnitID, UnitData in pairs( self:GetUnits() ) do local Unit = UnitData -- Unit#UNIT - if Zone:IsPointVec3InZone( Unit:GetPointVec3() ) then + -- TODO: Rename IsPointVec3InZone to IsVec3InZone + if Zone:IsPointVec3InZone( Unit:GetVec3() ) then else return false end @@ -526,7 +527,7 @@ function GROUP:IsPartlyInZone( Zone ) for UnitID, UnitData in pairs( self:GetUnits() ) do local Unit = UnitData -- Unit#UNIT - if Zone:IsPointVec3InZone( Unit:GetPointVec3() ) then + if Zone:IsPointVec3InZone( Unit:GetVec3() ) then return true end end @@ -543,7 +544,7 @@ function GROUP:IsNotInZone( Zone ) for UnitID, UnitData in pairs( self:GetUnits() ) do local Unit = UnitData -- Unit#UNIT - if Zone:IsPointVec3InZone( Unit:GetPointVec3() ) then + if Zone:IsPointVec3InZone( Unit:GetVec3() ) then return false end end @@ -731,7 +732,7 @@ end -- @param #table Template The template of the Group retrieved with GROUP:GetTemplate() function GROUP:Respawn( Template ) - local Vec3 = self:GetPointVec3() + local Vec3 = self:GetVec3() Template.x = Vec3.x Template.y = Vec3.z --Template.x = nil @@ -742,7 +743,7 @@ function GROUP:Respawn( Template ) local GroupUnit = UnitData -- Unit#UNIT self:E( GroupUnit:GetName() ) if GroupUnit:IsAlive() then - local GroupUnitVec3 = GroupUnit:GetPointVec3() + local GroupUnitVec3 = GroupUnit:GetVec3() local GroupUnitHeading = GroupUnit:GetHeading() Template.units[UnitID].alt = GroupUnitVec3.y Template.units[UnitID].x = GroupUnitVec3.x diff --git a/Moose Development/Moose/MissileTrainer.lua b/Moose Development/Moose/MissileTrainer.lua index 8b55684e9..35098fe81 100644 --- a/Moose Development/Moose/MissileTrainer.lua +++ b/Moose Development/Moose/MissileTrainer.lua @@ -512,11 +512,11 @@ function MISSILETRAINER:_AddRange( Client, TrainerWeapon ) if self.DetailsRangeOnOff then local PositionMissile = TrainerWeapon:getPoint() - local PositionTarget = Client:GetPointVec3() + local TargetVec3 = Client:GetVec3() - local Range = ( ( PositionMissile.x - PositionTarget.x )^2 + - ( PositionMissile.y - PositionTarget.y )^2 + - ( PositionMissile.z - PositionTarget.z )^2 + local Range = ( ( PositionMissile.x - TargetVec3.x )^2 + + ( PositionMissile.y - TargetVec3.y )^2 + + ( PositionMissile.z - TargetVec3.z )^2 ) ^ 0.5 / 1000 RangeText = string.format( ", at %4.2fkm", Range ) @@ -532,11 +532,11 @@ function MISSILETRAINER:_AddBearing( Client, TrainerWeapon ) if self.DetailsBearingOnOff then local PositionMissile = TrainerWeapon:getPoint() - local PositionTarget = Client:GetPointVec3() + local TargetVec3 = Client:GetVec3() - self:T2( { PositionTarget, PositionMissile }) + self:T2( { TargetVec3, PositionMissile }) - local DirectionVector = { x = PositionMissile.x - PositionTarget.x, y = PositionMissile.y - PositionTarget.y, z = PositionMissile.z - PositionTarget.z } + local DirectionVector = { x = PositionMissile.x - TargetVec3.x, y = PositionMissile.y - TargetVec3.y, z = PositionMissile.z - TargetVec3.z } local DirectionRadians = math.atan2( DirectionVector.z, DirectionVector.x ) --DirectionRadians = DirectionRadians + routines.getNorthCorrection( PositionTarget ) if DirectionRadians < 0 then @@ -580,11 +580,11 @@ function MISSILETRAINER:_TrackMissiles() 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 TargetVec3 = Client:GetVec3() - local Distance = ( ( PositionMissile.x - PositionTarget.x )^2 + - ( PositionMissile.y - PositionTarget.y )^2 + - ( PositionMissile.z - PositionTarget.z )^2 + local Distance = ( ( PositionMissile.x - TargetVec3.x )^2 + + ( PositionMissile.y - TargetVec3.y )^2 + + ( PositionMissile.z - TargetVec3.z )^2 ) ^ 0.5 / 1000 if Distance <= self.Distance then diff --git a/Moose Development/Moose/Point.lua b/Moose Development/Moose/Point.lua index 2c1adb920..05e647667 100644 --- a/Moose Development/Moose/Point.lua +++ b/Moose Development/Moose/Point.lua @@ -31,7 +31,9 @@ --- The POINT_VEC3 class -- @type POINT_VEC3 -- @extends Base#BASE --- @field DCSTypes#Vec3 PointVec3 +-- @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 #POINT_VEC3.RoutePointAltType RoutePointAltType @@ -113,8 +115,9 @@ POINT_VEC3 = { 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 ) + self.x = x + self.y = y + self.z = z return self end @@ -132,14 +135,14 @@ end -- @param #POINT_VEC3 self -- @return DCSTypes#Vec3 The Vec3 coodinate. function POINT_VEC3:GetVec3() - return self.PointVec3 + 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. function POINT_VEC3:GetVec2() - return { x = self.PointVec3.x, y = self.PointVec3.z } + return { x = self.x, y = self.z } end @@ -147,24 +150,39 @@ end -- @param #POINT_VEC3 self -- @return #number The x coodinate. function POINT_VEC3:GetX() - self:F2(self.PointVec3.x) - return self.PointVec3.x + return self.x end --- Return the y coordinate of the POINT_VEC3. -- @param #POINT_VEC3 self -- @return #number The y coodinate. function POINT_VEC3:GetY() - self:F2(self.PointVec3.y) - return self.PointVec3.y + return self.y end --- Return the z coordinate of the POINT_VEC3. -- @param #POINT_VEC3 self -- @return #number The z coodinate. function POINT_VEC3:GetZ() - self:F2(self.PointVec3.z) - return self.PointVec3.z + return self.z +end + +--- Set the x coordinate of the POINT_VEC3. +-- @param #number x The x coordinate. +function POINT_VEC3:SetX( x ) + self.x = x +end + +--- Set the y coordinate of the POINT_VEC3. +-- @param #number y The y coordinate. +function POINT_VEC3:SetY( y ) + self.y = y +end + +--- Set the z coordinate of the POINT_VEC3. +-- @param #number z The z coordinate. +function POINT_VEC3:SetZ( z ) + self.z = z end --- Return a random Vec3 point within an Outer Radius and optionally NOT within an Inner Radius of the POINT_VEC3. @@ -173,7 +191,7 @@ end -- @param DCSTypes#Distance InnerRadius -- @return DCSTypes#Vec2 Vec2 function POINT_VEC3:GetRandomVec2InRadius( OuterRadius, InnerRadius ) - self:F2( { self.PointVec3, OuterRadius, InnerRadius } ) + self:F2( { OuterRadius, InnerRadius } ) local Theta = 2 * math.pi * math.random() local Radials = math.random() + math.random() @@ -215,7 +233,7 @@ end --- Return a direction vector Vec3 from POINT_VEC3 to the POINT_VEC3. -- @param #POINT_VEC3 self --- @param #POINT_VEC3 TargetPointVec3 The target PointVec3. +-- @param #POINT_VEC3 TargetPointVec3 The target POINT_VEC3. -- @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() } @@ -247,7 +265,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 PointVec3. +-- @param #POINT_VEC3 TargetPointVec3 The target POINT_VEC3. -- @return DCSTypes#Distance Distance The distance in meters. function POINT_VEC3:Get2DDistance( TargetPointVec3 ) local TargetVec3 = TargetPointVec3:GetVec3() @@ -257,7 +275,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 PointVec3. +-- @param #POINT_VEC3 TargetPointVec3 The target POINT_VEC3. -- @return DCSTypes#Distance Distance The distance in meters. function POINT_VEC3:Get3DDistance( TargetPointVec3 ) local TargetVec3 = TargetPointVec3:GetVec3() @@ -265,6 +283,22 @@ function POINT_VEC3:Get3DDistance( TargetPointVec3 ) return ( ( TargetVec3.x - SourceVec3.x ) ^ 2 + ( TargetVec3.y - SourceVec3.y ) ^ 2 + ( TargetVec3.z - SourceVec3.z ) ^ 2 ) ^ 0.5 end +--- Add a Distance in meters from the POINT_VEC3 orthogonal plane, with the given angle, and calculate the new POINT_VEC3. +-- @param #POINT_VEC3 self +-- @param DCSTypes#Distance Distance The Distance to be added in meters. +-- @param 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 SY = self:GetY() + local Radians = Angle / 180 * math.pi + local TX = Distance * math.cos( Radians ) + SX + local TY = Distance * math.sin( Radians ) + SY + + 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 @@ -294,7 +328,7 @@ end function POINT_VEC3:ToStringLL( acc, DMS ) acc = acc or 3 - local lat, lon = coord.LOtoLL( self.PointVec3 ) + local lat, lon = coord.LOtoLL( self:GetVec3() ) return UTILS.tostringLL(lat, lon, acc, DMS) end @@ -311,7 +345,7 @@ end --- Return a BR string from a POINT_VEC3 to the POINT_VEC3. -- @param #POINT_VEC3 self --- @param #POINT_VEC3 TargetPointVec3 The target PointVec3. +-- @param #POINT_VEC3 TargetPointVec3 The target POINT_VEC3. -- @return #string The BR text. function POINT_VEC3:GetBRText( TargetPointVec3 ) local DirectionVec3 = self:GetDirectionVec3( TargetPointVec3 ) @@ -349,9 +383,9 @@ 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.x = self:GetX() + RoutePoint.y = self:GetZ() + RoutePoint.alt = self:GetY() RoutePoint.alt_type = AltType RoutePoint.type = Type @@ -390,8 +424,8 @@ function POINT_VEC3:RoutePointGround( Speed, Formation ) self:F2( { Formation, Speed } ) local RoutePoint = {} - RoutePoint.x = self.PointVec3.x - RoutePoint.y = self.PointVec3.z + RoutePoint.x = self:GetX() + RoutePoint.y = self:GetZ() RoutePoint.action = Formation or "" @@ -425,8 +459,8 @@ end -- @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 ) + self:F2( { SmokeColor } ) + trigger.action.smoke( self:GetVec3(), SmokeColor ) end --- Smoke the POINT_VEC3 Green. @@ -469,8 +503,8 @@ end -- @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 ) + self:F2( { FlareColor } ) + trigger.action.signalFlare( self:GetVec3(), FlareColor, Azimuth and Azimuth or 0 ) end --- Flare the POINT_VEC3 White. @@ -507,8 +541,9 @@ end --- The POINT_VEC2 class -- @type POINT_VEC2 --- @field DCSTypes#Vec2 PointVec2 -- @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", } @@ -527,10 +562,7 @@ function POINT_VEC2:New( x, y, 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 @@ -550,9 +582,6 @@ function POINT_VEC2:NewFromVec2( Vec2, LandHeightAdd ) local self = BASE:Inherit( self, POINT_VEC3:New( Vec2.x, LandHeight, Vec2.y ) ) self:F2( { Vec2.x, Vec2.y, LandHeightAdd } ) - self.PointVec2 = Vec2 - self:F2( self.PointVec3 ) - return self end @@ -570,20 +599,52 @@ function POINT_VEC2:NewFromVec3( Vec3 ) local self = BASE:Inherit( self, POINT_VEC3:New( Vec2.x, LandHeight, Vec2.y ) ) self:F2( { Vec2.x, LandHeight, Vec2.y } ) - self.PointVec2 = Vec2 - self:F2( self.PointVec3 ) - return self end ---- Calculate the distance from a reference @{Point#POINT_VEC2}. +--- Return the x coordinate of the 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. +-- @return #number The x coodinate. +function POINT_VEC2:GetX() + return self.x +end + +--- Return the y coordinate of the POINT_VEC2. +-- @param #POINT_VEC2 self +-- @return #number The y coodinate. +function POINT_VEC2:GetY() + return self.z +end + +--- Return the altitude of the land at the POINT_VEC2. +-- @param #POINT_VEC2 self +-- @return #number The land altitude. +function POINT_VEC2:GetAlt() + return land.getHeight( { x = self.x, y = self.z } ) +end + +--- Set the x coordinate of the POINT_VEC2. +-- @param #number x The x coordinate. +function POINT_VEC2:SetX( x ) + elf.x = x +end + +--- Set the y coordinate of the POINT_VEC2. +-- @param #number y The y coordinate. +function POINT_VEC2:SetY( y ) + self.z = y +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. 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 + local Distance = ( ( PointVec2Reference:GetX() - self:GetX() ) ^ 2 + ( PointVec2Reference:GetY() - self:GetY() ) ^2 ) ^0.5 self:T2( Distance ) return Distance @@ -596,7 +657,7 @@ end function POINT_VEC2:DistanceFromVec2( Vec2Reference ) self:F2( Vec2Reference ) - local Distance = ( ( Vec2Reference.x - self.PointVec2.x ) ^ 2 + ( Vec2Reference.y - self.PointVec2.y ) ^2 ) ^0.5 + local Distance = ( ( Vec2Reference.x - self:GetX() ) ^ 2 + ( Vec2Reference.y - self:GetY() ) ^2 ) ^0.5 self:T2( Distance ) return Distance diff --git a/Moose Development/Moose/Positionable.lua b/Moose Development/Moose/Positionable.lua index d53ea0c9d..97d0776c8 100644 --- a/Moose Development/Moose/Positionable.lua +++ b/Moose Development/Moose/Positionable.lua @@ -2,11 +2,11 @@ -- -- 1) @{Positionable#POSITIONABLE} class, extends @{Identifiable#IDENTIFIABLE} -- =========================================================== --- The @{Positionable#POSITIONABLE} class is a wrapper class to handle the DCS Positionable objects: +-- The @{Positionable#POSITIONABLE} class is a wrapper class to handle the 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. +-- * 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: -- ------------------------------ @@ -41,7 +41,7 @@ POSITIONABLE = { --- Create a new POSITIONABLE from a DCSPositionable -- @param #POSITIONABLE self --- @param DCSPositionable#Positionable PositionableName The DCS Positionable name +-- @param DCSPositionable#Positionable PositionableName The POSITIONABLE name -- @return #POSITIONABLE self function POSITIONABLE:New( PositionableName ) local self = BASE:Inherit( self, IDENTIFIABLE:New( PositionableName ) ) @@ -49,10 +49,10 @@ function POSITIONABLE: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. +--- 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 DCS Positionable. --- @return #nil The DCS Positionable is not existing or alive. +-- @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 ) @@ -67,10 +67,10 @@ function POSITIONABLE:GetPositionVec3() return nil end ---- Returns the @{DCSTypes#Vec2} vector indicating the point in 2D of the DCS Positionable within the mission. +--- 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 DCS Positionable. --- @return #nil The DCS Positionable is not existing or alive. +-- @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 ) @@ -93,7 +93,7 @@ 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 DCS Positionable is not existing or alive. +-- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetPointVec2() self:F2( self.PositionableName ) @@ -112,52 +112,52 @@ function POSITIONABLE:GetPointVec2() end ---- Returns a random @{DCSTypes#Vec3} vector within a range, indicating the point in 3D of the DCS Positionable within the mission. +--- 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 DCS Positionable. --- @return #nil The DCS Positionable is not existing or alive. -function POSITIONABLE:GetRandomPointVec3( Radius ) +-- @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 PositionableRandomPointVec3 = {} + local PositionableRandomVec3 = {} 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; + 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( PositionableRandomPointVec3 ) - return PositionableRandomPointVec3 + self:T3( PositionableRandomVec3 ) + return PositionableRandomVec3 end return nil end ---- Returns the @{DCSTypes#Vec3} vector indicating the point in 3D of the DCS Positionable within the mission. +--- 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 DCS Positionable. --- @return #nil The DCS Positionable is not existing or alive. -function POSITIONABLE:GetPointVec3() +-- @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 PositionablePointVec3 = DCSPositionable:getPosition().p - self:T3( PositionablePointVec3 ) - return PositionablePointVec3 + local PositionableVec3 = DCSPositionable:getPosition().p + self:T3( PositionableVec3 ) + return PositionableVec3 end return nil end ---- Returns the altitude of the DCS Positionable. +--- Returns the altitude of the POSITIONABLE. -- @param Positionable#POSITIONABLE self --- @return DCSTypes#Distance The altitude of the DCS Positionable. --- @return #nil The DCS Positionable is not existing or alive. +-- @return DCSTypes#Distance The altitude of the POSITIONABLE. +-- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetAltitude() self:F2() @@ -174,7 +174,7 @@ 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. +-- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:IsAboveRunway() self:F2( self.PositionableName ) @@ -182,8 +182,8 @@ function POSITIONABLE:IsAboveRunway() if DCSPositionable then - local PointVec2 = self:GetVec2() - local SurfaceType = land.getSurfaceType( PointVec2 ) + local Vec2 = self:GetVec2() + local SurfaceType = land.getSurfaceType( Vec2 ) local IsAboveRunway = SurfaceType == land.SurfaceType.RUNWAY self:T2( IsAboveRunway ) @@ -195,9 +195,9 @@ end ---- Returns the DCS Positionable heading. +--- Returns the POSITIONABLE heading in degrees. -- @param Positionable#POSITIONABLE self --- @return #number The DCS Positionable heading +-- @return #number The POSTIONABLE heading function POSITIONABLE:GetHeading() local DCSPositionable = self:GetDCSObject() @@ -218,10 +218,10 @@ function POSITIONABLE:GetHeading() end ---- Returns true if the DCS Positionable is in the air. +--- Returns true if the 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. +-- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:InAir() self:F2( self.PositionableName ) @@ -236,10 +236,10 @@ function POSITIONABLE:InAir() return nil end ---- Returns the DCS Positionable velocity vector. +--- Returns the POSITIONABLE velocity vector. -- @param Positionable#POSITIONABLE self -- @return DCSTypes#Vec3 The velocity vector --- @return #nil The DCS Positionable is not existing or alive. +-- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetVelocity() self:F2( self.PositionableName ) @@ -254,10 +254,10 @@ function POSITIONABLE:GetVelocity() return nil end ---- Returns the @{Unit#UNIT} velocity in km/h. +--- Returns the POSITIONABLE 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. +-- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetVelocityKMH() self:F2( self.PositionableName ) diff --git a/Moose Development/Moose/Process_JTAC.lua b/Moose Development/Moose/Process_JTAC.lua index 7e78f1ad5..ac5ae5e69 100644 --- a/Moose Development/Moose/Process_JTAC.lua +++ b/Moose Development/Moose/Process_JTAC.lua @@ -162,9 +162,9 @@ function PROCESS_JTAC:OnJTACMenuSpot( Fsm, Event, From, To, TargetUnit ) TaskJTAC.Spots[TargetUnitName] = TaskJTAC.Spots[TargetUnitName] or {} local DCSFACObject = self.FACUnit:GetDCSObject() - local TargetVec3 = TargetUnit:GetPointVec3() + local TargetVec3 = TargetUnit:GetVec3() - TaskJTAC.Spots[TargetUnitName] = Spot.createInfraRed( self.FACUnit:GetDCSObject(), { x = 0, y = 1, z = 0 }, TargetUnit:GetPointVec3(), math.random( 1000, 9999 ) ) + 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 ) diff --git a/Moose Development/Moose/Spawn.lua b/Moose Development/Moose/Spawn.lua index 581cf92ae..8b64db423 100644 --- a/Moose Development/Moose/Spawn.lua +++ b/Moose Development/Moose/Spawn.lua @@ -651,7 +651,7 @@ 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 - return self:SpawnFromVec3( HostUnit:GetPointVec3(), OuterRadius, InnerRadius, SpawnIndex ) + return self:SpawnFromVec3( HostUnit:GetVec3(), OuterRadius, InnerRadius, SpawnIndex ) end return nil @@ -670,7 +670,7 @@ function SPAWN:SpawnFromStatic( HostStatic, OuterRadius, InnerRadius, SpawnIndex self:F( { self.SpawnTemplatePrefix, HostStatic, OuterRadius, InnerRadius, SpawnIndex } ) if HostStatic and HostStatic:IsAlive() then - return self:SpawnFromVec3( HostStatic:GetPointVec3(), OuterRadius, InnerRadius, SpawnIndex ) + return self:SpawnFromVec3( HostStatic:GetVec3(), OuterRadius, InnerRadius, SpawnIndex ) end return nil diff --git a/Moose Development/Moose/Unit.lua b/Moose Development/Moose/Unit.lua index 05cce20a2..0d117a2b5 100644 --- a/Moose Development/Moose/Unit.lua +++ b/Moose Development/Moose/Unit.lua @@ -48,7 +48,7 @@ -- 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. +-- 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 @@ -505,7 +505,7 @@ function UNIT:IsInZone( Zone ) self:F2( { self.UnitName, Zone } ) if self:IsAlive() then - local IsInZone = Zone:IsPointVec3InZone( self:GetPointVec3() ) + local IsInZone = Zone:IsPointVec3InZone( self:GetVec3() ) self:T( { IsInZone } ) return IsInZone @@ -522,7 +522,7 @@ function UNIT:IsNotInZone( Zone ) self:F2( { self.UnitName, Zone } ) if self:IsAlive() then - local IsInZone = not Zone:IsPointVec3InZone( self:GetPointVec3() ) + local IsInZone = not Zone:IsPointVec3InZone( self:GetVec3() ) self:T( { IsInZone } ) return IsInZone @@ -544,10 +544,10 @@ function UNIT:OtherUnitInRadius( AwaitUnit, Radius ) local DCSUnit = self:GetDCSObject() if DCSUnit then - local UnitPos = self:GetPointVec3() - local AwaitUnitPos = AwaitUnit:GetPointVec3() + local UnitVec3 = self:GetVec3() + local AwaitUnitVec3 = AwaitUnit:GetVec3() - if (((UnitPos.x - AwaitUnitPos.x)^2 + (UnitPos.z - AwaitUnitPos.z)^2)^0.5 <= Radius) then + if (((UnitVec3.x - AwaitUnitVec3.x)^2 + (UnitVec3.z - AwaitUnitVec3.z)^2)^0.5 <= Radius) then self:T3( "true" ) return true else @@ -565,35 +565,35 @@ end -- @param #UNIT self function UNIT:Flare( FlareColor ) self:F2() - trigger.action.signalFlare( self:GetPointVec3(), FlareColor , 0 ) + 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:GetPointVec3(), trigger.flareColor.White , 0 ) + 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:GetPointVec3(), trigger.flareColor.Yellow , 0 ) + 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:GetPointVec3(), trigger.flareColor.Green , 0 ) + 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:GetPointVec3() + local Vec3 = self:GetVec3() if Vec3 then trigger.action.signalFlare( Vec3, trigger.flareColor.Red, 0 ) end @@ -604,9 +604,9 @@ end function UNIT:Smoke( SmokeColor, Range ) self:F2() if Range then - trigger.action.smoke( self:GetRandomPointVec3( Range ), SmokeColor ) + trigger.action.smoke( self:GetRandomVec3( Range ), SmokeColor ) else - trigger.action.smoke( self:GetPointVec3(), SmokeColor ) + trigger.action.smoke( self:GetVec3(), SmokeColor ) end end @@ -615,35 +615,35 @@ end -- @param #UNIT self function UNIT:SmokeGreen() self:F2() - trigger.action.smoke( self:GetPointVec3(), trigger.smokeColor.Green ) + 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:GetPointVec3(), trigger.smokeColor.Red ) + 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:GetPointVec3(), trigger.smokeColor.White ) + 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:GetPointVec3(), trigger.smokeColor.Orange ) + 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:GetPointVec3(), trigger.smokeColor.Blue ) + trigger.action.smoke( self:GetVec3(), trigger.smokeColor.Blue ) end -- Is methods diff --git a/Moose Development/Moose/Zone.lua b/Moose Development/Moose/Zone.lua index 30b118fcd..651185eba 100644 --- a/Moose Development/Moose/Zone.lua +++ b/Moose Development/Moose/Zone.lua @@ -111,12 +111,12 @@ end --- Returns if a point is within the zone. -- @param #ZONE_BASE self --- @param DCSTypes#Vec3 PointVec3 The point to test. +-- @param DCSTypes#Vec3 Vec3 The point to test. -- @return #boolean true if the point is within the zone. -function ZONE_BASE:IsPointVec3InZone( PointVec3 ) - self:F2( PointVec3 ) +function ZONE_BASE:IsPointVec3InZone( Vec3 ) + self:F2( Vec3 ) - local InZone = self:IsPointVec2InZone( { x = PointVec3.x, y = PointVec3.z } ) + local InZone = self:IsPointVec2InZone( { x = Vec3.x, y = Vec3.z } ) return InZone end @@ -282,11 +282,11 @@ function ZONE_RADIUS:SetPointVec2( Vec2 ) return self.Vec2 end ---- Returns the point of the zone. +--- Returns the @{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. -function ZONE_RADIUS:GetPointVec3( Height ) +function ZONE_RADIUS:GetVec3( Height ) self:F2( { self.ZoneName, Height } ) Height = Height or 0 @@ -320,7 +320,7 @@ end --- Returns if a point is within the zone. -- @param #ZONE_RADIUS self --- @param DCSTypes#Vec3 PointVec3 The point to test. +-- @param 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 ) @@ -445,22 +445,22 @@ function ZONE_UNIT:GetRandomVec2() return Point end ---- Returns the point of the zone. --- @param #ZONE_RADIUS self +--- Returns the @{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. -function ZONE_UNIT:GetPointVec3( Height ) +function ZONE_UNIT:GetVec3( 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 } + local Vec3 = { x = Vec2.x, y = land.getHeight( self:GetVec2() ) + Height, z = Vec2.y } - self:T2( { PointVec3 } ) + self:T2( { Vec3 } ) - return PointVec3 + return Vec3 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. diff --git a/Moose Development/Release Notes/2016-07 - ReleaseNotes.txt b/Moose Development/Release Notes/2016-07 - ReleaseNotes.txt index 65b9d8a4b..381b2087c 100644 --- a/Moose Development/Release Notes/2016-07 - ReleaseNotes.txt +++ b/Moose Development/Release Notes/2016-07 - ReleaseNotes.txt @@ -1,3 +1,9 @@ +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). From bd62df4d284967ba0f8db7168e3ecf536e6167b9 Mon Sep 17 00:00:00 2001 From: Sven Van de Velde Date: Sat, 6 Aug 2016 12:44:23 +0200 Subject: [PATCH 08/16] Progress --- Moose Development/Moose/Cargo.lua | 33 +++++++++++++----- Moose Development/Moose/Database.lua | 8 +++++ Moose Development/Moose/Unit.lua | 50 ++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 8 deletions(-) diff --git a/Moose Development/Moose/Cargo.lua b/Moose Development/Moose/Cargo.lua index 1e6e9d7e0..1e77bd5dd 100644 --- a/Moose Development/Moose/Cargo.lua +++ b/Moose Development/Moose/Cargo.lua @@ -317,20 +317,19 @@ function CARGO_REPRESENTABLE:OnUnBoard( FsmP, Event, From, To, Speed, Angle, Dis local Points = {} - local PointStartVec2 = self.CargoCarrier:GetPointVec2() + 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 PointEndVec2 = CargoCarrier:GetPointVec2() + local CargoDeployPointVec2 = StartPointVec2:Translate( Distance, CargoDeployHeading ) - - Points[#Points+1] = PointStartVec2:RoutePointGround( Speed ) - Points[#Points+1] = PointEndVec2:RoutePointGround( Speed ) + Points[#Points+1] = StartPointVec2:RoutePointGround( Speed ) + Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) local TaskRoute = self.CargoObject:TaskRoute( Points ) self.CargoObject:SetTask( TaskRoute, 4 ) end - self:_NextEvent( FsmP.Boarded, CargoCarrier ) + self:_NextEvent( FsmP.UnBoarded ) end @@ -345,9 +344,8 @@ function CARGO_REPRESENTABLE:OnUnBoarded( FsmP, Event, From, To ) self:F() if self.CargoObject:GetVelocityKMH() <= 0.1 then - self:_NextEvent( FsmP.UnLoad ) else - self:_NextEvent( FsmP.Boarded, CargoCarrier ) + self:_NextEvent( FsmP.UnBoarded ) end end @@ -365,6 +363,25 @@ function CARGO_REPRESENTABLE:OnLoad( FsmP, Event, From, To, CargoCarrier ) self.CargoObject:Destroy() end +--- UnLoad Event. +-- @param #CARGO self +-- @param StateMachine#STATEMACHINE_PROCESS FsmP +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param Unit#UNIT CargoCarrier +function CARGO_REPRESENTABLE:OnUnLoad( FsmP, Event, From, To, 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 ) + + -- Respawn the group... + self.CargoObject:ReSpawn( CargoDeployPointVec2:GetVec3(), CargoDeployHeading ) +end + end do -- CARGO_UNIT diff --git a/Moose Development/Moose/Database.lua b/Moose Development/Moose/Database.lua index 21563032f..14de9a4c7 100644 --- a/Moose Development/Moose/Database.lua +++ b/Moose Development/Moose/Database.lua @@ -387,6 +387,14 @@ function DATABASE:GetGroupTemplate( GroupName ) return GroupTemplate end +function DATABASE:GetGroupNameFromUnitName( UnitName ) + return self.Templates.Units[UnitName].GroupName +end + +function DATABASE:GetGroupTemplateFromUnitName( UnitName ) + return self.Templates.Units[UnitName].GroupTemplate +end + function DATABASE:GetCoalitionFromClientTemplate( ClientName ) return self.Templates.ClientsByName[ClientName].CoalitionID end diff --git a/Moose Development/Moose/Unit.lua b/Moose Development/Moose/Unit.lua index 0d117a2b5..5c91f9b0e 100644 --- a/Moose Development/Moose/Unit.lua +++ b/Moose Development/Moose/Unit.lua @@ -166,6 +166,56 @@ function UNIT:GetDCSObject() 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 = _DATABASE:GetGroupTemplateFromUnitName( self:GetName() ) + 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 + if UnitTemplateData.name == self:GetName() then + 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 From d2efc61ddcaa5d29b4ae5e7f717a598be1ffbfe8 Mon Sep 17 00:00:00 2001 From: Sven Van de Velde Date: Sat, 6 Aug 2016 19:27:17 +0200 Subject: [PATCH 09/16] Progress --- Moose Development/Moose/Cargo.lua | 40 ++++++++++-------- .../MOOSE_Test_CARGO_UnBoard.miz | Bin 16755 -> 17602 bytes .../Moose_Test_CARGO_UnBoard.lua | 3 +- 3 files changed, 25 insertions(+), 18 deletions(-) diff --git a/Moose Development/Moose/Cargo.lua b/Moose Development/Moose/Cargo.lua index 1e77bd5dd..439d15f73 100644 --- a/Moose Development/Moose/Cargo.lua +++ b/Moose Development/Moose/Cargo.lua @@ -224,9 +224,10 @@ function CARGO_REPRESENTABLE:New( Mission, CargoObject, Type, Name, Weight, Repo { 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 = 'UnBoard', from = 'Loaded', to = 'UnLoading' }, + { name = 'UnLoad', from = 'UnLoading', to = 'UnBoarding' }, + { name = 'UnBoard', from = 'UnBoarding', to = 'UnBoarding' }, + { name = 'UnBoarded', from = 'UnBoarding', to = 'UnLoaded' }, { name = 'UnLoad', from = 'Loaded', to = 'UnLoaded' }, }, callbacks = { @@ -304,7 +305,7 @@ end -- @param #string From -- @param #string To -- @param Unit#UNIT CargoCarrier -function CARGO_REPRESENTABLE:OnUnBoard( FsmP, Event, From, To, Speed, Angle, Distance ) +function CARGO_REPRESENTABLE:OnUnBoard( FsmP, Event, From, To, Speed, Distance, Angle ) self:F() self.CargoInAir = self.CargoObject:InAir() @@ -314,22 +315,27 @@ function CARGO_REPRESENTABLE:OnUnBoard( FsmP, Event, From, To, Speed, Angle, Dis -- 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 + + if self.FsmP:is( "Loaded" ) then + self:_NextEvent( FsmP.UnLoad, Distance, Angle ) + else + local Points = {} + + 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 ) + + Points[#Points+1] = StartPointVec2:RoutePointGround( Speed ) + Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) + + local TaskRoute = self.CargoObject:TaskRoute( Points ) + self.CargoObject:SetTask( TaskRoute, 4 ) - local Points = {} - - 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 ) - - Points[#Points+1] = StartPointVec2:RoutePointGround( Speed ) - Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) - - local TaskRoute = self.CargoObject:TaskRoute( Points ) - self.CargoObject:SetTask( TaskRoute, 4 ) + self:_NextEvent( FsmP.UnBoarded ) + end end - self:_NextEvent( FsmP.UnBoarded ) end diff --git a/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_UnBoard/MOOSE_Test_CARGO_UnBoard.miz b/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_UnBoard/MOOSE_Test_CARGO_UnBoard.miz index de9c5fc2f8dc4b27851ec75f3244217d784a7c54..3fb0237910958ef6c8f453d6f69d90e66f0c4aa9 100644 GIT binary patch delta 15909 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 P*ss^Ym;R{GU(){vECA&o delta 15012 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 diff --git a/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_UnBoard/Moose_Test_CARGO_UnBoard.lua b/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_UnBoard/Moose_Test_CARGO_UnBoard.lua index 0c61e07d8..e7df96ee7 100644 --- a/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_UnBoard/Moose_Test_CARGO_UnBoard.lua +++ b/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_UnBoard/Moose_Test_CARGO_UnBoard.lua @@ -11,4 +11,5 @@ InfantryCargo:Load( CargoCarrier ) -- This will Unboard the Cargo from the Carrier. -- The Cargo will run from the Carrier to a point in the NearRadius around the Carrier. -InfantryCargo:UnBoard( CargoCarrier, 10 ) \ No newline at end of file +-- Unboard the Cargo with a speed of 10 km/h, go to 200 meters 180 degrees from the Carrier, iin a zone of 25 meters (NearRadius). +InfantryCargo:UnBoard( CargoCarrier, 10, 200, 180 ) \ No newline at end of file From 7ec2d3425c3b65d1dbac007385910ef3d0e42963 Mon Sep 17 00:00:00 2001 From: Sven Van de Velde Date: Sat, 6 Aug 2016 21:22:19 +0200 Subject: [PATCH 10/16] Progress --- Moose Development/Moose/Cargo.lua | 41 ++++--------- Moose Development/Moose/Database.lua | 57 ++++++++++-------- Moose Development/Moose/Point.lua | 41 ++++++++++--- Moose Development/Moose/Positionable.lua | 1 + Moose Development/Moose/Spawn.lua | 12 ++-- Moose Development/Moose/Unit.lua | 15 ++++- .../MOOSE_Test_CARGO_UnBoard.miz | Bin 17602 -> 17597 bytes .../Moose_Test_CARGO_UnBoard.lua | 2 +- 8 files changed, 96 insertions(+), 73 deletions(-) diff --git a/Moose Development/Moose/Cargo.lua b/Moose Development/Moose/Cargo.lua index 439d15f73..edcfa94b1 100644 --- a/Moose Development/Moose/Cargo.lua +++ b/Moose Development/Moose/Cargo.lua @@ -110,10 +110,10 @@ end -- @param #CARGO self -- @param Unit#UNIT CargoCarrier -- @param #number Speed -function CARGO:UnBoard( Speed ) +function CARGO:UnBoard( Speed, Distance, Angle ) self:F() - self:_NextEvent( self.FsmP.UnBoard, Speed ) + self:_NextEvent( self.FsmP.UnBoard, Speed, Distance, Angle ) end --- Load Cargo to a Carrier. @@ -181,13 +181,12 @@ function CARGO:OnUnLoaded( FsmP, Event, From, To ) self:F() self:T( "Cargo " .. self.Name .. " unloaded from " .. self.CargoCarrier:GetName() ) - self.CargoCarrier = nil end --- @param #CARGO self function CARGO:_NextEvent( NextEvent, ... ) self:F( self.Name ) - self.CargoScheduler:Schedule( self.FsmP, NextEvent, arg, 1 ) -- This schedules the next event, but only if scheduling is activated. + SCHEDULER:New( self.FsmP, NextEvent, arg, 1 ) -- This schedules the next event, but only if scheduling is activated. end end @@ -224,10 +223,9 @@ function CARGO_REPRESENTABLE:New( Mission, CargoObject, Type, Name, Weight, Repo { name = 'Boarded', from = 'Boarding', to = 'Boarding' }, { name = 'Load', from = 'Boarding', to = 'Loaded' }, { name = 'Load', from = 'UnLoaded', to = 'Loaded' }, - { name = 'UnBoard', from = 'Loaded', to = 'UnLoading' }, - { name = 'UnLoad', from = 'UnLoading', to = 'UnBoarding' }, - { name = 'UnBoard', from = 'UnBoarding', to = 'UnBoarding' }, - { name = 'UnBoarded', from = 'UnBoarding', to = 'UnLoaded' }, + { 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 = { @@ -316,45 +314,30 @@ function CARGO_REPRESENTABLE:OnUnBoard( FsmP, Event, From, To, Speed, Distance, -- (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 - if self.FsmP:is( "Loaded" ) then - self:_NextEvent( FsmP.UnLoad, Distance, Angle ) + if self.FsmP:is( "UnLoading" ) then + self:_NextEvent( FsmP.UnLoad, 3, Angle ) + self:_NextEvent( FsmP.UnBoard, Speed, Distance, Angle ) else local Points = {} 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 ) + self:T( { CargoCarrierHeading, CargoDeployHeading } ) local CargoDeployPointVec2 = StartPointVec2:Translate( Distance, CargoDeployHeading ) + local CargoDeployPointVec2 = CargoDeployPointVec2:GetRandomPointVec2InRadius( self.NearRadius ) Points[#Points+1] = StartPointVec2:RoutePointGround( Speed ) Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) local TaskRoute = self.CargoObject:TaskRoute( Points ) - self.CargoObject:SetTask( TaskRoute, 4 ) - - self:_NextEvent( FsmP.UnBoarded ) + self.CargoObject:SetTask( TaskRoute, 1 ) end end end ---- UnBoarded Event. --- @param #CARGO self --- @param StateMachine#STATEMACHINE_PROCESS FsmP --- @param #string Event --- @param #string From --- @param #string To --- @param Unit#UNIT CargoCarrier -function CARGO_REPRESENTABLE:OnUnBoarded( FsmP, Event, From, To ) - self:F() - - if self.CargoObject:GetVelocityKMH() <= 0.1 then - else - self:_NextEvent( FsmP.UnBoarded ) - end -end - --- Load Event. -- @param #CARGO self -- @param StateMachine#STATEMACHINE_PROCESS FsmP diff --git a/Moose Development/Moose/Database.lua b/Moose Development/Moose/Database.lua index 14de9a4c7..04e8d52b1 100644 --- a/Moose Development/Moose/Database.lua +++ b/Moose Development/Moose/Database.lua @@ -264,19 +264,19 @@ end -- @param #table SpawnTemplate -- @return #DATABASE self function DATABASE:Spawn( SpawnTemplate ) - self:F2( SpawnTemplate.name ) + self:F( SpawnTemplate.name ) - self:T2( { SpawnTemplate.SpawnCountryID, SpawnTemplate.SpawnCategoryID } ) + self:T( { 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 + local SpawnCoalitionID = SpawnTemplate.CoalitionID + local SpawnCountryID = SpawnTemplate.CountryID + local SpawnCategoryID = SpawnTemplate.CategoryID -- Nullify - SpawnTemplate.SpawnCoalitionID = nil - SpawnTemplate.SpawnCountryID = nil - SpawnTemplate.SpawnCategoryID = nil + SpawnTemplate.CoalitionID = nil + SpawnTemplate.CountryID = nil + SpawnTemplate.CategoryID = nil self:_RegisterTemplate( SpawnTemplate, SpawnCoalitionID, SpawnCategoryID, SpawnCountryID ) @@ -284,9 +284,9 @@ function DATABASE:Spawn( SpawnTemplate ) coalition.addGroup( SpawnCountryID, SpawnCategoryID, SpawnTemplate ) -- Restore - SpawnTemplate.SpawnCoalitionID = SpawnCoalitionID - SpawnTemplate.SpawnCountryID = SpawnCountryID - SpawnTemplate.SpawnCategoryID = SpawnCategoryID + SpawnTemplate.CoalitionID = SpawnCoalitionID + SpawnTemplate.CountryID = SpawnCountryID + SpawnTemplate.CategoryID = SpawnCategoryID local SpawnGroup = self:AddGroup( SpawnTemplate.name ) return SpawnGroup @@ -330,6 +330,10 @@ function DATABASE:_RegisterTemplate( GroupTemplate, CoalitionID, CategoryID, Cou GroupTemplate.route.spans = nil end + GroupTemplate.CategoryID = CategoryID + GroupTemplate.CoalitionID = CoalitionID + GroupTemplate.CountryID = CountryID + self.Templates.Groups[GroupTemplateName].GroupName = GroupTemplateName self.Templates.Groups[GroupTemplateName].Template = GroupTemplate self.Templates.Groups[GroupTemplateName].groupId = GroupTemplate.groupId @@ -354,26 +358,27 @@ function DATABASE:_RegisterTemplate( GroupTemplate, CoalitionID, CategoryID, Cou 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 + UnitTemplate.name = env.getValueDictByKey(UnitTemplate.name) + + self.Templates.Units[UnitTemplate.name] = {} + self.Templates.Units[UnitTemplate.name].UnitName = UnitTemplate.name + self.Templates.Units[UnitTemplate.name].Template = UnitTemplate + self.Templates.Units[UnitTemplate.name].GroupName = GroupTemplateName + self.Templates.Units[UnitTemplate.name].GroupTemplate = GroupTemplate + self.Templates.Units[UnitTemplate.name].GroupId = GroupTemplate.groupId + self.Templates.Units[UnitTemplate.name].CategoryID = CategoryID + self.Templates.Units[UnitTemplate.name].CoalitionID = CoalitionID + self.Templates.Units[UnitTemplate.name].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.ClientsByName[UnitTemplate.name] = UnitTemplate + self.Templates.ClientsByName[UnitTemplate.name].CategoryID = CategoryID + self.Templates.ClientsByName[UnitTemplate.name].CoalitionID = CoalitionID + self.Templates.ClientsByName[UnitTemplate.name].CountryID = CountryID self.Templates.ClientsByID[UnitTemplate.unitId] = UnitTemplate end - TraceTable[#TraceTable+1] = self.Templates.Units[UnitTemplateName].UnitName + TraceTable[#TraceTable+1] = self.Templates.Units[UnitTemplate.name].UnitName end self:E( TraceTable ) diff --git a/Moose Development/Moose/Point.lua b/Moose Development/Moose/Point.lua index 05e647667..d2223ad14 100644 --- a/Moose Development/Moose/Point.lua +++ b/Moose Development/Moose/Point.lua @@ -185,7 +185,7 @@ function POINT_VEC3:SetZ( z ) self.z = z end ---- Return a random Vec3 point within an Outer Radius and optionally NOT within an Inner Radius of the POINT_VEC3. +--- 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 @@ -206,17 +206,28 @@ function POINT_VEC3:GetRandomVec2InRadius( OuterRadius, InnerRadius ) RadialMultiplier = OuterRadius * Radials end - local RandomVec3 + local RandomVec2 if OuterRadius > 0 then - RandomVec3 = { x = math.cos( Theta ) * RadialMultiplier + self:GetX(), y = math.sin( Theta ) * RadialMultiplier + self:GetZ() } + RandomVec2 = { x = math.cos( Theta ) * RadialMultiplier + self:GetX(), y = math.sin( Theta ) * RadialMultiplier + self:GetZ() } else - RandomVec3 = { x = self:GetX(), y = self:GetZ() } + RandomVec2 = { x = self:GetX(), y = self:GetZ() } end - return RandomVec3 + return RandomVec2 end ---- Return a random Vec3 point within an Outer Radius and optionally NOT within an Inner Radius of the POINT_VEC3. +--- 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 +-- @return #POINT_VEC2 +function POINT_VEC3:GetRandomPointVec2InRadius( OuterRadius, InnerRadius ) + self:F2( { OuterRadius, InnerRadius } ) + + return POINT_VEC2:NewFromVec2( self:GetRandomPointVec2InRadius( OuterRadius, InnerRadius ) ) +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 @@ -230,6 +241,16 @@ function POINT_VEC3:GetRandomVec3InRadius( OuterRadius, InnerRadius ) return RandomVec3 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 +-- @return #POINT_VEC3 +function POINT_VEC3:GetRandomPointVec3InRadius( OuterRadius, InnerRadius ) + + return POINT_VEC3:NewFromVec3( self:GetRandomVec3InRadius( OuterRadius, InnerRadius ) ) +end + --- Return a direction vector Vec3 from POINT_VEC3 to the POINT_VEC3. -- @param #POINT_VEC3 self @@ -295,8 +316,10 @@ function POINT_VEC3:Translate( Distance, Angle ) local TX = Distance * math.cos( Radians ) + SX local TY = Distance * math.sin( Radians ) + SY - local SourceVec3 = self:GetVec3() - return ( ( TargetVec3.x - SourceVec3.x ) ^ 2 + ( TargetVec3.y - SourceVec3.y ) ^ 2 + ( TargetVec3.z - SourceVec3.z ) ^ 2 ) ^ 0.5 + self:SetX( TX ) + self:SetY( TY ) + + return self end --- Provides a Bearing / Range string @@ -626,7 +649,7 @@ end --- Set the x coordinate of the POINT_VEC2. -- @param #number x The x coordinate. function POINT_VEC2:SetX( x ) - elf.x = x + self.x = x end --- Set the y coordinate of the POINT_VEC2. diff --git a/Moose Development/Moose/Positionable.lua b/Moose Development/Moose/Positionable.lua index 97d0776c8..87c0629ac 100644 --- a/Moose Development/Moose/Positionable.lua +++ b/Moose Development/Moose/Positionable.lua @@ -209,6 +209,7 @@ function POSITIONABLE:GetHeading() if PositionableHeading < 0 then PositionableHeading = PositionableHeading + 2 * math.pi end + PositionableHeading = PositionableHeading * 180 / math.pi self:T2( PositionableHeading ) return PositionableHeading end diff --git a/Moose Development/Moose/Spawn.lua b/Moose Development/Moose/Spawn.lua index 8b64db423..721a658e1 100644 --- a/Moose Development/Moose/Spawn.lua +++ b/Moose Development/Moose/Spawn.lua @@ -1003,9 +1003,9 @@ function SPAWN:_GetTemplate( SpawnTemplatePrefix ) error( 'No Template returned for SpawnTemplatePrefix = ' .. SpawnTemplatePrefix ) end - SpawnTemplate.SpawnCoalitionID = self:_GetGroupCoalitionID( SpawnTemplatePrefix ) - SpawnTemplate.SpawnCategoryID = self:_GetGroupCategoryID( SpawnTemplatePrefix ) - SpawnTemplate.SpawnCountryID = self:_GetGroupCountryID( SpawnTemplatePrefix ) + --SpawnTemplate.SpawnCoalitionID = self:_GetGroupCoalitionID( SpawnTemplatePrefix ) + --SpawnTemplate.SpawnCategoryID = self:_GetGroupCategoryID( SpawnTemplatePrefix ) + --SpawnTemplate.SpawnCountryID = self:_GetGroupCountryID( SpawnTemplatePrefix ) self:T3( { SpawnTemplate } ) return SpawnTemplate @@ -1026,12 +1026,12 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) --SpawnTemplate.lateActivation = false SpawnTemplate.lateActivation = false - if SpawnTemplate.SpawnCategoryID == Group.Category.GROUND then + if SpawnTemplate.CategoryID == 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 + if SpawnTemplate.CategoryID == Group.Category.HELICOPTER or SpawnTemplate.CategoryID == Group.Category.AIRPLANE then SpawnTemplate.uncontrolled = false end @@ -1064,7 +1064,7 @@ function SPAWN:_RandomizeRoute( SpawnIndex ) SpawnTemplate.route.points[t].y = SpawnTemplate.route.points[t].y + math.random( self.SpawnRandomizeRouteRadius * -1, self.SpawnRandomizeRouteRadius ) -- Manage randomization of altitude for airborne units ... - if SpawnTemplate.SpawnCategoryID == Group.Category.AIRPLANE or SpawnTemplate.SpawnCategoryID == Group.Category.HELICOPTER then + if SpawnTemplate.CategoryID == Group.Category.AIRPLANE or SpawnTemplate.CategoryID == Group.Category.HELICOPTER then if SpawnTemplate.route.points[t].alt and self.SpawnRandomizeRouteHeight then SpawnTemplate.route.points[t].alt = SpawnTemplate.route.points[t].alt + math.random( 1, self.SpawnRandomizeRouteHeight ) end diff --git a/Moose Development/Moose/Unit.lua b/Moose Development/Moose/Unit.lua index 5c91f9b0e..a4aaf6d29 100644 --- a/Moose Development/Moose/Unit.lua +++ b/Moose Development/Moose/Unit.lua @@ -152,6 +152,14 @@ function UNIT:FindByName( 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 @@ -179,7 +187,8 @@ end -- @param #number Heading The heading of the unit respawn. function UNIT:ReSpawn( SpawnVec3, Heading ) - local SpawnGroupTemplate = _DATABASE:GetGroupTemplateFromUnitName( self:GetName() ) + local SpawnGroupTemplate = UTILS.DeepCopy( _DATABASE:GetGroupTemplateFromUnitName( self:Name() ) ) + self:T( SpawnGroupTemplate ) local SpawnGroup = self:GetGroup() if SpawnGroup then @@ -205,7 +214,9 @@ function UNIT:ReSpawn( SpawnVec3, Heading ) end for UnitTemplateID, UnitTemplateData in pairs( SpawnGroupTemplate.units ) do - if UnitTemplateData.name == self:GetName() then + 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 diff --git a/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_UnBoard/MOOSE_Test_CARGO_UnBoard.miz b/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_UnBoard/MOOSE_Test_CARGO_UnBoard.miz index 3fb0237910958ef6c8f453d6f69d90e66f0c4aa9..cde3f57847f1e4367260eac0af5aa1740c950b34 100644 GIT binary patch delta 3336 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 SL^J^%lb=K#2E#!B0001RFm}QK delta 3409 zcmV-X4X*ONi2=fi0kD}W4(Gn|VrT&X0QUj_04V^IPeUtzb&*?Z!axv(--Y~#Ax{cx z8Vdyxe5ls8ff|dK=dyJt$zrlAyGh0V`c5ulLg>rpa^{@#W#SA{edH;urL|Hkn8P~Q z?Sf4>3-K}p_fsBE#Tk~|#M))xzRNf%uY0sPMB3B})MF$ZHBV(uNuHGgz3u7+M$W)# zTB_ldilst-G}LtngZ=DPDzUhRJVWZciEe4DO@WHI%FVTIn#1yW`?mS2tQBK}&bsNW zmDRe3-4Ckr#dJdFqwo84KJ^&sF!CS7MssV^cnteQTDZtGg%8aI$I`;U zV9bq3(UKuSgY9AKIfKr(&dRu97pUKPP~Vy|TS<{_(OJ9JYaBPdvYA5l27z!noHcP1 zd!>ZP&6W#USl~t%O7w&K02i4j6lBkc0(1luA5Pgr!hkfYpshoOIT{O-sSg5UgJ^p+ z3?xBt_|*!_6#7k#Yj~#`^_F%}ixQ{1H%1KA?`Boy=nqg!0|b+RLmIPIEP?@OEoV@rTwinBxDkKPr{L%*nXwds)Zgj!LAK+><2Z3`CGK7B zMgx(Mg^D6{An8Qz+;=YskP-=kBuG0tx3!*ZV6pgNcd=M30Isp`W8xWqH(vewlRz#q zFO7T>QX3s_FVOb&Z!Pve`(rlYoM*uLt@Y;3-&#jmo~F!m$L}YA-~8f03V5D}<7g5=G97kfd5J6V>!hW^6ox*YHOac$^Cf zg8orlen&~@IVa2)P~xiRH}!}@(1L8{eDZwFgAJO}NLkel%qinr| zeuvbV9UfA?=fVjgEKoHiBwLSXK(J2fnW9Fh!Z*7<2lM2(rkMdME-=0X8j3TWuXrnUcY&amm7Z z=;>PbP1rX3Q%&NJZELL7;@NE*jJu~}owR=mqtFaRf$?lD(?ph%J~b6Ctn%f!seM5-5ROz9PQ zaPFeng50Th8$NU*vE71;y`4G z%)U&2;2_UWYKO%sERs!xicotR(m8W3o~@X(Qpbh(9UQsXm_szDiuIRfnO28OLZ@O= znw65-&B-Y(hFs+w`U0PoZ-_S(*_*)2$zQgD7^TWx?hG*T(Mwhk+w*=cOt$nMD%{XW zd^41B>owfStzt2v$PRxI8AGc(9*#RbbJQPyf}WYy>jQ}o06olPLAhbN7JT*_0vBdM^*8OWu5UOJFU2b`@Xl@6m) zd4i2Nsmx%U%FFJD&`sxV_QgQ8mqKy$!5Z(V95d&LXa^CH4jy4 zwsJz}dbV=v=cBW7%HYH6$>3r==hQFsoKpuMVK3JuAEKFKGAv&W_PRUERKEWpf!H@;(T;=d$RH^Th%xA1^C)sq;>0gSWC==8=RXn&r8 zGMkJX$1KP>!p!ZOLaS@quGq?d|L+Yvq=0j0+iJR)TkQM3G|>-EhkF>snuF)o(yV$P8)>mK~G%_r!8rv!I){w3$K@Ak3hD|{At1+EXp6#uIG*mzuHIkEt z4&Ng}?=%c+qyRN;0!j;i6J0}ZTZw6`z$CI1wI!vYy>p*J$OS8(ogGT;+tbuF;vAlOsU{bkt(9ExEC~>+(#wQdV8AZx}^sC{i96V^`;WbvB zX-HQ{!|+uu9yIdtia1#Y_2r@(kjlM-M*h9fJ=}}iwT<>;{Lpn%+_bzoXQB4M`V0gP zehwQCLOQcAZWL|)Zjg)YAU-F^zsONHWP=V$1Xd5cVCiq5YGeO6ww!gffSWOHz`TA3 zypv@AX#rq@xj_@pHbm7X~W!h>pdII-<@-(g`vr&#u2W~e(bdoq5 zHqp(@CHAV;;+AExzk}EB^i#ir&0;Ylkt7u(`@C2f*958}{&FVeY2%__6aN{#yoHLWR_# zJehp0YVSL0drkRaiJ0UX?x3OOnrlp@spx^khO4UR%14%iADtV|0=zDK*P3s$ ziqMSa%)IQoVn(7GpWAkBwZQJ zJxPsk>c1)=sQ=x@CJ!L2AyB3r61xJRFK@D+$Mc6s8V7& Date: Mon, 8 Aug 2016 11:16:50 +0200 Subject: [PATCH 11/16] Progress --- Moose Development/Moose/Cargo.lua | 677 ++++++++++-------- Moose Development/Moose/Point.lua | 2 +- Moose Development/Moose/StateMachine.lua | 7 + .../MOOSE_Test_CARGO_PACKAGE_Board.miz | Bin 0 -> 17588 bytes .../Moose_Test_CARGO_PACKAGE_Board.lua | 13 + .../MOOSE_Test_CARGO_PACKAGE_UnBoard.miz} | Bin 17597 -> 17592 bytes .../Moose_Test_CARGO_PACKAGE_UnBoard.lua} | 2 +- .../MOOSE_Test_CARGO_UNIT_Board.miz} | Bin .../Moose_Test_CARGO_UNIT_Board.lua} | 0 .../MOOSE_Test_CARGO_UNIT_UnBoard.miz | Bin 0 -> 17592 bytes .../Moose_Test_CARGO_UNIT_UnBoard.lua | 15 + 11 files changed, 418 insertions(+), 298 deletions(-) create mode 100644 Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_PACKAGE_Board/MOOSE_Test_CARGO_PACKAGE_Board.miz create mode 100644 Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_PACKAGE_Board/Moose_Test_CARGO_PACKAGE_Board.lua rename Moose Test Missions/Moose_Test_CARGO/{Moose_Test_CARGO_UnBoard/MOOSE_Test_CARGO_UnBoard.miz => Moose_Test_CARGO_PACKAGE_UnBoard/MOOSE_Test_CARGO_PACKAGE_UnBoard.miz} (73%) rename Moose Test Missions/Moose_Test_CARGO/{Moose_Test_CARGO_UnBoard/Moose_Test_CARGO_UnBoard.lua => Moose_Test_CARGO_PACKAGE_UnBoard/Moose_Test_CARGO_PACKAGE_UnBoard.lua} (94%) rename Moose Test Missions/Moose_Test_CARGO/{Moose_Test_CARGO_Board/MOOSE_Test_CARGO_Board.miz => Moose_Test_CARGO_UNIT_Board/MOOSE_Test_CARGO_UNIT_Board.miz} (100%) rename Moose Test Missions/Moose_Test_CARGO/{Moose_Test_CARGO_Board/Moose_Test_CARGO_Board.lua => Moose_Test_CARGO_UNIT_Board/Moose_Test_CARGO_UNIT_Board.lua} (100%) create mode 100644 Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_UNIT_UnBoard/MOOSE_Test_CARGO_UNIT_UnBoard.miz create mode 100644 Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_UNIT_UnBoard/Moose_Test_CARGO_UNIT_UnBoard.lua diff --git a/Moose Development/Moose/Cargo.lua b/Moose Development/Moose/Cargo.lua index edcfa94b1..cb0b4ab66 100644 --- a/Moose Development/Moose/Cargo.lua +++ b/Moose Development/Moose/Cargo.lua @@ -72,7 +72,7 @@ function CARGO:New( Mission, Type, Name, Weight, ReportRadius, NearRadius ) self.Weight = Weight self.ReportRadius = ReportRadius self.NearRadius = NearRadius - self.CargoObjects = nil + self.CargoObject = nil self.CargoCarrier = nil self.Representable = false self.Slingloadable = false @@ -96,46 +96,49 @@ function CARGO:Spawn( PointVec2 ) end ---- Board Cargo to a Carrier with a defined Speed. --- @param #CARGO self --- @param Unit#UNIT CargoCarrier --- @param #number Speed -function CARGO:Board( CargoCarrier, Speed ) - self:F() - - self:_NextEvent( self.FsmP.Board, CargoCarrier, Speed ) -end - ---- UnBoard Cargo from a Carrier with a defined Speed. --- @param #CARGO self --- @param Unit#UNIT CargoCarrier --- @param #number Speed -function CARGO:UnBoard( Speed, Distance, Angle ) - self:F() - - self:_NextEvent( self.FsmP.UnBoard, Speed, Distance, Angle ) -end - --- Load Cargo to a Carrier. -- @param #CARGO self -- @param Unit#UNIT CargoCarrier --- @param #number Speed function CARGO:Load( CargoCarrier ) self:F() self:_NextEvent( self.FsmP.Load, CargoCarrier ) end ---- UnLoad Cargo from a Carrier. +--- 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 -- @param #number Speed -function CARGO:UnLoad() +-- @param #number BoardDistance +-- @param #number Angle +function CARGO:Board( CargoCarrier, Speed, BoardDistance, LoadDistance, Angle ) self:F() - self:_NextEvent( self.FsmP.UnLoad ) + self:_NextEvent( self.FsmP.Board, CargoCarrier, Speed, BoardDistance, LoadDistance, Angle ) end +--- UnBoard Cargo from a Carrier with a defined Speed. +-- @param #CARGO self +-- @param #number Speed +-- @param #number UnLoadDistance +-- @param #number UnBoardDistance +-- @param #number UnBoardRadius +-- @param #number Angle +function CARGO:UnBoard( Speed, UnLoadDistance, UnBoardDistance, UnBoardRadius, Angle ) + self:F() + + self:_NextEvent( self.FsmP.UnBoard, Speed, UnLoadDistance, UnBoardDistance, UnBoardRadius, Angle ) +end --- Check if CargoCarrier is near the Cargo to be Loaded. -- @param #CARGO self @@ -167,8 +170,7 @@ end function CARGO:OnLoaded( FsmP, Event, From, To, CargoCarrier ) self:F() - self.CargoCarrier = CargoCarrier - self:T( "Cargo " .. self.Name .. " loaded in " .. self.CargoCarrier:GetName() ) + self:T( "Cargo " .. self.Name .. " loaded in " .. CargoCarrier:GetName() ) end --- UnLoaded State. @@ -189,6 +191,12 @@ function CARGO:_NextEvent( NextEvent, ... ) 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 @@ -213,20 +221,52 @@ function CARGO_REPRESENTABLE:New( Mission, CargoObject, Type, Name, Weight, Repo local self = BASE:Inherit( self, CARGO:New( Mission, Type, Name, Weight, ReportRadius, NearRadius ) ) -- #CARGO self:F( { Type, Name, Weight, ReportRadius, NearRadius } ) - self:T( CargoObject ) - self.CargoObject = CargoObject - self.FsmP = STATEMACHINE_PROCESS:New( self, { + + + 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 = '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' }, + { 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, @@ -239,19 +279,20 @@ function CARGO_REPRESENTABLE:New( Mission, CargoObject, Type, Name, Weight, Repo onUnLoaded = self.OnUnLoaded, }, } ) - + + self:T( self.ClassName ) return self end --- Board Event. --- @param #CARGO_REPRESENTABLE self +-- @param #CARGO_UNIT self -- @param StateMachine#STATEMACHINE_PROCESS FsmP -- @param #string Event -- @param #string From -- @param #string To -- @param Unit#UNIT CargoCarrier -function CARGO_REPRESENTABLE:OnBoard( FsmP, Event, From, To, CargoCarrier, Speed ) +function CARGO_UNIT:OnBoard( FsmP, Event, From, To, CargoCarrier, Speed ) self:F() self.CargoInAir = self.CargoObject:InAir() @@ -280,13 +321,13 @@ function CARGO_REPRESENTABLE:OnBoard( FsmP, Event, From, To, CargoCarrier, Speed end --- Boarded Event. --- @param #CARGO self +-- @param #CARGO_UNIT self -- @param StateMachine#STATEMACHINE_PROCESS FsmP -- @param #string Event -- @param #string From -- @param #string To -- @param Unit#UNIT CargoCarrier -function CARGO_REPRESENTABLE:OnBoarded( FsmP, Event, From, To, CargoCarrier ) +function CARGO_UNIT:OnBoarded( FsmP, Event, From, To, CargoCarrier ) self:F() if self:IsNear( CargoCarrier ) then @@ -297,13 +338,17 @@ function CARGO_REPRESENTABLE:OnBoarded( FsmP, Event, From, To, CargoCarrier ) end --- UnBoard Event. --- @param #CARGO_REPRESENTABLE self +-- @param #CARGO_UNIT self -- @param StateMachine#STATEMACHINE_PROCESS FsmP -- @param #string Event -- @param #string From -- @param #string To --- @param Unit#UNIT CargoCarrier -function CARGO_REPRESENTABLE:OnUnBoard( FsmP, Event, From, To, Speed, Distance, Angle ) +-- @param #number Speed +-- @param #number UnLoadDistance +-- @param #number UnBoardDistance +-- @param #number Radius +-- @param #number Angle +function CARGO_UNIT:OnUnBoard( FsmP, Event, From, To, Speed, UnLoadDistance, UnBoardDistance, Radius, Angle ) self:F() self.CargoInAir = self.CargoObject:InAir() @@ -313,293 +358,333 @@ function CARGO_REPRESENTABLE:OnUnBoard( FsmP, Event, From, To, Speed, Distance, -- 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 - - if self.FsmP:is( "UnLoading" ) then - self:_NextEvent( FsmP.UnLoad, 3, Angle ) - self:_NextEvent( FsmP.UnBoard, Speed, Distance, Angle ) - else - local Points = {} - - 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 ) - self:T( { CargoCarrierHeading, CargoDeployHeading } ) - local CargoDeployPointVec2 = StartPointVec2:Translate( Distance, CargoDeployHeading ) - local CargoDeployPointVec2 = CargoDeployPointVec2:GetRandomPointVec2InRadius( self.NearRadius ) - - Points[#Points+1] = StartPointVec2:RoutePointGround( Speed ) - Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) - - local TaskRoute = self.CargoObject:TaskRoute( Points ) - self.CargoObject:SetTask( TaskRoute, 1 ) - end + end + self:_NextEvent( FsmP.UnBoarded, Speed, UnLoadDistance, UnBoardDistance, Radius, Angle ) end ---- Load Event. --- @param #CARGO self +--- UnBoarded 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_REPRESENTABLE:OnLoad( FsmP, Event, From, To, CargoCarrier ) +function CARGO_UNIT:OnUnBoarded( FsmP, Event, From, To, Speed, UnLoadDistance, UnBoardDistance, Radius, Angle ) self:F() + self:_NextEvent( FsmP.UnLoad, Speed, UnLoadDistance, UnBoardDistance, Radius, Angle ) +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:OnLoad( FsmP, Event, From, To, CargoCarrier ) + self:F() + + self:T( self.ClassName ) + self.CargoCarrier = CargoCarrier - self.CargoObject:Destroy() + + -- Only destroy the CargoObject is if there is a CargoObject (packages don't have CargoObjects). + if self.CargoObject then + self.CargoObject:Destroy() + end end --- UnLoad Event. --- @param #CARGO self +-- @param #CARGO_UNIT self +-- @param StateMachine#STATEMACHINE_PROCESS FsmP +-- @param #string Event +-- @param #string From +-- @param #string To +-- @param #number Distance +-- @param #number Angle +function CARGO_UNIT:OnUnLoad( FsmP, Event, From, To, Speed, UnLoadDistance, UnBoardDistance, Radius, 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( UnLoadDistance, CargoDeployHeading ) + + -- Respawn the group... + if self.CargoObject then + self.CargoObject:ReSpawn( CargoDeployPointVec2:GetVec3(), CargoDeployHeading ) + + local Points = {} + + 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 ) + self:T( { CargoCarrierHeading, CargoDeployHeading } ) + local CargoDeployPointVec2 = StartPointVec2:Translate( UnBoardDistance, CargoDeployHeading ) + local CargoDeployPointVec2 = CargoDeployPointVec2:GetRandomPointVec2InRadius( Radius ) + + Points[#Points+1] = StartPointVec2:RoutePointGround( Speed ) + Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) + + local TaskRoute = self.CargoObject:TaskRoute( Points ) + self.CargoObject:SetTask( TaskRoute, 4 ) + end +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 -function CARGO_REPRESENTABLE:OnUnLoad( FsmP, Event, From, To, Distance, Angle ) +-- @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, BoardDistance, 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() + + 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 ) + + 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 + +--- 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 ) - - -- Respawn the group... - self.CargoObject:ReSpawn( CargoDeployPointVec2:GetVec3(), CargoDeployHeading ) -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 } ) - - return self -end - -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() + + self.CargoCarrier = CargoCarrier local Points = {} + Points[#Points+1] = StartPointVec2:RoutePointGround( Speed ) + Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) - 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 + local TaskRoute = self.CargoCarrier:TaskRoute( Points ) + self.CargoCarrier:SetTask( TaskRoute, 1 ) 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" diff --git a/Moose Development/Moose/Point.lua b/Moose Development/Moose/Point.lua index d2223ad14..e21e1fc5a 100644 --- a/Moose Development/Moose/Point.lua +++ b/Moose Development/Moose/Point.lua @@ -224,7 +224,7 @@ end function POINT_VEC3:GetRandomPointVec2InRadius( OuterRadius, InnerRadius ) self:F2( { OuterRadius, InnerRadius } ) - return POINT_VEC2:NewFromVec2( self:GetRandomPointVec2InRadius( OuterRadius, InnerRadius ) ) + return POINT_VEC2:NewFromVec2( self:GetRandomVec2InRadius( OuterRadius, InnerRadius ) ) end --- Return a random Vec3 within an Outer Radius and optionally NOT within an Inner Radius of the POINT_VEC3. diff --git a/Moose Development/Moose/StateMachine.lua b/Moose Development/Moose/StateMachine.lua index 23ef36db1..c597cadff 100644 --- a/Moose Development/Moose/StateMachine.lua +++ b/Moose Development/Moose/StateMachine.lua @@ -67,6 +67,13 @@ function STATEMACHINE:New( options ) 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 } ) 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 new file mode 100644 index 0000000000000000000000000000000000000000..0def4c99fdd848981c213a82d88cff8dc41589c1 GIT binary patch literal 17588 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 N|21Hm-{J`30RVizsoDSl literal 0 HcmV?d00001 diff --git a/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_PACKAGE_Board/Moose_Test_CARGO_PACKAGE_Board.lua b/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_PACKAGE_Board/Moose_Test_CARGO_PACKAGE_Board.lua new file mode 100644 index 000000000..773a96c7a --- /dev/null +++ b/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_PACKAGE_Board/Moose_Test_CARGO_PACKAGE_Board.lua @@ -0,0 +1,13 @@ + +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 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. +Letter:Board( CargoCarrier, 40, 3, 25, 90 ) + diff --git a/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_UnBoard/MOOSE_Test_CARGO_UnBoard.miz b/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_PACKAGE_UnBoard/MOOSE_Test_CARGO_PACKAGE_UnBoard.miz similarity index 73% rename from Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_UnBoard/MOOSE_Test_CARGO_UnBoard.miz rename to Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_PACKAGE_UnBoard/MOOSE_Test_CARGO_PACKAGE_UnBoard.miz index cde3f57847f1e4367260eac0af5aa1740c950b34..ce5bc6adc02d356f239723e500be4cf71a736803 100644 GIT binary patch delta 4091 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 diff --git a/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_UnBoard/Moose_Test_CARGO_UnBoard.lua b/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_PACKAGE_UnBoard/Moose_Test_CARGO_PACKAGE_UnBoard.lua similarity index 94% rename from Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_UnBoard/Moose_Test_CARGO_UnBoard.lua rename to Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_PACKAGE_UnBoard/Moose_Test_CARGO_PACKAGE_UnBoard.lua index 88ebd7573..259cdc968 100644 --- a/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_UnBoard/Moose_Test_CARGO_UnBoard.lua +++ b/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_PACKAGE_UnBoard/Moose_Test_CARGO_PACKAGE_UnBoard.lua @@ -12,4 +12,4 @@ InfantryCargo:Load( CargoCarrier ) -- This will Unboard the Cargo from the Carrier. -- The Cargo will run from the Carrier to a point in the NearRadius around the Carrier. -- Unboard the Cargo with a speed of 10 km/h, go to 200 meters 180 degrees from the Carrier, iin a zone of 25 meters (NearRadius). -InfantryCargo:UnBoard( 10, 200, 180 ) \ No newline at end of file +InfantryCargo:UnBoard( 10, 2, 20, 10, 180 ) \ No newline at end of file diff --git a/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_Board/MOOSE_Test_CARGO_Board.miz b/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_UNIT_Board/MOOSE_Test_CARGO_UNIT_Board.miz similarity index 100% rename from Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_Board/MOOSE_Test_CARGO_Board.miz rename to Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_UNIT_Board/MOOSE_Test_CARGO_UNIT_Board.miz diff --git a/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_Board/Moose_Test_CARGO_Board.lua b/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_UNIT_Board/Moose_Test_CARGO_UNIT_Board.lua similarity index 100% rename from Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_Board/Moose_Test_CARGO_Board.lua rename to Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_UNIT_Board/Moose_Test_CARGO_UNIT_Board.lua 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 new file mode 100644 index 0000000000000000000000000000000000000000..ce5bc6adc02d356f239723e500be4cf71a736803 GIT binary patch literal 17592 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 O_4})}cM1Hzfp`EgUcX2H literal 0 HcmV?d00001 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 new file mode 100644 index 000000000..259cdc968 --- /dev/null +++ b/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_UNIT_UnBoard/Moose_Test_CARGO_UNIT_UnBoard.lua @@ -0,0 +1,15 @@ + +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 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. +-- The Cargo will run from the Carrier to a point in the NearRadius around the Carrier. +-- Unboard the Cargo with a speed of 10 km/h, go to 200 meters 180 degrees from the Carrier, iin a zone of 25 meters (NearRadius). +InfantryCargo:UnBoard( 10, 2, 20, 10, 180 ) \ No newline at end of file From 9f37dde093462672529e6548c014c81cbbf373f6 Mon Sep 17 00:00:00 2001 From: Sven Van de Velde Date: Wed, 10 Aug 2016 08:15:37 +0200 Subject: [PATCH 12/16] Progress --- Moose Development/Moose/Cargo.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Cargo.lua b/Moose Development/Moose/Cargo.lua index cb0b4ab66..ac0c669e8 100644 --- a/Moose Development/Moose/Cargo.lua +++ b/Moose Development/Moose/Cargo.lua @@ -559,7 +559,7 @@ function CARGO_PACKAGE:OnBoarded( FsmP, Event, From, To, CargoCarrier, Speed, Bo self:F() if self:IsNear( CargoCarrier ) then - self:_NextEvent( FsmP.Load, CargoCarrier, Speed, BoardDistance, LoadDistance, Angle ) + self:_NextEvent( FsmP.Load, CargoCarrier, Speed, LoadDistance, Angle ) else self:_NextEvent( FsmP.Boarded, CargoCarrier, Speed, BoardDistance, LoadDistance, Angle ) end @@ -638,13 +638,13 @@ end 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 ) - self.CargoCarrier = CargoCarrier - local Points = {} Points[#Points+1] = StartPointVec2:RoutePointGround( Speed ) Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) From bdbb1ea01834da49bb744f24cae1cdca97b23b19 Mon Sep 17 00:00:00 2001 From: Sven Van de Velde Date: Thu, 11 Aug 2016 09:01:47 +0200 Subject: [PATCH 13/16] Progress --- Moose Development/Moose/Cargo.lua | 286 ++++++++++++------ Moose Development/Moose/Point.lua | 49 ++- .../MOOSE_Test_CARGO_UNIT_Board.miz | Bin 16744 -> 16851 bytes .../Moose_Test_CARGO_UNIT_Board.lua | 4 +- .../MOOSE_Test_CARGO_UNIT_UnBoard.miz | Bin 17592 -> 17532 bytes .../Moose_Test_CARGO_UNIT_UnBoard.lua | 6 +- 6 files changed, 217 insertions(+), 128 deletions(-) diff --git a/Moose Development/Moose/Cargo.lua b/Moose Development/Moose/Cargo.lua index ac0c669e8..07dde6240 100644 --- a/Moose Development/Moose/Cargo.lua +++ b/Moose Development/Moose/Cargo.lua @@ -118,38 +118,28 @@ end --- Board Cargo to a Carrier with a defined Speed. -- @param #CARGO self -- @param Unit#UNIT CargoCarrier --- @param #number Speed --- @param #number BoardDistance --- @param #number Angle -function CARGO:Board( CargoCarrier, Speed, BoardDistance, LoadDistance, Angle ) +function CARGO:Board( CargoCarrier ) self:F() - self:_NextEvent( self.FsmP.Board, CargoCarrier, Speed, BoardDistance, LoadDistance, Angle ) + self:_NextEvent( self.FsmP.Board, CargoCarrier ) end ---- UnBoard Cargo from a Carrier with a defined Speed. +--- UnLoad Cargo from a Carrier. -- @param #CARGO self --- @param #number Speed --- @param #number UnLoadDistance --- @param #number UnBoardDistance --- @param #number UnBoardRadius --- @param #number Angle -function CARGO:UnBoard( Speed, UnLoadDistance, UnBoardDistance, UnBoardRadius, Angle ) +function CARGO:UnLoad() self:F() - self:_NextEvent( self.FsmP.UnBoard, Speed, UnLoadDistance, UnBoardDistance, UnBoardRadius, Angle ) + self:_NextEvent( self.FsmP.UnLoad ) end --- Check if CargoCarrier is near the Cargo to be Loaded. -- @param #CARGO self --- @param Unit#UNIT CargoCarrier +-- @param Point#POINT_VEC2 PointVec2 -- @return #boolean -function CARGO:IsNear( CargoCarrier ) +function CARGO:IsNear( PointVec2 ) self:F() - local CargoCarrierPoint = CargoCarrier:GetPointVec2() - - local Distance = CargoCarrierPoint:DistanceFromPointVec2( self.CargoObject:GetPointVec2() ) + local Distance = PointVec2:DistanceFromPointVec2( self.CargoObject:GetPointVec2() ) self:T( Distance ) if Distance <= self.NearRadius then @@ -260,23 +250,22 @@ function CARGO_UNIT:New( Mission, CargoUnit, Type, Name, Weight, ReportRadius, N initial = 'UnLoaded', events = { { name = 'Board', from = 'UnLoaded', to = 'Boarding' }, - { name = 'Boarded', from = 'Boarding', 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' }, - { 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, + onenterBoarding = self.EnterStateBoarding, + onleaveBoarding = self.LeaveStateBoarding, + onenterLoaded = self.EnterStateLoaded, + onenterUnBoarding = self.EnterStateUnBoarding, + onleaveUnBoarding = self.LeaveStateUnBoarding, + onenterUnLoaded = self.EnterStateUnLoaded, }, } ) @@ -285,14 +274,182 @@ function CARGO_UNIT:New( Mission, CargoUnit, Type, Name, Weight, ReportRadius, N return self end ---- Board Event. +--- 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 ) + 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.UnLoad, 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 + +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:OnBoard( FsmP, Event, From, To, CargoCarrier, Speed ) +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 +end + + +--- Board Event. +-- @param #CARGO_UNIT self +-- @param StateMachine#STATEMACHINE_PROCESS FsmP +-- @param #string Event +-- @param #string From +-- @param #string To +function CARGO_UNIT:OnBoard( FsmP, Event, From, To, CargoCarrier ) self:F() self.CargoInAir = self.CargoObject:InAir() @@ -302,21 +459,9 @@ function CARGO_UNIT:OnBoard( FsmP, Event, From, To, CargoCarrier, Speed ) -- 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 = {} - - local PointStartVec2 = self.CargoObject:GetPointVec2() - local PointEndVec2 = CargoCarrier:GetPointVec2() - - - Points[#Points+1] = PointStartVec2:RoutePointGround( Speed ) - Points[#Points+1] = PointEndVec2:RoutePointGround( Speed ) - - local TaskRoute = self.CargoObject:TaskRoute( Points ) - self.CargoObject:SetTask( TaskRoute, 4 ) + self:_NextEvent( FsmP.Load, CargoCarrier ) end - self:_NextEvent( FsmP.Boarded, CargoCarrier ) end @@ -343,12 +488,7 @@ end -- @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_UNIT:OnUnBoard( FsmP, Event, From, To, Speed, UnLoadDistance, UnBoardDistance, Radius, Angle ) +function CARGO_UNIT:OnUnBoard( FsmP, Event, From, To ) self:F() self.CargoInAir = self.CargoObject:InAir() @@ -361,24 +501,10 @@ function CARGO_UNIT:OnUnBoard( FsmP, Event, From, To, Speed, UnLoadDistance, UnB end - self:_NextEvent( FsmP.UnBoarded, Speed, UnLoadDistance, UnBoardDistance, Radius, Angle ) + self:_NextEvent( FsmP.UnLoad ) end ---- UnBoarded 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:OnUnBoarded( FsmP, Event, From, To, Speed, UnLoadDistance, UnBoardDistance, Radius, Angle ) - self:F() - - self:_NextEvent( FsmP.UnLoad, Speed, UnLoadDistance, UnBoardDistance, Radius, Angle ) -end - - --- Load Event. -- @param #CARGO_UNIT self -- @param StateMachine#STATEMACHINE_PROCESS FsmP @@ -391,12 +517,6 @@ function CARGO_UNIT:OnLoad( FsmP, Event, From, To, CargoCarrier ) self:T( self.ClassName ) - 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 end --- UnLoad Event. @@ -405,35 +525,9 @@ end -- @param #string Event -- @param #string From -- @param #string To --- @param #number Distance --- @param #number Angle -function CARGO_UNIT:OnUnLoad( FsmP, Event, From, To, Speed, UnLoadDistance, UnBoardDistance, Radius, Angle ) +function CARGO_UNIT:OnUnLoad( FsmP, Event, From, To ) 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( UnLoadDistance, CargoDeployHeading ) - - -- Respawn the group... - if self.CargoObject then - self.CargoObject:ReSpawn( CargoDeployPointVec2:GetVec3(), CargoDeployHeading ) - - local Points = {} - - 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 ) - self:T( { CargoCarrierHeading, CargoDeployHeading } ) - local CargoDeployPointVec2 = StartPointVec2:Translate( UnBoardDistance, CargoDeployHeading ) - local CargoDeployPointVec2 = CargoDeployPointVec2:GetRandomPointVec2InRadius( Radius ) - - Points[#Points+1] = StartPointVec2:RoutePointGround( Speed ) - Points[#Points+1] = CargoDeployPointVec2:RoutePointGround( Speed ) - - local TaskRoute = self.CargoObject:TaskRoute( Points ) - self.CargoObject:SetTask( TaskRoute, 4 ) - end end end diff --git a/Moose Development/Moose/Point.lua b/Moose Development/Moose/Point.lua index e21e1fc5a..de49027cb 100644 --- a/Moose Development/Moose/Point.lua +++ b/Moose Development/Moose/Point.lua @@ -304,24 +304,6 @@ function POINT_VEC3:Get3DDistance( TargetPointVec3 ) return ( ( TargetVec3.x - SourceVec3.x ) ^ 2 + ( TargetVec3.y - SourceVec3.y ) ^ 2 + ( TargetVec3.z - SourceVec3.z ) ^ 2 ) ^ 0.5 end ---- Add a Distance in meters from the POINT_VEC3 orthogonal plane, with the given angle, and calculate the new POINT_VEC3. --- @param #POINT_VEC3 self --- @param DCSTypes#Distance Distance The Distance to be added in meters. --- @param 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 SY = self:GetY() - local Radians = Angle / 180 * math.pi - local TX = Distance * math.cos( Radians ) + SX - local TY = Distance * math.sin( Radians ) + SY - - self:SetX( TX ) - self:SetY( TY ) - - return self -end - --- Provides a Bearing / Range string -- @param #POINT_VEC3 self -- @param #number AngleRadians The angle in randians @@ -580,9 +562,9 @@ POINT_VEC2 = { function POINT_VEC2:New( x, y, LandHeightAdd ) local LandHeight = land.getHeight( { ["x"] = x, ["y"] = y } ) - if LandHeightAdd then - LandHeight = LandHeight + LandHeightAdd - end + + LandHeightAdd = LandHeightAdd or 0 + LandHeight = LandHeight + LandHeightAdd local self = BASE:Inherit( self, POINT_VEC3:New( x, LandHeight, y ) ) @@ -595,12 +577,10 @@ end -- @return Point#POINT_VEC2 self function POINT_VEC2:NewFromVec2( Vec2, LandHeightAdd ) - local self = BASE:Inherit( self, BASE:New() ) - local LandHeight = land.getHeight( Vec2 ) - if LandHeightAdd then - LandHeight = LandHeight + LandHeightAdd - end + + 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 } ) @@ -694,3 +674,20 @@ function POINT_VEC2:GetAltitudeText() return '' 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. +-- @return #POINT_VEC2 The new calculated POINT_VEC2. +function POINT_VEC2:Translate( Distance, Angle ) + local SX = self:GetX() + local SY = self:GetY() + local Radians = Angle / 180 * math.pi + local TX = Distance * math.cos( Radians ) + SX + local TY = Distance * math.sin( Radians ) + SY + + return POINT_VEC2:New( TX, TY ) +end + + + 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 index 3437b5289c851f9b3b5e439d74f98d29d99e00f9..0d212d6e7524b5eaaf456371ba815f9a4b3b576e 100644 GIT binary patch delta 5183 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 delta 5082 zcmZ8lcQD*-*IuGT?>$&0dW$aFt`@AaLG&O>SkZe4KT#sOSiQ4K^xmUJh+y>+CEDsl zCx|C+p7;5_`R4h~%$YfJpZi=h_w~nh&vmB4uy#VR066R(4mAiHbRPr)fk8=OlBzF> zL7*FJ5D5Rzi8I2}6XEJ|Xuj;aC`}2BS+e~s!*w#6jS8(BTPA zJQ}OtK@~Orc=7uR)%75wkWr#)?ZmNj8phFyL zMK2&YL9_5TawkB{n)5?QH*UMl&Nu!LY(sMwye|sT<-y@ zUD*2HQ+S>vwzf|n$%sI#yM0Ne+;Mld2mxr~KyfCa4_|c@oxpk~)My$psk^|d8oYzI z?lScHhc($VzD}=~)CfDLmcFq$QGuII(G~sS=rgGKp`nnB2`*=d%Ti3{+~?S~Gt{rv z1r;6{-lv!c5&bg*qB0bQoMMtWJ^a8=c|tiMDgx|=tW-rfWdNGt3^%!T{W|WB=^zPQ zd>u+V@AH&BQN}AMS;|%|q~L&n zO0UJQZ5;wj_x3V8#uN* z{g4G;G=4JwdNV)m%DKfqFZOJ6I^t5V%?#3>oV+AoP)(H)>*3Gv=GVbmPnraC2@gs3 zdir1YLIvuJSfWAGA)~{Frh`-^wrVz$q2dOe8N(UWPg3VE<;J?&p$mZVX#;zm2Bg(76*oNJU_3g#@7=47stQ9Epu zk)}m=rPioHETlJ4kly1Yx?62MopTwm;iGaWVWxF+h#V-0atA)fgcXFb548)3rK`kU zdlli^3uU5*!u(p+ICiLNOwX`P>%6h>*i;dWMdD=0y5Xa_j85&dfi@f8-xoDt-eBV^K&!NA6@FIma+-+gJK8k zU=3yjm+mTf3>b5)I;E!lrX9{zaLHLDwq-77CSuAx#U9 zUs2J_EH{iQfXjphhw2JT_P>fg@Lr|CjF~~B^)Ny$cqj=Zx&4%wC)BVLZ`W*3zIR3L{5O(=E->R%*B6uvUW&&8pVq zL6O*pFWrk#>8Vffrd$1b=mt?!{{3K<+}^NzCqIcb5&30odBf_$puTL5e;WsQWI!i)5X_lg#6)?P3w-Oz#Y&$Q?=3l|~Wea8C%;D;=6w zXlh3nPJ;*Vi0F+cBY*jK?^srTraC@1$!`0ABpBxGXZo_-K}4jInluM%tueDYxQ zMN;fKXrL|3U8x|BQ1WW7cE!U+py^Zos-f?Xz2s(_F@bmOin*s{`Sb#M*IW~H2&o>DB$&+)j82zRA>lF9AXDjPL8|Hb*gQm zfcs-BsH|KLW1X&Ucxk(>@mU>a4@(Igk5HP{LE3RI!Yv?D!XjyI^@ppz@$9x6;|Y=_ z^n}FClX$qW22KGm);@I`nY8&#=p;`5xn%y^i*$58eolbQF1ETF|HY71Ar2lu`Be-j zf5b2!k1{_P99Xba=q>G8_dI5l+KQ=BRG^b(QRdVzQRo$v1Sh{zGJhmMi%uMUISZ}F zNqk#{;Vk&%`)(Yc;1eSuByJ%jsZYgdd~w>EkwE03N%TNc()jmcT{e&XRPn=HQ< zG3GG}XOAj2!F-4E|5iwXG{2p%807TW&(E<;gRk4ZNlxH-EE=7>JNJo7#&7)VCs^fU zWTMdf@U<9oIfu=SxBz%w%^p_NlB~n#3T*k4jYmiGjEjJR*Yh-~Di;e1z+_nL3-e(~ z*ed#9m`T;FTNcAEt3E~$E$q=x@-6?~bQ!J?zCye;ARA@jCHIa?dGKsVuAcR^XEP4NVvo3s$wUpAvArJpuGtQN!6&1ctgJkphM z%Oq~t(m87AqcFPtRq&0@2G%p~xT{^osdV11IoLYYh9ugPgxCBU7^P@YzT-rtK5srs z9W&yl*U25?qFNaeD-g#dng-pLH6&cX(kD_K($q>QA2~f}vv)nSMcDzCP*+&Y^@54j zAk?BkS}``a??A#FUzmaqL%VtM6GjEFGE>y~*=sw1?1lC|I*jxNczkix+kxvb+tJnz z*Yu8a9DZGNcS!|=>-J05gH@;%^3^DTm*dC3LZH+FDhXZd4ll+X1O)sRzyWxjp3$sN ziP8qQ9)^+DGSLSfy1_x`^B?z=oW zXE8>^-FZgeY6c=}mQr!cHU2uQ+xRav=3T*t9NB7BF-tITujkPlPp#DlRMLYuXd+Ix zMjPzy^qy;`Kb0cKn<*hjzu=&p-7Fork}ylA1E%DTC|b+O69CvV3MM?{WPgG8>i zFUL-Pu_X=kS}KB~_RluHd0+t#PEW#Ks>=t5h@X>P{e(7f3SBh#13iOe?cv!%-;#jV zv_Dm1^(tAbxr(K$%u)q@5RdnX>5b^gcMCv4eQBYSB*@bG^6+pojLqHXige-|8>>Rd z`vbdU-L3>%mfM4)yD+vFXTD5)8<4B3U(ogI`R#`h6TTm^x&VjvA9Fyz^{wrsy*{}o zo4KB(M6+d})#80cn5P{XpRuuuPuD5OuVZCRWak($ud2oL6Us8u%2+x&T*jNrPr6d> zO4DlS(x&4q<{y@(#ADyZcjA--cM*qR-7vxqsvT+oJ>!4IY0+780`qupup> zp^-6|K)FHfv(@q~Jy58_L%RGAzK(XU{h(2n)-H}8+w=tBrWzXf+TS~Qx*I{2eUycL z62kIg!V*pQ6EyYTX>Ruy7HSC)DxZn6`7kn)i_}YZf8Z38_>L2~sHYc$j1eV*ba{T| z)h5vpK*viB6wq$zDs%L`jVLm-PVQ%-6n&S7P4wW@?I>%FjBC&~+$^7M4?}<8NTZjW zp1qfjL_O6r$SjWs~HzdisZjI;_7FDyka07POqL9nj=;qpG zbJ_?It|zp0)V#+p1_dv>kG>>3JuR8MSq*zBeKsnk`&_0Vh55H6%bhIa)X~1J@Uay) zFqgMSw&qFxXte8D-}CS~B{*NiqXiry89+T|DmS#(oL%;s*)UXo2$zUaif;F6*H4lO zwQiH+OA$xe$CboYt=UI0O2>0z>v@VxW`n0^TBnahpw62$pZpMfSzPj)dcB6qpf&}% z6`P=9cE@7za)hZuTd$1rh(|2`E1|C(Vx+X}ouePgWX_v%2ZHv#{c(D7$FL(b6F{t) zoFO^RrEl^0L{nVWd3NVD&Gc5Rv?zI*5W#|HuKq%3)naum#4Ta+TwhhgpwH3Xi^jTr z|0^kkz9tW2@l`_X9iIj@UGvKutT-KeYvIcIYRF-xfbazyq3y{a47;?7r@rS_w_A&Y zMHWZR)d|a7Do*r>&|;2^Y|c_q7%)_pl%#fkOwV=6bkASN_&!%O2HeUYit6b2BH%-L z;i*!M(?XC^#}ayUKQ5@kg!5$)_7Jux!RWinpUXw`BavVFv5AsB$o{1dG7T{&tAKJHEWhIm@P36I~rvMv`bR`$* zEy$fX!-Hp4Gx4a=;e+6=pYs&Pi*d5+XMi$7%fHkp_pIb7U~@KX(Hb`4|I&5Z$L=`_ zbDGX2e#L{2$34&Sj3~X)xR172JC%_{?Bu%UGh`#Ysn%!pV-U_6xL5(K%kLVo7KJzW zLZWMelAv)iG5}@UC|0Oe35+NA$urcdQkL<+oFBaI=|Kv?bQeh-6Ev8#A=7Crm^}&S za%>?DVMLFWDtOX>HgsblYHKsaM0r_G#5w1~B-j223ryQw4Jx=a*ATpd9#mUV3Iqut zQUe_Z+t{No=_hpi+kAQp#KieaPu3-0;m*j!1u2$JkTqiE{iaF+c=(;42QKzc8H<#s zXusLS{ScofXrO_@4|iOpWOv(9(CBn_EdA|8hZp4LK+dTh;`y^P^|0Ats>v|$^Arh; zY1vlE4p(zsJTA`5@0ml2Y5cPCBg)qSoY~cWU7&8luQB`ffr3F9^uJ0c&5hm(EC&>~ zjQ+?TMO9hf%@S}w0&;f3B$IqQFTOaXGc_T1pnsL8f< zRb-hFE%}wr2$cs~#SX+2UZ-t>G1uws)@*ZcU!-!J3QoL_&_HIE2B!W<=o|O@8QC`- zEul)%y9)RU}O{V4*!Suu-rDEz1`swYn+nk9{aot0FUuL;=hLpn-g zwZT|1>dx33I*9Ji(+NWI5%CUOk`}> z@d#`CUV|QOG7s*5cV5VS1rgkTeipDI4R#DMvVxca30EX%`nMqeVdYQ!;pI<6AOjUu zu^5oOip=o;ueN{p|G8C04g&p;_QGLU*dWmVZQK6M^WP)?JG6upg0LYCAY#}DG)Na( vdSn5F4=V#X0)b)|JV25`wXmp==FlhDrqakIX?kQQl$db+544-Y-w^)+w0ctp diff --git a/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_UNIT_Board/Moose_Test_CARGO_UNIT_Board.lua b/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_UNIT_Board/Moose_Test_CARGO_UNIT_Board.lua index 3e1e338a2..b45425e7c 100644 --- a/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_UNIT_Board/Moose_Test_CARGO_UNIT_Board.lua +++ b/Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_UNIT_Board/Moose_Test_CARGO_UNIT_Board.lua @@ -1,5 +1,5 @@ -local Mission = MISSION:New( "Pickup Cargo", "High", "Test for Cargo Pickup", coalition.side.RED ) +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 ) @@ -9,5 +9,5 @@ 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, 10 ) +InfantryCargo:Board( CargoCarrier ) 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 index ce5bc6adc02d356f239723e500be4cf71a736803..51caecd1581f94e988dbb0e699ecfa611956ad9d 100644 GIT binary patch delta 4998 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 delta 5066 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%0AxrK*W;Do_*oleN0!}#SQ ztLB3XYibp+9igt6vZ7TXjs!18)Z!5#X&o6GQCAWD0mm8V{3j-b@~sZrTs0oEG|7nH zQl#1a1L53JTb}G;6knj9siUDYNuMvLwny&QZ4@lsgpMM|9$zqIpQ&?6px~#0^ZPv# zR>Knk_FQ#?awf%FXDLh#7t9KbOh3`|p4-0E((1-68A*DYZND*Wfe)t3eRcJ1DD3aq|xw zE`04zXjIR14J~M;w~wfa(aV{t``(nE@$cZcu51kM%ghlFi(frer_~|qqg_rKm zdB;YgZ&^W`>ohnzY@$5dhG=?$7W^{K2Cn>47wLfkhjwQhK?)ReJ$?A~bCM+JFaC#& zLGNU^p7dt<)8Aun}(ss yAPUO=-$DPaZ$s&2`RU1|004h0Z#zd1-* Date: Thu, 11 Aug 2016 10:08:22 +0200 Subject: [PATCH 14/16] Progress --- Moose Development/Moose/Cargo.lua | 77 +- .../l10n/DEFAULT/Moose.lua | 2973 +- Moose Mission Setup/Moose.lua | 29930 +++++++++++++++- .../MOOSE_Test_CARGO_UNIT_Transfer.miz | Bin 0 -> 201355 bytes .../Moose_Test_CARGO_UNIT_Transfer.lua | 28 + 5 files changed, 31714 insertions(+), 1294 deletions(-) create mode 100644 Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_UNIT_Transfer/MOOSE_Test_CARGO_UNIT_Transfer.miz create mode 100644 Moose Test Missions/Moose_Test_CARGO/Moose_Test_CARGO_UNIT_Transfer/Moose_Test_CARGO_UNIT_Transfer.lua diff --git a/Moose Development/Moose/Cargo.lua b/Moose Development/Moose/Cargo.lua index 07dde6240..8d500d001 100644 --- a/Moose Development/Moose/Cargo.lua +++ b/Moose Development/Moose/Cargo.lua @@ -150,29 +150,21 @@ function CARGO:IsNear( PointVec2 ) end ---- Loaded State. --- @param #CARGO self --- @param StateMachine#STATEMACHINE_PROCESS FsmP --- @param #string Event --- @param #string From --- @param #string To --- @param Unit#UNIT CargoCarrier -function CARGO:OnLoaded( FsmP, Event, From, To, CargoCarrier ) +--- On Loaded callback function. +function CARGO:OnLoaded( CallBackFunction, ... ) self:F() - - self:T( "Cargo " .. self.Name .. " loaded in " .. CargoCarrier:GetName() ) + + self.OnLoadedCallBack = CallBackFunction + self.OnLoadedParameters = arg + end ---- UnLoaded State. --- @param #CARGO self --- @param StateMachine#STATEMACHINE_PROCESS FsmP --- @param #string Event --- @param #string From --- @param #string To -function CARGO:OnUnLoaded( FsmP, Event, From, To ) +--- On UnLoaded callback function. +function CARGO:OnUnLoaded( CallBackFunction, ... ) self:F() - self:T( "Cargo " .. self.Name .. " unloaded from " .. self.CargoCarrier:GetName() ) + self.OnUnLoadedCallBack = CallBackFunction + self.OnUnLoadedParameters = arg end --- @param #CARGO self @@ -256,10 +248,10 @@ function CARGO_UNIT:New( Mission, CargoUnit, Type, Name, Weight, ReportRadius, N { name = 'Load', from = 'UnLoaded', to = 'Loaded' }, }, callbacks = { - onBoard = self.OnBoard, - onLoad = self.OnLoad, - onUnBoard = self.OnUnBoard, - onUnLoad = self.OnUnLoad, + onafterBoard = self.EventBoard, + onafterLoad = self.EventLoad, + onafterUnBoard = self.EventUnBoard, + onafterUnLoad = self.EventUnLoad, onenterBoarding = self.EnterStateBoarding, onleaveBoarding = self.LeaveStateBoarding, onenterLoaded = self.EnterStateLoaded, @@ -314,6 +306,8 @@ function CARGO_UNIT:EnterStateUnBoarding( FsmP, Event, From, To, ToPointVec2 ) local TaskRoute = self.CargoObject:TaskRoute( Points ) self.CargoObject:SetTask( TaskRoute, 1 ) + + self:_NextEvent( FsmP.UnBoard, ToPointVec2 ) end end @@ -337,7 +331,7 @@ function CARGO_UNIT:LeaveStateUnBoarding( FsmP, Event, From, To, ToPointVec2 ) if self:IsNear( ToPointVec2 ) then return true else - self:_NextEvent( FsmP.UnLoad, ToPointVec2 ) + self:_NextEvent( FsmP.UnBoard, ToPointVec2 ) end return false end @@ -368,6 +362,12 @@ function CARGO_UNIT:EnterStateUnLoaded( FsmP, Event, From, To, ToPointVec2 ) 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 @@ -440,6 +440,12 @@ function CARGO_UNIT:EnterStateLoaded( FsmP, Event, From, To, CargoCarrier ) if self.CargoObject then self.CargoObject:Destroy() end + + if self.OnLoadedCallBack then + self.OnLoadedCallBack( self, unpack( self.OnLoadedParameters ) ) + self.OnLoadedCallBack = nil + end + end @@ -449,7 +455,7 @@ end -- @param #string Event -- @param #string From -- @param #string To -function CARGO_UNIT:OnBoard( FsmP, Event, From, To, CargoCarrier ) +function CARGO_UNIT:EventBoard( FsmP, Event, From, To, CargoCarrier ) self:F() self.CargoInAir = self.CargoObject:InAir() @@ -465,30 +471,13 @@ function CARGO_UNIT:OnBoard( FsmP, Event, From, To, CargoCarrier ) end ---- Boarded 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:OnBoarded( FsmP, Event, From, To, CargoCarrier ) - self:F() - - if self:IsNear( CargoCarrier ) then - self:_NextEvent( FsmP.Load, CargoCarrier ) - else - self:_NextEvent( FsmP.Boarded, 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:OnUnBoard( FsmP, Event, From, To ) +function CARGO_UNIT:EventUnBoard( FsmP, Event, From, To ) self:F() self.CargoInAir = self.CargoObject:InAir() @@ -512,7 +501,7 @@ end -- @param #string From -- @param #string To -- @param Unit#UNIT CargoCarrier -function CARGO_UNIT:OnLoad( FsmP, Event, From, To, CargoCarrier ) +function CARGO_UNIT:EventLoad( FsmP, Event, From, To, CargoCarrier ) self:F() self:T( self.ClassName ) @@ -525,7 +514,7 @@ end -- @param #string Event -- @param #string From -- @param #string To -function CARGO_UNIT:OnUnLoad( FsmP, Event, From, To ) +function CARGO_UNIT:EventUnLoad( FsmP, Event, From, To ) self:F() end 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 9395486ea..f878cae10 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: 20160723_2026' ) +env.info( 'Moose Generation Timestamp: 20160811_0937' ) local base = _G Include = {} @@ -3471,6 +3471,23 @@ function OBJECT:GetID() 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. @@ -3697,11 +3714,11 @@ end -- -- 1) @{Positionable#POSITIONABLE} class, extends @{Identifiable#IDENTIFIABLE} -- =========================================================== --- The @{Positionable#POSITIONABLE} class is a wrapper class to handle the DCS Positionable objects: +-- The @{Positionable#POSITIONABLE} class is a wrapper class to handle the 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. +-- * 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: -- ------------------------------ @@ -3736,7 +3753,7 @@ POSITIONABLE = { --- Create a new POSITIONABLE from a DCSPositionable -- @param #POSITIONABLE self --- @param DCSPositionable#Positionable PositionableName The DCS Positionable name +-- @param DCSPositionable#Positionable PositionableName The POSITIONABLE name -- @return #POSITIONABLE self function POSITIONABLE:New( PositionableName ) local self = BASE:Inherit( self, IDENTIFIABLE:New( PositionableName ) ) @@ -3744,10 +3761,10 @@ function POSITIONABLE: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. +--- 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 DCS Positionable. --- @return #nil The DCS Positionable is not existing or alive. +-- @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 ) @@ -3762,21 +3779,42 @@ function POSITIONABLE:GetPositionVec3() return nil end ---- Returns the @{DCSTypes#Vec2} vector indicating the point in 2D of the DCS Positionable within the mission. +--- 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 DCS Positionable. --- @return #nil The DCS Positionable is not existing or alive. +-- @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 PositionablePointVec3 = DCSPositionable:getPosition().p + local PositionableVec3 = DCSPositionable:getPosition().p - local PositionablePointVec2 = {} - PositionablePointVec2.x = PositionablePointVec3.x - PositionablePointVec2.y = PositionablePointVec3.z + 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 @@ -3786,52 +3824,52 @@ function POSITIONABLE:GetVec2() end ---- Returns a random @{DCSTypes#Vec3} vector within a range, indicating the point in 3D of the DCS Positionable within the mission. +--- 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 DCS Positionable. --- @return #nil The DCS Positionable is not existing or alive. -function POSITIONABLE:GetRandomPointVec3( Radius ) +-- @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 PositionableRandomPointVec3 = {} + local PositionableRandomVec3 = {} 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; + 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( PositionableRandomPointVec3 ) - return PositionableRandomPointVec3 + self:T3( PositionableRandomVec3 ) + return PositionableRandomVec3 end return nil end ---- Returns the @{DCSTypes#Vec3} vector indicating the point in 3D of the DCS Positionable within the mission. +--- 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 DCS Positionable. --- @return #nil The DCS Positionable is not existing or alive. -function POSITIONABLE:GetPointVec3() +-- @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 PositionablePointVec3 = DCSPositionable:getPosition().p - self:T3( PositionablePointVec3 ) - return PositionablePointVec3 + local PositionableVec3 = DCSPositionable:getPosition().p + self:T3( PositionableVec3 ) + return PositionableVec3 end return nil end ---- Returns the altitude of the DCS Positionable. +--- Returns the altitude of the POSITIONABLE. -- @param Positionable#POSITIONABLE self --- @return DCSTypes#Distance The altitude of the DCS Positionable. --- @return #nil The DCS Positionable is not existing or alive. +-- @return DCSTypes#Distance The altitude of the POSITIONABLE. +-- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetAltitude() self:F2() @@ -3848,7 +3886,7 @@ 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. +-- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:IsAboveRunway() self:F2( self.PositionableName ) @@ -3856,8 +3894,8 @@ function POSITIONABLE:IsAboveRunway() if DCSPositionable then - local PointVec2 = self:GetVec2() - local SurfaceType = land.getSurfaceType( PointVec2 ) + local Vec2 = self:GetVec2() + local SurfaceType = land.getSurfaceType( Vec2 ) local IsAboveRunway = SurfaceType == land.SurfaceType.RUNWAY self:T2( IsAboveRunway ) @@ -3869,9 +3907,9 @@ end ---- Returns the DCS Positionable heading. +--- Returns the POSITIONABLE heading in degrees. -- @param Positionable#POSITIONABLE self --- @return #number The DCS Positionable heading +-- @return #number The POSTIONABLE heading function POSITIONABLE:GetHeading() local DCSPositionable = self:GetDCSObject() @@ -3883,6 +3921,7 @@ function POSITIONABLE:GetHeading() if PositionableHeading < 0 then PositionableHeading = PositionableHeading + 2 * math.pi end + PositionableHeading = PositionableHeading * 180 / math.pi self:T2( PositionableHeading ) return PositionableHeading end @@ -3892,10 +3931,10 @@ function POSITIONABLE:GetHeading() end ---- Returns true if the DCS Positionable is in the air. +--- Returns true if the 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. +-- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:InAir() self:F2( self.PositionableName ) @@ -3910,10 +3949,10 @@ function POSITIONABLE:InAir() return nil end ---- Returns the DCS Positionable velocity vector. +--- Returns the POSITIONABLE velocity vector. -- @param Positionable#POSITIONABLE self -- @return DCSTypes#Vec3 The velocity vector --- @return #nil The DCS Positionable is not existing or alive. +-- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetVelocity() self:F2( self.PositionableName ) @@ -3928,10 +3967,10 @@ function POSITIONABLE:GetVelocity() return nil end ---- Returns the @{Unit#UNIT} velocity in km/h. +--- Returns the POSITIONABLE 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. +-- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetVelocityKMH() self:F2( self.PositionableName ) @@ -4459,15 +4498,15 @@ 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 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( PointVec2, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack ) - self:F2( { self.ControllableName, PointVec2, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack } ) +function CONTROLLABLE:TaskBombing( Vec2, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack ) + self:F2( { self.ControllableName, Vec2, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack } ) -- Bombing = { -- id = 'Bombing', @@ -4484,7 +4523,7 @@ function CONTROLLABLE:TaskBombing( PointVec2, WeaponType, WeaponExpend, AttackQt local DCSTask DCSTask = { id = 'Bombing', params = { - point = PointVec2, + point = Vec2, weaponType = WeaponType, expend = WeaponExpend, attackQty = AttackQty, @@ -4577,15 +4616,15 @@ 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 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( PointVec2, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack ) - self:F2( { self.ControllableName, PointVec2, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack } ) +function CONTROLLABLE:TaskAttackMapObject( Vec2, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack ) + self:F2( { self.ControllableName, Vec2, WeaponType, WeaponExpend, AttackQty, Direction, ControllableAttack } ) -- AttackMapObject = { -- id = 'AttackMapObject', @@ -4602,7 +4641,7 @@ function CONTROLLABLE:TaskAttackMapObject( PointVec2, WeaponType, WeaponExpend, local DCSTask DCSTask = { id = 'AttackMapObject', params = { - point = PointVec2, + point = Vec2, weaponType = WeaponType, expend = WeaponExpend, attackQty = AttackQty, @@ -4746,11 +4785,11 @@ end -- 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 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, PointVec3, LastWaypointIndex ) - self:F2( { self.ControllableName, FollowControllable, PointVec3, LastWaypointIndex } ) +function CONTROLLABLE:TaskFollow( FollowControllable, Vec3, LastWaypointIndex ) + self:F2( { self.ControllableName, FollowControllable, Vec3, LastWaypointIndex } ) -- Follow = { -- id = 'Follow', @@ -4771,7 +4810,7 @@ function CONTROLLABLE:TaskFollow( FollowControllable, PointVec3, LastWaypointInd DCSTask = { id = 'Follow', params = { controllableId = FollowControllable:GetID(), - pos = PointVec3, + pos = Vec3, lastWptIndexFlag = LastWaypointIndexFlag, lastWptIndex = LastWaypointIndex, }, @@ -4787,13 +4826,13 @@ end -- 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 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, PointVec3, LastWaypointIndex, EngagementDistance, TargetTypes ) - self:F2( { self.ControllableName, FollowControllable, PointVec3, LastWaypointIndex, EngagementDistance, TargetTypes } ) +function CONTROLLABLE:TaskEscort( FollowControllable, Vec3, LastWaypointIndex, EngagementDistance, TargetTypes ) + self:F2( { self.ControllableName, FollowControllable, Vec3, LastWaypointIndex, EngagementDistance, TargetTypes } ) -- Escort = { -- id = 'Escort', @@ -4816,7 +4855,7 @@ function CONTROLLABLE:TaskEscort( FollowControllable, PointVec3, LastWaypointInd DCSTask = { id = 'Follow', params = { controllableId = FollowControllable:GetID(), - pos = PointVec3, + pos = Vec3, lastWptIndexFlag = LastWaypointIndexFlag, lastWptIndex = LastWaypointIndex, engagementDistMax = EngagementDistance, @@ -4833,11 +4872,11 @@ end --- (GROUND) Fire at a VEC2 point until ammunition is finished. -- @param #CONTROLLABLE self --- @param DCSTypes#Vec2 PointVec2 The point to fire at. +-- @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( PointVec2, Radius ) - self:F2( { self.ControllableName, PointVec2, Radius } ) +function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius ) + self:F2( { self.ControllableName, Vec2, Radius } ) -- FireAtPoint = { -- id = 'FireAtPoint', @@ -4850,7 +4889,7 @@ function CONTROLLABLE:TaskFireAtPoint( PointVec2, Radius ) local DCSTask DCSTask = { id = 'FireAtPoint', params = { - point = PointVec2, + point = Vec2, radius = Radius, } } @@ -4957,13 +4996,13 @@ 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#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( PointVec2, Radius, TargetTypes, Priority ) - self:F2( { self.ControllableName, PointVec2, Radius, TargetTypes, Priority } ) +function CONTROLLABLE:EnRouteTaskEngageTargets( Vec2, Radius, TargetTypes, Priority ) + self:F2( { self.ControllableName, Vec2, Radius, TargetTypes, Priority } ) -- EngageTargetsInZone = { -- id = 'EngageTargetsInZone', @@ -4978,7 +5017,7 @@ function CONTROLLABLE:EnRouteTaskEngageTargets( PointVec2, Radius, TargetTypes, local DCSTask DCSTask = { id = 'EngageTargetsInZone', params = { - point = PointVec2, + point = Vec2, zoneRadius = Radius, targetTypes = TargetTypes, priority = Priority @@ -5381,12 +5420,12 @@ end function CONTROLLABLE:TaskRouteToVec3( Point, Speed ) self:F2( { Point, Speed } ) - local ControllablePoint = self:GetUnit( 1 ):GetPointVec3() + local ControllableVec3 = self:GetUnit( 1 ):GetVec3() local PointFrom = {} - PointFrom.x = ControllablePoint.x - PointFrom.y = ControllablePoint.z - PointFrom.alt = ControllablePoint.y + PointFrom.x = ControllableVec3.x + PointFrom.y = ControllableVec3.z + PointFrom.alt = ControllableVec3.y PointFrom.alt_type = "BARO" PointFrom.type = "Turning Point" PointFrom.action = "Turning Point" @@ -8606,14 +8645,14 @@ function GROUP:GetVec2() 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() +--- 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 GroupPointVec3 = self:GetUnit(1):GetPointVec3() - self:T3( GroupPointVec3 ) - return GroupPointVec3 + local GroupVec3 = self:GetUnit(1):GetVec3() + self:T3( GroupVec3 ) + return GroupVec3 end @@ -8629,7 +8668,8 @@ function GROUP:IsCompletelyInZone( Zone ) for UnitID, UnitData in pairs( self:GetUnits() ) do local Unit = UnitData -- Unit#UNIT - if Zone:IsPointVec3InZone( Unit:GetPointVec3() ) then + -- TODO: Rename IsPointVec3InZone to IsVec3InZone + if Zone:IsPointVec3InZone( Unit:GetVec3() ) then else return false end @@ -8647,7 +8687,7 @@ function GROUP:IsPartlyInZone( Zone ) for UnitID, UnitData in pairs( self:GetUnits() ) do local Unit = UnitData -- Unit#UNIT - if Zone:IsPointVec3InZone( Unit:GetPointVec3() ) then + if Zone:IsPointVec3InZone( Unit:GetVec3() ) then return true end end @@ -8664,7 +8704,7 @@ function GROUP:IsNotInZone( Zone ) for UnitID, UnitData in pairs( self:GetUnits() ) do local Unit = UnitData -- Unit#UNIT - if Zone:IsPointVec3InZone( Unit:GetPointVec3() ) then + if Zone:IsPointVec3InZone( Unit:GetVec3() ) then return false end end @@ -8852,7 +8892,7 @@ end -- @param #table Template The template of the Group retrieved with GROUP:GetTemplate() function GROUP:Respawn( Template ) - local Vec3 = self:GetPointVec3() + local Vec3 = self:GetVec3() Template.x = Vec3.x Template.y = Vec3.z --Template.x = nil @@ -8863,7 +8903,7 @@ function GROUP:Respawn( Template ) local GroupUnit = UnitData -- Unit#UNIT self:E( GroupUnit:GetName() ) if GroupUnit:IsAlive() then - local GroupUnitVec3 = GroupUnit:GetPointVec3() + local GroupUnitVec3 = GroupUnit:GetVec3() local GroupUnitHeading = GroupUnit:GetHeading() Template.units[UnitID].alt = GroupUnitVec3.y Template.units[UnitID].x = GroupUnitVec3.x @@ -9036,7 +9076,7 @@ end -- 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. +-- 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 @@ -9140,6 +9180,14 @@ function UNIT:FindByName( 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 @@ -9154,6 +9202,59 @@ function UNIT:GetDCSObject() 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 @@ -9175,22 +9276,6 @@ function UNIT:IsActive() 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. @@ -9509,7 +9594,7 @@ function UNIT:IsInZone( Zone ) self:F2( { self.UnitName, Zone } ) if self:IsAlive() then - local IsInZone = Zone:IsPointVec3InZone( self:GetPointVec3() ) + local IsInZone = Zone:IsPointVec3InZone( self:GetVec3() ) self:T( { IsInZone } ) return IsInZone @@ -9526,7 +9611,7 @@ function UNIT:IsNotInZone( Zone ) self:F2( { self.UnitName, Zone } ) if self:IsAlive() then - local IsInZone = not Zone:IsPointVec3InZone( self:GetPointVec3() ) + local IsInZone = not Zone:IsPointVec3InZone( self:GetVec3() ) self:T( { IsInZone } ) return IsInZone @@ -9548,10 +9633,10 @@ function UNIT:OtherUnitInRadius( AwaitUnit, Radius ) local DCSUnit = self:GetDCSObject() if DCSUnit then - local UnitPos = self:GetPointVec3() - local AwaitUnitPos = AwaitUnit:GetPointVec3() + local UnitVec3 = self:GetVec3() + local AwaitUnitVec3 = AwaitUnit:GetVec3() - if (((UnitPos.x - AwaitUnitPos.x)^2 + (UnitPos.z - AwaitUnitPos.z)^2)^0.5 <= Radius) then + if (((UnitVec3.x - AwaitUnitVec3.x)^2 + (UnitVec3.z - AwaitUnitVec3.z)^2)^0.5 <= Radius) then self:T3( "true" ) return true else @@ -9569,35 +9654,35 @@ end -- @param #UNIT self function UNIT:Flare( FlareColor ) self:F2() - trigger.action.signalFlare( self:GetPointVec3(), FlareColor , 0 ) + 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:GetPointVec3(), trigger.flareColor.White , 0 ) + 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:GetPointVec3(), trigger.flareColor.Yellow , 0 ) + 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:GetPointVec3(), trigger.flareColor.Green , 0 ) + 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:GetPointVec3() + local Vec3 = self:GetVec3() if Vec3 then trigger.action.signalFlare( Vec3, trigger.flareColor.Red, 0 ) end @@ -9608,9 +9693,9 @@ end function UNIT:Smoke( SmokeColor, Range ) self:F2() if Range then - trigger.action.smoke( self:GetRandomPointVec3( Range ), SmokeColor ) + trigger.action.smoke( self:GetRandomVec3( Range ), SmokeColor ) else - trigger.action.smoke( self:GetPointVec3(), SmokeColor ) + trigger.action.smoke( self:GetVec3(), SmokeColor ) end end @@ -9619,35 +9704,35 @@ end -- @param #UNIT self function UNIT:SmokeGreen() self:F2() - trigger.action.smoke( self:GetPointVec3(), trigger.smokeColor.Green ) + 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:GetPointVec3(), trigger.smokeColor.Red ) + 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:GetPointVec3(), trigger.smokeColor.White ) + 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:GetPointVec3(), trigger.smokeColor.Orange ) + 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:GetPointVec3(), trigger.smokeColor.Blue ) + trigger.action.smoke( self:GetVec3(), trigger.smokeColor.Blue ) end -- Is methods @@ -9852,12 +9937,12 @@ end --- Returns if a point is within the zone. -- @param #ZONE_BASE self --- @param DCSTypes#Vec3 PointVec3 The point to test. +-- @param DCSTypes#Vec3 Vec3 The point to test. -- @return #boolean true if the point is within the zone. -function ZONE_BASE:IsPointVec3InZone( PointVec3 ) - self:F2( PointVec3 ) +function ZONE_BASE:IsPointVec3InZone( Vec3 ) + self:F2( Vec3 ) - local InZone = self:IsPointVec2InZone( { x = PointVec3.x, y = PointVec3.z } ) + local InZone = self:IsPointVec2InZone( { x = Vec3.x, y = Vec3.z } ) return InZone end @@ -10023,11 +10108,11 @@ function ZONE_RADIUS:SetPointVec2( Vec2 ) return self.Vec2 end ---- Returns the point of the zone. +--- Returns the @{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. -function ZONE_RADIUS:GetPointVec3( Height ) +function ZONE_RADIUS:GetVec3( Height ) self:F2( { self.ZoneName, Height } ) Height = Height or 0 @@ -10061,7 +10146,7 @@ end --- Returns if a point is within the zone. -- @param #ZONE_RADIUS self --- @param DCSTypes#Vec3 PointVec3 The point to test. +-- @param 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 ) @@ -10186,22 +10271,22 @@ function ZONE_UNIT:GetRandomVec2() return Point end ---- Returns the point of the zone. --- @param #ZONE_RADIUS self +--- Returns the @{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. -function ZONE_UNIT:GetPointVec3( Height ) +function ZONE_UNIT:GetVec3( 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 } + local Vec3 = { x = Vec2.x, y = land.getHeight( self:GetVec2() ) + Height, z = Vec2.y } - self:T2( { PointVec3 } ) + self:T2( { Vec3 } ) - return PointVec3 + return Vec3 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. @@ -11378,19 +11463,19 @@ end -- @param #table SpawnTemplate -- @return #DATABASE self function DATABASE:Spawn( SpawnTemplate ) - self:F2( SpawnTemplate.name ) + self:F( SpawnTemplate.name ) - self:T2( { SpawnTemplate.SpawnCountryID, SpawnTemplate.SpawnCategoryID } ) + self:T( { 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 + local SpawnCoalitionID = SpawnTemplate.CoalitionID + local SpawnCountryID = SpawnTemplate.CountryID + local SpawnCategoryID = SpawnTemplate.CategoryID -- Nullify - SpawnTemplate.SpawnCoalitionID = nil - SpawnTemplate.SpawnCountryID = nil - SpawnTemplate.SpawnCategoryID = nil + SpawnTemplate.CoalitionID = nil + SpawnTemplate.CountryID = nil + SpawnTemplate.CategoryID = nil self:_RegisterTemplate( SpawnTemplate, SpawnCoalitionID, SpawnCategoryID, SpawnCountryID ) @@ -11398,9 +11483,9 @@ function DATABASE:Spawn( SpawnTemplate ) coalition.addGroup( SpawnCountryID, SpawnCategoryID, SpawnTemplate ) -- Restore - SpawnTemplate.SpawnCoalitionID = SpawnCoalitionID - SpawnTemplate.SpawnCountryID = SpawnCountryID - SpawnTemplate.SpawnCategoryID = SpawnCategoryID + SpawnTemplate.CoalitionID = SpawnCoalitionID + SpawnTemplate.CountryID = SpawnCountryID + SpawnTemplate.CategoryID = SpawnCategoryID local SpawnGroup = self:AddGroup( SpawnTemplate.name ) return SpawnGroup @@ -11444,6 +11529,10 @@ function DATABASE:_RegisterTemplate( GroupTemplate, CoalitionID, CategoryID, Cou GroupTemplate.route.spans = nil end + GroupTemplate.CategoryID = CategoryID + GroupTemplate.CoalitionID = CoalitionID + GroupTemplate.CountryID = CountryID + self.Templates.Groups[GroupTemplateName].GroupName = GroupTemplateName self.Templates.Groups[GroupTemplateName].Template = GroupTemplate self.Templates.Groups[GroupTemplateName].groupId = GroupTemplate.groupId @@ -11468,26 +11557,27 @@ function DATABASE:_RegisterTemplate( GroupTemplate, CoalitionID, CategoryID, Cou 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 + UnitTemplate.name = env.getValueDictByKey(UnitTemplate.name) + + self.Templates.Units[UnitTemplate.name] = {} + self.Templates.Units[UnitTemplate.name].UnitName = UnitTemplate.name + self.Templates.Units[UnitTemplate.name].Template = UnitTemplate + self.Templates.Units[UnitTemplate.name].GroupName = GroupTemplateName + self.Templates.Units[UnitTemplate.name].GroupTemplate = GroupTemplate + self.Templates.Units[UnitTemplate.name].GroupId = GroupTemplate.groupId + self.Templates.Units[UnitTemplate.name].CategoryID = CategoryID + self.Templates.Units[UnitTemplate.name].CoalitionID = CoalitionID + self.Templates.Units[UnitTemplate.name].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.ClientsByName[UnitTemplate.name] = UnitTemplate + self.Templates.ClientsByName[UnitTemplate.name].CategoryID = CategoryID + self.Templates.ClientsByName[UnitTemplate.name].CoalitionID = CoalitionID + self.Templates.ClientsByName[UnitTemplate.name].CountryID = CountryID self.Templates.ClientsByID[UnitTemplate.unitId] = UnitTemplate end - TraceTable[#TraceTable+1] = self.Templates.Units[UnitTemplateName].UnitName + TraceTable[#TraceTable+1] = self.Templates.Units[UnitTemplate.name].UnitName end self:E( TraceTable ) @@ -11501,6 +11591,14 @@ function DATABASE:GetGroupTemplate( GroupName ) return GroupTemplate end +function DATABASE:GetGroupNameFromUnitName( UnitName ) + return self.Templates.Units[UnitName].GroupName +end + +function DATABASE:GetGroupTemplateFromUnitName( UnitName ) + return self.Templates.Units[UnitName].GroupTemplate +end + function DATABASE:GetCoalitionFromClientTemplate( ClientName ) return self.Templates.ClientsByName[ClientName].CoalitionID end @@ -14138,7 +14236,9 @@ end --- The POINT_VEC3 class -- @type POINT_VEC3 -- @extends Base#BASE --- @field DCSTypes#Vec3 PointVec3 +-- @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 #POINT_VEC3.RoutePointAltType RoutePointAltType @@ -14220,8 +14320,9 @@ POINT_VEC3 = { 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 ) + self.x = x + self.y = y + self.z = z return self end @@ -14239,14 +14340,14 @@ end -- @param #POINT_VEC3 self -- @return DCSTypes#Vec3 The Vec3 coodinate. function POINT_VEC3:GetVec3() - return self.PointVec3 + 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. function POINT_VEC3:GetVec2() - return { x = self.PointVec3.x, y = self.PointVec3.z } + return { x = self.x, y = self.z } end @@ -14254,33 +14355,48 @@ end -- @param #POINT_VEC3 self -- @return #number The x coodinate. function POINT_VEC3:GetX() - self:F2(self.PointVec3.x) - return self.PointVec3.x + return self.x end --- Return the y coordinate of the POINT_VEC3. -- @param #POINT_VEC3 self -- @return #number The y coodinate. function POINT_VEC3:GetY() - self:F2(self.PointVec3.y) - return self.PointVec3.y + return self.y end --- Return the z coordinate of the POINT_VEC3. -- @param #POINT_VEC3 self -- @return #number The z coodinate. function POINT_VEC3:GetZ() - self:F2(self.PointVec3.z) - return self.PointVec3.z + return self.z end ---- Return a random Vec3 point within an Outer Radius and optionally NOT within an Inner Radius of the POINT_VEC3. +--- Set the x coordinate of the POINT_VEC3. +-- @param #number x The x coordinate. +function POINT_VEC3:SetX( x ) + self.x = x +end + +--- Set the y coordinate of the POINT_VEC3. +-- @param #number y The y coordinate. +function POINT_VEC3:SetY( y ) + self.y = y +end + +--- Set the z coordinate of the POINT_VEC3. +-- @param #number z The z coordinate. +function POINT_VEC3:SetZ( z ) + self.z = z +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 function POINT_VEC3:GetRandomVec2InRadius( OuterRadius, InnerRadius ) - self:F2( { self.PointVec3, OuterRadius, InnerRadius } ) + self:F2( { OuterRadius, InnerRadius } ) local Theta = 2 * math.pi * math.random() local Radials = math.random() + math.random() @@ -14295,17 +14411,28 @@ function POINT_VEC3:GetRandomVec2InRadius( OuterRadius, InnerRadius ) RadialMultiplier = OuterRadius * Radials end - local RandomVec3 + local RandomVec2 if OuterRadius > 0 then - RandomVec3 = { x = math.cos( Theta ) * RadialMultiplier + self:GetX(), y = math.sin( Theta ) * RadialMultiplier + self:GetZ() } + RandomVec2 = { x = math.cos( Theta ) * RadialMultiplier + self:GetX(), y = math.sin( Theta ) * RadialMultiplier + self:GetZ() } else - RandomVec3 = { x = self:GetX(), y = self:GetZ() } + RandomVec2 = { x = self:GetX(), y = self:GetZ() } end - return RandomVec3 + return RandomVec2 end ---- Return a random Vec3 point within an Outer Radius and optionally NOT within an Inner Radius of the POINT_VEC3. +--- 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 +-- @return #POINT_VEC2 +function POINT_VEC3:GetRandomPointVec2InRadius( OuterRadius, InnerRadius ) + self:F2( { OuterRadius, InnerRadius } ) + + return POINT_VEC2:NewFromVec2( self:GetRandomVec2InRadius( OuterRadius, InnerRadius ) ) +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 @@ -14319,10 +14446,20 @@ function POINT_VEC3:GetRandomVec3InRadius( OuterRadius, InnerRadius ) return RandomVec3 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 +-- @return #POINT_VEC3 +function POINT_VEC3:GetRandomPointVec3InRadius( OuterRadius, InnerRadius ) + + return POINT_VEC3:NewFromVec3( self:GetRandomVec3InRadius( OuterRadius, InnerRadius ) ) +end + --- Return a direction vector Vec3 from POINT_VEC3 to the POINT_VEC3. -- @param #POINT_VEC3 self --- @param #POINT_VEC3 TargetPointVec3 The target PointVec3. +-- @param #POINT_VEC3 TargetPointVec3 The target POINT_VEC3. -- @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() } @@ -14354,7 +14491,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 PointVec3. +-- @param #POINT_VEC3 TargetPointVec3 The target POINT_VEC3. -- @return DCSTypes#Distance Distance The distance in meters. function POINT_VEC3:Get2DDistance( TargetPointVec3 ) local TargetVec3 = TargetPointVec3:GetVec3() @@ -14364,7 +14501,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 PointVec3. +-- @param #POINT_VEC3 TargetPointVec3 The target POINT_VEC3. -- @return DCSTypes#Distance Distance The distance in meters. function POINT_VEC3:Get3DDistance( TargetPointVec3 ) local TargetVec3 = TargetPointVec3:GetVec3() @@ -14401,7 +14538,7 @@ end function POINT_VEC3:ToStringLL( acc, DMS ) acc = acc or 3 - local lat, lon = coord.LOtoLL( self.PointVec3 ) + local lat, lon = coord.LOtoLL( self:GetVec3() ) return UTILS.tostringLL(lat, lon, acc, DMS) end @@ -14418,7 +14555,7 @@ end --- Return a BR string from a POINT_VEC3 to the POINT_VEC3. -- @param #POINT_VEC3 self --- @param #POINT_VEC3 TargetPointVec3 The target PointVec3. +-- @param #POINT_VEC3 TargetPointVec3 The target POINT_VEC3. -- @return #string The BR text. function POINT_VEC3:GetBRText( TargetPointVec3 ) local DirectionVec3 = self:GetDirectionVec3( TargetPointVec3 ) @@ -14456,9 +14593,9 @@ 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.x = self:GetX() + RoutePoint.y = self:GetZ() + RoutePoint.alt = self:GetY() RoutePoint.alt_type = AltType RoutePoint.type = Type @@ -14488,13 +14625,52 @@ function POINT_VEC3:RoutePointAir( AltType, Type, Action, Speed, SpeedLocked ) return RoutePoint end +--- Build an ground type route point. +-- @param #POINT_VEC3 self +-- @param 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 ) + self:F2( { Formation, Speed } ) + + local RoutePoint = {} + RoutePoint.x = self:GetX() + RoutePoint.y = self:GetZ() + + RoutePoint.action = Formation or "" + + + 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 ) + self:F2( { SmokeColor } ) + trigger.action.smoke( self:GetVec3(), SmokeColor ) end --- Smoke the POINT_VEC3 Green. @@ -14537,8 +14713,8 @@ end -- @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 ) + self:F2( { FlareColor } ) + trigger.action.signalFlare( self:GetVec3(), FlareColor, Azimuth and Azimuth or 0 ) end --- Flare the POINT_VEC3 White. @@ -14575,8 +14751,9 @@ end --- The POINT_VEC2 class -- @type POINT_VEC2 --- @field DCSTypes#Vec2 PointVec2 -- @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", } @@ -14590,15 +14767,12 @@ POINT_VEC2 = { function POINT_VEC2:New( x, y, LandHeightAdd ) local LandHeight = land.getHeight( { ["x"] = x, ["y"] = y } ) - if LandHeightAdd then - LandHeight = LandHeight + LandHeightAdd - end + + LandHeightAdd = LandHeightAdd or 0 + LandHeight = LandHeight + LandHeightAdd 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 @@ -14608,30 +14782,77 @@ end -- @return Point#POINT_VEC2 self function POINT_VEC2:NewFromVec2( Vec2, LandHeightAdd ) - local self = BASE:Inherit( self, BASE:New() ) - local LandHeight = land.getHeight( Vec2 ) - if LandHeightAdd then - LandHeight = LandHeight + LandHeightAdd - end + + 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.PointVec2 = Vec2 - self:F2( self.PointVec3 ) + 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 +function POINT_VEC2:NewFromVec3( Vec3 ) + + local self = BASE:Inherit( self, BASE:New() ) + local Vec2 = { x = Vec3.x, y = Vec3.z } + + 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 } ) return self end ---- Calculate the distance from a reference @{Point#POINT_VEC2}. +--- Return the x coordinate of the 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. +-- @return #number The x coodinate. +function POINT_VEC2:GetX() + return self.x +end + +--- Return the y coordinate of the POINT_VEC2. +-- @param #POINT_VEC2 self +-- @return #number The y coodinate. +function POINT_VEC2:GetY() + return self.z +end + +--- Return the altitude of the land at the POINT_VEC2. +-- @param #POINT_VEC2 self +-- @return #number The land altitude. +function POINT_VEC2:GetAlt() + return land.getHeight( { x = self.x, y = self.z } ) +end + +--- Set the x coordinate of the POINT_VEC2. +-- @param #number x The x coordinate. +function POINT_VEC2:SetX( x ) + self.x = x +end + +--- Set the y coordinate of the POINT_VEC2. +-- @param #number y The y coordinate. +function POINT_VEC2:SetY( y ) + self.z = y +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. 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 + local Distance = ( ( PointVec2Reference:GetX() - self:GetX() ) ^ 2 + ( PointVec2Reference:GetY() - self:GetY() ) ^2 ) ^0.5 self:T2( Distance ) return Distance @@ -14644,7 +14865,7 @@ end function POINT_VEC2:DistanceFromVec2( Vec2Reference ) self:F2( Vec2Reference ) - local Distance = ( ( Vec2Reference.x - self.PointVec2.x ) ^ 2 + ( Vec2Reference.y - self.PointVec2.y ) ^2 ) ^0.5 + local Distance = ( ( Vec2Reference.x - self:GetX() ) ^ 2 + ( Vec2Reference.y - self:GetY() ) ^2 ) ^0.5 self:T2( Distance ) return Distance @@ -14658,6 +14879,23 @@ function POINT_VEC2:GetAltitudeText() return '' 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. +-- @return #POINT_VEC2 The new calculated POINT_VEC2. +function POINT_VEC2:Translate( Distance, Angle ) + local SX = self:GetX() + local SY = self:GetY() + local Radians = Angle / 180 * math.pi + local TX = Distance * math.cos( Radians ) + SX + local TY = Distance * math.sin( Radians ) + SY + + return POINT_VEC2:New( TX, TY ) +end + + + --- The main include file for the MOOSE system. Include.File( "Routines" ) @@ -15550,39 +15788,988 @@ function SCORING:CloseCSV() end end ---- CARGO Classes --- @module CARGO +--- 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 - - - - ---- 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 = {} +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" } - } - } + 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. @@ -15590,1045 +16777,301 @@ CARGO_ZONE = { -- 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. +-- @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:F( { CargoZoneName, CargoHostName } ) - self.CargoZoneName = CargoZoneName - self.SignalHeight = 2 - --self.CargoZone = trigger.misc.getZone( CargoZoneName ) - + 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 + if CargoHostName then + self.CargoHostName = CargoHostName + end + + self:T( self.CargoZoneName ) + + return self end function CARGO_ZONE:Spawn() - self:F( self.CargoHostName ) + 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 + 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 + return self end function CARGO_ZONE:GetHostUnit() - self:F( self ) + 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 + 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() + self:F() - local SignalUnit = self:GetHostUnit() + local SignalUnit = self:GetHostUnit() - if SignalUnit then - - local SignalUnitTypeName = SignalUnit:getTypeName() - - local HostMessage = "" + if SignalUnit then - 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 + local SignalUnitTypeName = SignalUnit:getTypeName() - Client:Message( HostMessage, 20, SignalUnitTypeName .. ": Reporting Cargo", 10 ) - end + 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() + self:F() - local Signalled = false + 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 then - if self.SignalType.ID == CARGO_ZONE.SIGNAL.TYPE.SMOKE.ID then + if self.CargoHostName then - trigger.action.smoke( SignalVehiclePos, self.SignalColor.TRIGGERCOLOR ) - Signalled = true + -- A Host has been given, signal the host - elseif self.SignalType.ID == CARGO_ZONE.SIGNAL.TYPE.FLARE.ID then + local SignalUnit = self:GetHostUnit() - trigger.action.signalFlare( SignalVehiclePos, self.SignalColor.TRIGGERCOLOR , 0 ) - Signalled = false + if SignalUnit then - 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 + self:T( 'Signalling Unit' ) + local SignalVehicleVec3 = SignalUnit:GetVec3() + SignalVehicleVec3.y = SignalVehicleVec3.y + 2 - trigger.action.smoke( ZonePointVec3, self.SignalColor.TRIGGERCOLOR ) - Signalled = true + if self.SignalType.ID == CARGO_ZONE.SIGNAL.TYPE.SMOKE.ID then - elseif self.SignalType.ID == CARGO_ZONE.SIGNAL.TYPE.FLARE.ID then - trigger.action.signalFlare( ZonePointVec3, self.SignalColor.TRIGGERCOLOR, 0 ) - Signalled = false + trigger.action.smoke( SignalVehicleVec3, self.SignalColor.TRIGGERCOLOR ) + Signalled = true - end - end - end - - return Signalled + 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:F() - self.SignalType = CARGO_ZONE.SIGNAL.TYPE.SMOKE - self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.WHITE - - if SignalHeight then - self.SignalHeight = SignalHeight - end + self.SignalType = CARGO_ZONE.SIGNAL.TYPE.SMOKE + self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.WHITE - return self + if SignalHeight then + self.SignalHeight = SignalHeight + end + + return self end function CARGO_ZONE:BlueSmoke( SignalHeight ) - self:F() + self:F() - self.SignalType = CARGO_ZONE.SIGNAL.TYPE.SMOKE - self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.BLUE + self.SignalType = CARGO_ZONE.SIGNAL.TYPE.SMOKE + self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.BLUE if SignalHeight then - self.SignalHeight = SignalHeight + self.SignalHeight = SignalHeight end - return self + return self end function CARGO_ZONE:RedSmoke( SignalHeight ) - self:F() + self:F() - self.SignalType = CARGO_ZONE.SIGNAL.TYPE.SMOKE - self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.RED + self.SignalType = CARGO_ZONE.SIGNAL.TYPE.SMOKE + self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.RED if SignalHeight then - self.SignalHeight = SignalHeight + self.SignalHeight = SignalHeight end - return self + return self end function CARGO_ZONE:OrangeSmoke( SignalHeight ) - self:F() + self:F() - self.SignalType = CARGO_ZONE.SIGNAL.TYPE.SMOKE - self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.ORANGE + self.SignalType = CARGO_ZONE.SIGNAL.TYPE.SMOKE + self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.ORANGE if SignalHeight then - self.SignalHeight = SignalHeight + self.SignalHeight = SignalHeight end - return self + return self end function CARGO_ZONE:GreenSmoke( SignalHeight ) - self:F() + self:F() - self.SignalType = CARGO_ZONE.SIGNAL.TYPE.SMOKE - self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.GREEN + self.SignalType = CARGO_ZONE.SIGNAL.TYPE.SMOKE + self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.GREEN if SignalHeight then - self.SignalHeight = SignalHeight + self.SignalHeight = SignalHeight end - return self + return self end function CARGO_ZONE:WhiteFlare( SignalHeight ) - self:F() + self:F() - self.SignalType = CARGO_ZONE.SIGNAL.TYPE.FLARE - self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.WHITE + self.SignalType = CARGO_ZONE.SIGNAL.TYPE.FLARE + self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.WHITE if SignalHeight then - self.SignalHeight = SignalHeight + self.SignalHeight = SignalHeight end - return self + return self end function CARGO_ZONE:RedFlare( SignalHeight ) - self:F() + self:F() - self.SignalType = CARGO_ZONE.SIGNAL.TYPE.FLARE - self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.RED + self.SignalType = CARGO_ZONE.SIGNAL.TYPE.FLARE + self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.RED if SignalHeight then - self.SignalHeight = SignalHeight + self.SignalHeight = SignalHeight end - return self + return self end function CARGO_ZONE:GreenFlare( SignalHeight ) - self:F() + self:F() - self.SignalType = CARGO_ZONE.SIGNAL.TYPE.FLARE - self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.GREEN + self.SignalType = CARGO_ZONE.SIGNAL.TYPE.FLARE + self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.GREEN if SignalHeight then - self.SignalHeight = SignalHeight + self.SignalHeight = SignalHeight end - return self + return self end function CARGO_ZONE:YellowFlare( SignalHeight ) - self:F() + self:F() - self.SignalType = CARGO_ZONE.SIGNAL.TYPE.FLARE - self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.YELLOW + self.SignalType = CARGO_ZONE.SIGNAL.TYPE.FLARE + self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.YELLOW if SignalHeight then - self.SignalHeight = SignalHeight + self.SignalHeight = SignalHeight end - return self + return self end function CARGO_ZONE:GetCargoHostUnit() - self:F( self ) + 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 + 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 + return nil end function CARGO_ZONE:GetCargoZoneName() - self:F() + 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 - + return self.CargoZoneName 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} @@ -20444,7 +20887,7 @@ end -- 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 new groups get spawned by using the SPAWN methods (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. @@ -20465,10 +20908,10 @@ end -- ------------------------------- -- 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. +-- * @{#SPAWN.New}: Creates a new SPAWN object taking the name of the group that represents the GROUP Template (definition). -- -- 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. +-- 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. -- 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 @@ -20476,11 +20919,11 @@ end -- 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.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 functions are @{#SPAWN.InitRepeatOnLanding} and @{#SPAWN.InitRepeatOnEngineShutDown}. +-- * @{#SPAWN.InitRepeat}: Re-spawn groups when they land at the home base. Similar methods are @{#SPAWN.InitRepeatOnLanding} and @{#SPAWN.InitRepeatOnEngineShutDown}. -- -- 1.3) SPAWN spawning methods -- --------------------------- @@ -20498,7 +20941,20 @@ end -- 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 +-- 1.4) Retrieve alive GROUPs spawned by the SPAWN object +-- ------------------------------------------------------ +-- The SPAWN class administers which GROUPS it has reserved (in stock) or has created during mission execution. +-- 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. +-- +-- 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, @@ -20541,7 +20997,7 @@ SPAWN = { -- 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() ) + local self = BASE:Inherit( self, BASE:New() ) -- #SPAWN self:F( { SpawnTemplatePrefix } ) local TemplateGroup = Group.getByName( SpawnTemplatePrefix ) @@ -20641,6 +21097,7 @@ end -- @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 ... +-- @param #number SpawnHeight (optional) Specifies the **additional** height in meters that can be added to the base height specified at each waypoint in the ME. -- @return #SPAWN -- @usage -- -- NATO helicopters engaging in the battle field. @@ -20648,13 +21105,14 @@ end -- -- 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 } ) +function SPAWN:RandomizeRoute( SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight ) + self:F( { self.SpawnTemplatePrefix, SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight } ) self.SpawnRandomizeRoute = true self.SpawnRandomizeRouteStartPoint = SpawnStartPoint self.SpawnRandomizeRouteEndPoint = SpawnEndPoint self.SpawnRandomizeRouteRadius = SpawnRadius + self.SpawnRandomizeRouteHeight = SpawnHeight for GroupID = 1, self.SpawnMaxGroups do self:_RandomizeRoute( GroupID ) @@ -21073,7 +21531,7 @@ 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 - return self:SpawnFromVec3( HostUnit:GetPointVec3(), OuterRadius, InnerRadius, SpawnIndex ) + return self:SpawnFromVec3( HostUnit:GetVec3(), OuterRadius, InnerRadius, SpawnIndex ) end return nil @@ -21092,7 +21550,7 @@ function SPAWN:SpawnFromStatic( HostStatic, OuterRadius, InnerRadius, SpawnIndex self:F( { self.SpawnTemplatePrefix, HostStatic, OuterRadius, InnerRadius, SpawnIndex } ) if HostStatic and HostStatic:IsAlive() then - return self:SpawnFromVec3( HostStatic:GetPointVec3(), OuterRadius, InnerRadius, SpawnIndex ) + return self:SpawnFromVec3( HostStatic:GetVec3(), OuterRadius, InnerRadius, SpawnIndex ) end return nil @@ -21164,19 +21622,24 @@ function SPAWN:SpawnGroupName( SpawnIndex ) end ---- Find the first alive group. +--- 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 --- @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 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. -function SPAWN:GetFirstAliveGroup( SpawnCursor ) - self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnCursor } ) +-- @usage +-- -- 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. +-- GroupPlane, Index = SpawnPlanes:GetNextAliveGroup( Index ) +-- end +function SPAWN:GetFirstAliveGroup() + self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) for SpawnIndex = 1, self.SpawnCount do local SpawnGroup = self:GetGroupFromIndex( SpawnIndex ) if SpawnGroup and SpawnGroup:IsAlive() then - SpawnCursor = SpawnIndex - return SpawnGroup, SpawnCursor + return SpawnGroup, SpawnIndex end end @@ -21184,27 +21647,42 @@ function SPAWN:GetFirstAliveGroup( SpawnCursor ) end ---- Find the next alive group. +--- 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 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 } ) +-- @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. +-- @usage +-- -- 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. +-- GroupPlane, Index = SpawnPlanes:GetNextAliveGroup( Index ) +-- end +function SPAWN:GetNextAliveGroup( SpawnIndexStart ) + self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnIndexStart } ) - SpawnCursor = SpawnCursor + 1 - for SpawnIndex = SpawnCursor, self.SpawnCount do + SpawnIndexStart = SpawnIndexStart + 1 + for SpawnIndex = SpawnIndexStart, self.SpawnCount do local SpawnGroup = self:GetGroupFromIndex( SpawnIndex ) if SpawnGroup and SpawnGroup:IsAlive() then - SpawnCursor = SpawnIndex - return SpawnGroup, SpawnCursor + return SpawnGroup, SpawnIndex end end return nil, nil end ---- Find the last alive group during runtime. +--- 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. +-- @usage +-- -- 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. +-- end function SPAWN:GetLastAliveGroup() self:F( { self.SpawnTemplatePrefixself.SpawnAliasPrefix } ) @@ -21405,9 +21883,9 @@ function SPAWN:_GetTemplate( SpawnTemplatePrefix ) error( 'No Template returned for SpawnTemplatePrefix = ' .. SpawnTemplatePrefix ) end - SpawnTemplate.SpawnCoalitionID = self:_GetGroupCoalitionID( SpawnTemplatePrefix ) - SpawnTemplate.SpawnCategoryID = self:_GetGroupCategoryID( SpawnTemplatePrefix ) - SpawnTemplate.SpawnCountryID = self:_GetGroupCountryID( SpawnTemplatePrefix ) + --SpawnTemplate.SpawnCoalitionID = self:_GetGroupCoalitionID( SpawnTemplatePrefix ) + --SpawnTemplate.SpawnCategoryID = self:_GetGroupCategoryID( SpawnTemplatePrefix ) + --SpawnTemplate.SpawnCountryID = self:_GetGroupCountryID( SpawnTemplatePrefix ) self:T3( { SpawnTemplate } ) return SpawnTemplate @@ -21428,12 +21906,12 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) --SpawnTemplate.lateActivation = false SpawnTemplate.lateActivation = false - if SpawnTemplate.SpawnCategoryID == Group.Category.GROUND then + if SpawnTemplate.CategoryID == 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 + if SpawnTemplate.CategoryID == Group.Category.HELICOPTER or SpawnTemplate.CategoryID == Group.Category.AIRPLANE then SpawnTemplate.uncontrolled = false end @@ -21461,11 +21939,19 @@ function SPAWN:_RandomizeRoute( SpawnIndex ) 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 + + -- Manage randomization of altitude for airborne units ... + if SpawnTemplate.CategoryID == Group.Category.AIRPLANE or SpawnTemplate.CategoryID == Group.Category.HELICOPTER then + if SpawnTemplate.route.points[t].alt and self.SpawnRandomizeRouteHeight then + SpawnTemplate.route.points[t].alt = SpawnTemplate.route.points[t].alt + math.random( 1, self.SpawnRandomizeRouteHeight ) + end + else + SpawnTemplate.route.points[t].alt = nil + end + self:T( 'SpawnTemplate.route.points[' .. t .. '].x = ' .. SpawnTemplate.route.points[t].x .. ', SpawnTemplate.route.points[' .. t .. '].y = ' .. SpawnTemplate.route.points[t].y ) end end @@ -21489,6 +21975,9 @@ function SPAWN:_RandomizeTemplate( SpawnIndex ) 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 + self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].x = self.SpawnTemplate.units[1].x + self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].y = self.SpawnTemplate.units[1].y + self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].alt = self.SpawnTemplate.units[1].alt end end @@ -22130,6 +22619,7 @@ ESCORT = { -- @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. +-- @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 -- @usage -- -- Declare a new EscortPlanes object as follows: @@ -22167,12 +22657,18 @@ function ESCORT:New( EscortClient, EscortGroup, EscortName, EscortBriefing ) 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 - ) + + if not EscortBriefing then + 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 + ) + else + EscortGroup:MessageToClient( EscortGroup:GetCategoryName() .. " '" .. EscortName .. "' (" .. EscortGroup:GetCallsign() .. ") " .. EscortBriefing, + 60, EscortClient + ) + end self.FollowDistance = 100 self.CT1 = 0 @@ -22631,13 +23127,13 @@ function ESCORT._HoldPosition( MenuParam ) self.FollowScheduler:Stop() local PointFrom = {} - local GroupPoint = EscortGroup:GetUnit(1):GetPointVec3() + local GroupVec3 = EscortGroup:GetUnit(1):GetVec3() PointFrom = {} - PointFrom.x = GroupPoint.x - PointFrom.y = GroupPoint.z + PointFrom.x = GroupVec3.x + PointFrom.y = GroupVec3.z PointFrom.speed = 250 PointFrom.type = AI.Task.WaypointType.TURNING_POINT - PointFrom.alt = GroupPoint.y + PointFrom.alt = GroupVec3.y PointFrom.alt_type = AI.Task.AltitudeType.BARO local OrbitPoint = OrbitUnit:GetVec2() @@ -22963,16 +23459,16 @@ function ESCORT:_FollowScheduler() self:T( {ClientUnit.UnitName, GroupUnit.UnitName } ) if self.CT1 == 0 and self.GT1 == 0 then - self.CV1 = ClientUnit:GetPointVec3() + self.CV1 = ClientUnit:GetVec3() self:T( { "self.CV1", self.CV1 } ) self.CT1 = timer.getTime() - self.GV1 = GroupUnit:GetPointVec3() + self.GV1 = GroupUnit:GetVec3() self.GT1 = timer.getTime() else local CT1 = self.CT1 local CT2 = timer.getTime() local CV1 = self.CV1 - local CV2 = ClientUnit:GetPointVec3() + local CV2 = ClientUnit:GetVec3() self.CT1 = CT2 self.CV1 = CV2 @@ -22986,7 +23482,7 @@ function ESCORT:_FollowScheduler() local GT1 = self.GT1 local GT2 = timer.getTime() local GV1 = self.GV1 - local GV2 = GroupUnit:GetPointVec3() + local GV2 = GroupUnit:GetVec3() self.GT1 = GT2 self.GV1 = GV2 @@ -23098,11 +23594,11 @@ function ESCORT:_ReportTargetsScheduler() -- 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 + local EscortTargetUnitVec3 = EscortTargetUnit:GetVec3() + local EscortVec3 = self.EscortGroup:GetVec3() + local Distance = ( ( EscortTargetUnitVec3.x - EscortVec3.x )^2 + + ( EscortTargetUnitVec3.y - EscortVec3.y )^2 + + ( EscortTargetUnitVec3.z - EscortVec3.z )^2 ) ^ 0.5 / 1000 self:T( { self.EscortGroup:GetName(), EscortTargetUnit:GetName(), Distance, EscortTarget } ) @@ -23157,11 +23653,11 @@ function ESCORT:_ReportTargetsScheduler() 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 + local EscortTargetUnitVec3 = ClientEscortTargetData.AttackUnit:GetVec3() + local EscortVec3 = self.EscortGroup:GetVec3() + local Distance = ( ( EscortTargetUnitVec3.x - EscortVec3.x )^2 + + ( EscortTargetUnitVec3.y - EscortVec3.y )^2 + + ( EscortTargetUnitVec3.z - EscortVec3.z )^2 ) ^ 0.5 / 1000 self:T( { self.EscortGroup:GetName(), ClientEscortTargetData.AttackUnit:GetName(), Distance, ClientEscortTargetData.AttackUnit } ) @@ -23225,9 +23721,9 @@ function ESCORT:_ReportTargetsScheduler() 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 + local EscortVec3 = self.EscortGroup:GetVec3() + local Distance = ( ( WayPoint.x - EscortVec3.x )^2 + + ( WayPoint.y - EscortVec3.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 @@ -23752,11 +24248,11 @@ function MISSILETRAINER:_AddRange( Client, TrainerWeapon ) if self.DetailsRangeOnOff then local PositionMissile = TrainerWeapon:getPoint() - local PositionTarget = Client:GetPointVec3() + local TargetVec3 = Client:GetVec3() - local Range = ( ( PositionMissile.x - PositionTarget.x )^2 + - ( PositionMissile.y - PositionTarget.y )^2 + - ( PositionMissile.z - PositionTarget.z )^2 + local Range = ( ( PositionMissile.x - TargetVec3.x )^2 + + ( PositionMissile.y - TargetVec3.y )^2 + + ( PositionMissile.z - TargetVec3.z )^2 ) ^ 0.5 / 1000 RangeText = string.format( ", at %4.2fkm", Range ) @@ -23772,11 +24268,11 @@ function MISSILETRAINER:_AddBearing( Client, TrainerWeapon ) if self.DetailsBearingOnOff then local PositionMissile = TrainerWeapon:getPoint() - local PositionTarget = Client:GetPointVec3() + local TargetVec3 = Client:GetVec3() - self:T2( { PositionTarget, PositionMissile }) + self:T2( { TargetVec3, PositionMissile }) - local DirectionVector = { x = PositionMissile.x - PositionTarget.x, y = PositionMissile.y - PositionTarget.y, z = PositionMissile.z - PositionTarget.z } + local DirectionVector = { x = PositionMissile.x - TargetVec3.x, y = PositionMissile.y - TargetVec3.y, z = PositionMissile.z - TargetVec3.z } local DirectionRadians = math.atan2( DirectionVector.z, DirectionVector.x ) --DirectionRadians = DirectionRadians + routines.getNorthCorrection( PositionTarget ) if DirectionRadians < 0 then @@ -23820,11 +24316,11 @@ function MISSILETRAINER:_TrackMissiles() 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 TargetVec3 = Client:GetVec3() - local Distance = ( ( PositionMissile.x - PositionTarget.x )^2 + - ( PositionMissile.y - PositionTarget.y )^2 + - ( PositionMissile.z - PositionTarget.z )^2 + local Distance = ( ( PositionMissile.x - TargetVec3.x )^2 + + ( PositionMissile.y - TargetVec3.y )^2 + + ( PositionMissile.z - TargetVec3.z )^2 ) ^ 0.5 / 1000 if Distance <= self.Distance then @@ -25992,11 +26488,11 @@ function DETECTION_BASE:_DetectionScheduler( SchedulerName ) local DetectionDetectedObjectName = DetectionObject:getName() local DetectionDetectedObjectPositionVec3 = DetectionObject:getPoint() - local DetectionGroupPositionVec3 = DetectionGroup:GetPointVec3() + local DetectionGroupVec3 = DetectionGroup:GetVec3() - local Distance = ( ( DetectionDetectedObjectPositionVec3.x - DetectionGroupPositionVec3.x )^2 + - ( DetectionDetectedObjectPositionVec3.y - DetectionGroupPositionVec3.y )^2 + - ( DetectionDetectedObjectPositionVec3.z - DetectionGroupPositionVec3.z )^2 + local Distance = ( ( DetectionDetectedObjectPositionVec3.x - DetectionGroupVec3.x )^2 + + ( DetectionDetectedObjectPositionVec3.y - DetectionGroupVec3.y )^2 + + ( DetectionDetectedObjectPositionVec3.z - DetectionGroupVec3.z )^2 ) ^ 0.5 / 1000 self:T2( { DetectionGroupName, DetectionDetectedObjectName, Distance } ) @@ -26176,7 +26672,7 @@ function DETECTION_AREAS:ReportFriendliesNearBy( ReportGroupData ) local SphereSearch = { id = world.VolumeType.SPHERE, params = { - point = DetectedZoneUnit:GetPointVec3(), + point = DetectedZoneUnit:GetVec3(), radius = 6000, } @@ -26258,9 +26754,9 @@ function DETECTION_AREAS:NearestFAC( DetectedArea ) for FACUnit, FACUnitData in pairs( FACGroupData:GetUnits() ) do local FACUnit = FACUnitData -- Unit#UNIT if FACUnit:IsActive() then - local Vec3 = FACUnit:GetPointVec3() + local Vec3 = FACUnit:GetVec3() local PointVec3 = POINT_VEC3:NewFromVec3( Vec3 ) - local Distance = PointVec3:Get2DDistance(POINT_VEC3:NewFromVec3( FACUnit:GetPointVec3() ) ) + local Distance = PointVec3:Get2DDistance(POINT_VEC3:NewFromVec3( FACUnit:GetVec3() ) ) if Distance < MinDistance then MinDistance = Distance NearestFAC = FACUnit @@ -27090,7 +27586,7 @@ do -- DETECTION_DISPATCHER local ThreatLevel = Detection:GetTreatLevelA2G( DetectedArea ) - local DetectedAreaVec3 = DetectedZone:GetPointVec3() + local DetectedAreaVec3 = DetectedZone:GetVec3() 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)", @@ -27200,6 +27696,13 @@ function STATEMACHINE:New( options ) 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 } ) @@ -27504,7 +28007,7 @@ function PROCESS:OnStateChange( Fsm, Event, From, To ) end ---- This module contains the TASK_ASSIGN classes. +--- This module contains the PROCESS_ASSIGN classes. -- -- === -- @@ -28226,9 +28729,9 @@ function PROCESS_JTAC:OnJTACMenuSpot( Fsm, Event, From, To, TargetUnit ) TaskJTAC.Spots[TargetUnitName] = TaskJTAC.Spots[TargetUnitName] or {} local DCSFACObject = self.FACUnit:GetDCSObject() - local TargetVec3 = TargetUnit:GetPointVec3() + local TargetVec3 = TargetUnit:GetVec3() - TaskJTAC.Spots[TargetUnitName] = Spot.createInfraRed( self.FACUnit:GetDCSObject(), { x = 0, y = 1, z = 0 }, TargetUnit:GetPointVec3(), math.random( 1000, 9999 ) ) + 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 ) @@ -29129,8 +29632,9 @@ end -- -- 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: +-- 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. @@ -29274,8 +29778,9 @@ end -- -- 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: +-- 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. @@ -29287,7 +29792,7 @@ end -- -- ### Authors: FlightControl - Design and Programming -- --- @module Task_CAS +-- @module Task_A2G do -- TASK_A2G diff --git a/Moose Mission Setup/Moose.lua b/Moose Mission Setup/Moose.lua index f943793d3..f878cae10 100644 --- a/Moose Mission Setup/Moose.lua +++ b/Moose Mission Setup/Moose.lua @@ -1,31 +1,29929 @@ -env.info( '*** MOOSE DYNAMIC INCLUDE START *** ' ) -env.info( 'Moose Generation Timestamp: 20160805_0653' ) - +env.info( '*** MOOSE STATIC INCLUDE START *** ' ) +env.info( 'Moose Generation Timestamp: 20160811_0937' ) local base = _G Include = {} - +Include.Files = {} Include.File = function( IncludeFile ) - if not Include.Files[ IncludeFile ] then - Include.Files[IncludeFile] = IncludeFile - env.info( "Include:" .. IncludeFile .. " from " .. Include.ProgramPath ) - local f = assert( base.loadfile( Include.ProgramPath .. IncludeFile .. ".lua" ) ) - if f == nil then - error ("Could not load MOOSE file " .. IncludeFile .. ".lua" ) +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 - env.info( "Include:" .. IncludeFile .. " loaded from " .. Include.ProgramPath ) - return f() + 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 -Include.ProgramPath = "Scripts/Moose/" -env.info( "Include.ProgramPath = " .. Include.ProgramPath) +routines.utils.toDegree = function(angle) + return angle*180/math.pi +end -Include.Files = {} +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 + +--- 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() + + -- 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 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 = { +-- 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 = 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 = { +-- 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 = 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 = { +-- 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 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 = { + -- 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 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.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 + + + + + + + + + + + + + + + + +--- 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.initiator, Event.IniDCSUnitName, Event.target, Event.TgtDCSUnitName, Event.weapon, 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 + +--- This module contains the MENU classes. +-- +-- There is a small note... When you see a class like MENU_COMMAND_COALITION with COMMAND in italic, it acutally represents it like this: `MENU_COMMAND_COALITION`. +-- +-- === +-- +-- DCS Menus can be managed using the MENU classes. +-- The advantage of using MENU classes is that it hides the complexity of dealing with menu management in more advanced scanerios where you need to +-- set menus and later remove them, and later set them again. You'll find while using use normal DCS scripting functions, that setting and removing +-- menus is not a easy feat if you have complex menu hierarchies defined. +-- Using the MOOSE menu classes, the removal and refreshing of menus are nicely being handled within these classes, and becomes much more easy. +-- On top, MOOSE implements **variable parameter** passing for command menus. +-- +-- There are basically two different MENU class types that you need to use: +-- +-- ### 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". +-- +-- ### 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". +-- +-- === +-- +-- The above menus classes **are derived** from 2 main **abstract** classes defined within the MOOSE framework (so don't use these): +-- +-- 1) MENU_ BASE abstract base classes (don't use them) +-- ==================================================== +-- The underlying base menu classes are **NOT** to be used within your missions. +-- 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} +-- -------------------------------------------------- +-- 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} +-- ---------------------------------------------------------- +-- The @{#MENU_COMMAND_BASE} class defines the main MENU class where other MENU COMMAND_ classes are derived from, in order to set commands. +-- +-- === +-- +-- **The next menus define the MENU classes that you can use within your missions.** +-- +-- 2) MENU MISSION classes +-- ====================== +-- The underlying classes manage the menus for a complete mission file. +-- +-- 2.1) @{Menu#MENU_MISSION} class, extends @{Menu#MENU_BASE} +-- --------------------------------------------------------- +-- The @{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} +-- ------------------------------------------------------------------------- +-- The @{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}. +-- +-- === +-- +-- 3) MENU COALITION classes +-- ========================= +-- The underlying classes manage the menus for whole coalitions. +-- +-- 3.1) @{Menu#MENU_COALITION} class, extends @{Menu#MENU_BASE} +-- ------------------------------------------------------------ +-- The @{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} +-- ---------------------------------------------------------------------------- +-- The @{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}. +-- +-- === +-- +-- 4) MENU GROUP classes +-- ===================== +-- 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} +-- -------------------------------------------------------- +-- The @{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} +-- ------------------------------------------------------------------------ +-- The @{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}. +-- +-- === +-- +-- 5) MENU CLIENT classes +-- ====================== +-- The underlying classes manage the menus for units with skill level client or player. +-- +-- 5.1) @{Menu#MENU_CLIENT} class, extends @{Menu#MENU_BASE} +-- --------------------------------------------------------- +-- The @{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} +-- ------------------------------------------------------------------------- +-- The @{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}. +-- +-- === +-- +-- ### Contributions: - +-- ### Authors: FlightControl : Design & Programming +-- +-- @module Menu + + +do -- MENU_BASE + + --- The MENU_BASE class + -- @type MENU_BASE + -- @extends Base#BASE + MENU_BASE = { + ClassName = "MENU_BASE", + MenuPath = nil, + MenuText = "", + MenuParentPath = nil + } + + --- Consructor + function MENU_BASE:New( MenuText, ParentMenu ) + + local MenuParentPath = {} + if ParentMenu ~= nil then + MenuParentPath = ParentMenu.MenuPath + end + + local self = BASE:Inherit( self, BASE:New() ) + + self.MenuPath = nil + self.MenuText = MenuText + self.MenuParentPath = MenuParentPath + + return self + end + +end + +do -- MENU_COMMAND_BASE + + --- The MENU_COMMAND_BASE class + -- @type MENU_COMMAND_BASE + -- @field #function MenuCallHandler + -- @extends Menu#MENU_BASE + MENU_COMMAND_BASE = { + ClassName = "MENU_COMMAND_BASE", + CommandMenuFunction = nil, + CommandMenuArgument = nil, + MenuCallHandler = nil, + } + + --- Constructor + function MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, CommandMenuArguments ) + + local self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) ) + + self.CommandMenuFunction = CommandMenuFunction + self.MenuCallHandler = function( CommandMenuArguments ) + self.CommandMenuFunction( unpack( CommandMenuArguments ) ) + end + + return self + end + +end + + +do -- MENU_MISSION + + --- The MENU_MISSION class + -- @type MENU_MISSION + -- @extends Menu#MENU_BASE + MENU_MISSION = { + ClassName = "MENU_MISSION" + } + + --- MENU_MISSION constructor. Creates a new MENU_MISSION object and creates the menu for a complete mission file. + -- @param #MENU_MISSION self + -- @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_MISSION self + function MENU_MISSION:New( MenuText, ParentMenu ) + + local self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) ) + + self:F( { MenuText, ParentMenu } ) + + self.MenuText = MenuText + self.ParentMenu = ParentMenu + + self.Menus = {} + + self:T( { MenuText } ) + + self.MenuPath = missionCommands.addSubMenu( MenuText, self.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_MISSION. Note that the main menu is kept! + -- @param #MENU_MISSION self + -- @return #MENU_MISSION self + function MENU_MISSION:RemoveSubMenus() + self:F( self.MenuPath ) + + for MenuID, Menu in pairs( self.Menus ) do + Menu:Remove() + end + + end + + --- Removes the main menu and the sub menus recursively of this MENU_MISSION. + -- @param #MENU_MISSION self + -- @return #nil + function MENU_MISSION:Remove() + self:F( self.MenuPath ) + + self:RemoveSubMenus() + missionCommands.removeItem( self.MenuPath ) + if self.ParentMenu then + self.ParentMenu.Menus[self.MenuPath] = nil + end + + return nil + end + +end + +do -- MENU_MISSION_COMMAND + + --- The MENU_MISSION_COMMAND class + -- @type MENU_MISSION_COMMAND + -- @extends Menu#MENU_COMMAND_BASE + MENU_MISSION_COMMAND = { + ClassName = "MENU_MISSION_COMMAND" + } + + --- MENU_MISSION constructor. Creates a new radio command item for a complete mission file, which can invoke a function with parameters. + -- @param #MENU_MISSION_COMMAND self + -- @param #string MenuText The text for the menu. + -- @param Menu#MENU_MISSION ParentMenu The parent menu. + -- @param CommandMenuFunction A function that is called when the menu key is pressed. + -- @param CommandMenuArgument An argument for the function. There can only be ONE argument given. So multiple arguments must be wrapped into a table. See the below example how to do this. + -- @return #MENU_MISSION_COMMAND self + function MENU_MISSION_COMMAND:New( MenuText, ParentMenu, CommandMenuFunction, ... ) + + local self = BASE:Inherit( self, MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, arg ) ) + + self.MenuText = MenuText + self.ParentMenu = ParentMenu + + self:T( { MenuText, CommandMenuFunction, arg } ) + + + self.MenuPath = missionCommands.addCommand( MenuText, self.MenuParentPath, self.MenuCallHandler, arg ) + + ParentMenu.Menus[self.MenuPath] = self + + return self + end + + --- Removes a radio command item for a coalition + -- @param #MENU_MISSION_COMMAND self + -- @return #nil + function MENU_MISSION_COMMAND:Remove() + self:F( self.MenuPath ) + + missionCommands.removeItem( self.MenuPath ) + if self.ParentMenu then + self.ParentMenu.Menus[self.MenuPath] = nil + end + return nil + end + +end + + + +do -- MENU_COALITION + + --- The MENU_COALITION class + -- @type MENU_COALITION + -- @extends Menu#MENU_BASE + -- @usage + -- -- This demo creates a menu structure for the planes within the red coalition. + -- -- To test, join the planes, then look at the other radio menus (Option F10). + -- -- Then switch planes and check if the menu is still there. + -- + -- local Plane1 = CLIENT:FindByName( "Plane 1" ) + -- local Plane2 = CLIENT:FindByName( "Plane 2" ) + -- + -- + -- -- This would create a menu for the red coalition under the main DCS "Others" menu. + -- local MenuCoalitionRed = MENU_COALITION:New( coalition.side.RED, "Manage Menus" ) + -- + -- + -- local function ShowStatus( StatusText, Coalition ) + -- + -- MESSAGE:New( Coalition, 15 ):ToRed() + -- Plane1:Message( StatusText, 15 ) + -- Plane2:Message( StatusText, 15 ) + -- end + -- + -- local MenuStatus -- Menu#MENU_COALITION + -- local MenuStatusShow -- Menu#MENU_COALITION_COMMAND + -- + -- local function RemoveStatusMenu() + -- MenuStatus:Remove() + -- end + -- + -- local function AddStatusMenu() + -- + -- -- This would create a menu for the red coalition under the MenuCoalitionRed menu object. + -- MenuStatus = MENU_COALITION:New( coalition.side.RED, "Status for Planes" ) + -- MenuStatusShow = MENU_COALITION_COMMAND:New( coalition.side.RED, "Show Status", MenuStatus, ShowStatus, "Status of planes is ok!", "Message to Red Coalition" ) + -- end + -- + -- local MenuAdd = MENU_COALITION_COMMAND:New( coalition.side.RED, "Add Status Menu", MenuCoalitionRed, AddStatusMenu ) + -- local MenuRemove = MENU_COALITION_COMMAND:New( coalition.side.RED, "Remove Status Menu", MenuCoalitionRed, RemoveStatusMenu ) + MENU_COALITION = { + ClassName = "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 #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 + function MENU_COALITION:New( Coalition, MenuText, ParentMenu ) + + local self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) ) + + self:F( { Coalition, MenuText, ParentMenu } ) + + self.Coalition = Coalition + self.MenuText = MenuText + self.ParentMenu = ParentMenu + + self.Menus = {} + + self:T( { MenuText } ) + + self.MenuPath = missionCommands.addSubMenuForCoalition( Coalition, MenuText, self.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. Note that the main menu is kept! + -- @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 main menu and the sub menus recursively of this MENU_COALITION. + -- @param #MENU_COALITION self + -- @return #nil + function MENU_COALITION:Remove() + self:F( self.MenuPath ) + + self:RemoveSubMenus() + missionCommands.removeItemForCoalition( self.Coalition, self.MenuPath ) + if self.ParentMenu then + self.ParentMenu.Menus[self.MenuPath] = nil + end + + return nil + end + +end + +do -- MENU_COALITION_COMMAND + + --- The MENU_COALITION_COMMAND class + -- @type MENU_COALITION_COMMAND + -- @extends Menu#MENU_COMMAND_BASE + MENU_COALITION_COMMAND = { + ClassName = "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 #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. + -- @param CommandMenuArgument An argument for the function. There can only be ONE argument given. So multiple arguments must be wrapped into a table. See the below example how to do this. + -- @return #MENU_COALITION_COMMAND self + function MENU_COALITION_COMMAND:New( Coalition, MenuText, ParentMenu, CommandMenuFunction, ... ) + + local self = BASE:Inherit( self, MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, arg ) ) + + self.MenuCoalition = Coalition + self.MenuText = MenuText + self.ParentMenu = ParentMenu + + self:T( { MenuText, CommandMenuFunction, arg } ) + + + self.MenuPath = missionCommands.addCommandForCoalition( self.MenuCoalition, MenuText, self.MenuParentPath, self.MenuCallHandler, arg ) + + ParentMenu.Menus[self.MenuPath] = self + + return self + end + + --- Removes a radio command item for a coalition + -- @param #MENU_COALITION_COMMAND self + -- @return #nil + function MENU_COALITION_COMMAND:Remove() + self:F( self.MenuPath ) + + missionCommands.removeItemForCoalition( self.MenuCoalition, self.MenuPath ) + if self.ParentMenu then + self.ParentMenu.Menus[self.MenuPath] = nil + end + return nil + end + +end + +do -- MENU_CLIENT + + -- 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 = {} + + --- MENU_COALITION constructor. Creates a new radio command item for a coalition, which can invoke a function with parameters. + -- @type MENU_CLIENT + -- @extends Menu#MENU_BASE + -- @usage + -- -- This demo creates a menu structure for the two clients of planes. + -- -- Each client will receive a different menu structure. + -- -- To test, join the planes, then look at the other radio menus (Option F10). + -- -- Then switch planes and check if the menu is still there. + -- -- And play with the Add and Remove menu options. + -- + -- -- Note that in multi player, this will only work after the DCS clients bug is solved. + -- + -- local function ShowStatus( PlaneClient, StatusText, Coalition ) + -- + -- MESSAGE:New( Coalition, 15 ):ToRed() + -- PlaneClient:Message( StatusText, 15 ) + -- end + -- + -- local MenuStatus = {} + -- + -- local function RemoveStatusMenu( MenuClient ) + -- local MenuClientName = MenuClient:GetName() + -- MenuStatus[MenuClientName]:Remove() + -- end + -- + -- --- @param 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. + -- MenuStatus[MenuClientName] = MENU_CLIENT:New( MenuClient, "Status for Planes" ) + -- MENU_CLIENT_COMMAND:New( MenuClient, "Show Status", MenuStatus[MenuClientName], ShowStatus, MenuClient, "Status of planes is ok!", "Message to Red Coalition" ) + -- end + -- + -- SCHEDULER:New( nil, + -- function() + -- local PlaneClient = CLIENT:FindByName( "Plane 1" ) + -- if PlaneClient and PlaneClient:IsAlive() then + -- local MenuManage = MENU_CLIENT:New( PlaneClient, "Manage Menus" ) + -- MENU_CLIENT_COMMAND:New( PlaneClient, "Add Status Menu Plane 1", MenuManage, AddStatusMenu, PlaneClient ) + -- MENU_CLIENT_COMMAND:New( PlaneClient, "Remove Status Menu Plane 1", MenuManage, RemoveStatusMenu, PlaneClient ) + -- end + -- end, {}, 10, 10 ) + -- + -- SCHEDULER:New( nil, + -- function() + -- local PlaneClient = CLIENT:FindByName( "Plane 2" ) + -- if PlaneClient and PlaneClient:IsAlive() then + -- local MenuManage = MENU_CLIENT:New( PlaneClient, "Manage Menus" ) + -- MENU_CLIENT_COMMAND:New( PlaneClient, "Add Status Menu Plane 2", MenuManage, AddStatusMenu, PlaneClient ) + -- MENU_CLIENT_COMMAND:New( PlaneClient, "Remove Status Menu Plane 2", MenuManage, RemoveStatusMenu, PlaneClient ) + -- end + -- end, {}, 10, 10 ) + MENU_CLIENT = { + ClassName = "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 #string MenuText The text for the menu. + -- @param #table ParentMenu The parent menu. + -- @return #MENU_CLIENT self + function MENU_CLIENT:New( Client, 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( { Client, MenuText, ParentMenu } ) + + self.MenuClient = Client + self.MenuClientGroupID = Client: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( { Client: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( { Client: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 #nil + 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_COMMAND + MENU_CLIENT_COMMAND = { + ClassName = "MENU_CLIENT_COMMAND" + } + + --- 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 #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. + -- @param CommandMenuArgument An argument for the function. + -- @return Menu#MENU_CLIENT_COMMAND self + function MENU_CLIENT_COMMAND:New( MenuClient, MenuText, ParentMenu, CommandMenuFunction, ... ) + + -- Arrange meta tables + + local MenuParentPath = {} + if ParentMenu ~= nil then + MenuParentPath = ParentMenu.MenuPath + end + + local self = BASE:Inherit( self, MENU_COMMAND_BASE:New( MenuText, MenuParentPath, CommandMenuFunction, arg ) ) -- Menu#MENU_CLIENT_COMMAND + + 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, arg } ) + + 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, self.MenuCallHandler, arg ) + MenuPath[MenuPathID] = self.MenuPath + + ParentMenu.Menus[self.MenuPath] = self + + return self + end + + --- Removes a menu structure for a client. + -- @param #MENU_CLIENT_COMMAND self + -- @return #nil + 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 groups. + -- Menus don't dissapear when groups for players 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_BASE + -- @usage + -- -- This demo creates a menu structure for the two groups of planes. + -- -- Each group will receive a different menu structure. + -- -- To test, join the planes, then look at the other radio menus (Option F10). + -- -- Then switch planes and check if the menu is still there. + -- -- And play with the Add and Remove menu options. + -- + -- -- Note that in multi player, this will only work after the DCS groups bug is solved. + -- + -- local function ShowStatus( PlaneGroup, StatusText, Coalition ) + -- + -- MESSAGE:New( Coalition, 15 ):ToRed() + -- PlaneGroup:Message( StatusText, 15 ) + -- end + -- + -- local MenuStatus = {} + -- + -- local function RemoveStatusMenu( MenuGroup ) + -- local MenuGroupName = MenuGroup:GetName() + -- MenuStatus[MenuGroupName]:Remove() + -- end + -- + -- --- @param 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. + -- MenuStatus[MenuGroupName] = MENU_GROUP:New( MenuGroup, "Status for Planes" ) + -- MENU_GROUP_COMMAND:New( MenuGroup, "Show Status", MenuStatus[MenuGroupName], ShowStatus, MenuGroup, "Status of planes is ok!", "Message to Red Coalition" ) + -- end + -- + -- SCHEDULER:New( nil, + -- function() + -- local PlaneGroup = GROUP:FindByName( "Plane 1" ) + -- if PlaneGroup and PlaneGroup:IsAlive() then + -- local MenuManage = MENU_GROUP:New( PlaneGroup, "Manage Menus" ) + -- MENU_GROUP_COMMAND:New( PlaneGroup, "Add Status Menu Plane 1", MenuManage, AddStatusMenu, PlaneGroup ) + -- MENU_GROUP_COMMAND:New( PlaneGroup, "Remove Status Menu Plane 1", MenuManage, RemoveStatusMenu, PlaneGroup ) + -- end + -- end, {}, 10, 10 ) + -- + -- SCHEDULER:New( nil, + -- function() + -- local PlaneGroup = GROUP:FindByName( "Plane 2" ) + -- if PlaneGroup and PlaneGroup:IsAlive() then + -- local MenuManage = MENU_GROUP:New( PlaneGroup, "Manage Menus" ) + -- MENU_GROUP_COMMAND:New( PlaneGroup, "Add Status Menu Plane 2", MenuManage, AddStatusMenu, PlaneGroup ) + -- MENU_GROUP_COMMAND:New( PlaneGroup, "Remove Status Menu Plane 2", MenuManage, RemoveStatusMenu, PlaneGroup ) + -- end + -- end, {}, 10, 10 ) + -- + MENU_GROUP = { + ClassName = "MENU_GROUP" + } + + --- 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 #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 + + 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 main menu and sub menus recursively of this MENU_GROUP. + -- @param #MENU_GROUP self + -- @return #nil + 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_BASE + 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, ... ) + + 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] = {} + 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 + + return self + end + + --- Removes a menu structure for a group. + -- @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] + + + 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 + +--- 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}. +-- 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 Vec3 The point to test. +-- @return #boolean true if the point is within the zone. +function ZONE_BASE:IsPointVec3InZone( Vec3 ) + self:F2( Vec3 ) + + local InZone = self:IsPointVec2InZone( { x = Vec3.x, y = Vec3.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 @{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. +function ZONE_RADIUS:GetVec3( Height ) + self:F2( { self.ZoneName, Height } ) + + Height = Height or 0 + local Vec2 = self:GetVec2() + + local Vec3 = { x = Vec2.x, y = land.getHeight( self:GetVec2() ) + Height, z = Vec2.y } + + self:T2( { Vec3 } ) + + return Vec3 +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 Vec3 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 @{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. +function ZONE_UNIT:GetVec3( Height ) + self:F2( self.ZoneName ) + + Height = Height or 0 + + local Vec2 = self:GetVec2() + + local Vec3 = { x = Vec2.x, y = land.getHeight( self:GetVec2() ) + Height, z = Vec2.y } + + self:T2( { Vec3 } ) + + return Vec3 +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 ) + + 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} +-- =================================================== +-- 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:F( SpawnTemplate.name ) + + self:T( { SpawnTemplate.SpawnCountryID, SpawnTemplate.SpawnCategoryID } ) + + -- Copy the spawn variables of the template in temporary storage, nullify, and restore the spawn variables. + local SpawnCoalitionID = SpawnTemplate.CoalitionID + local SpawnCountryID = SpawnTemplate.CountryID + local SpawnCategoryID = SpawnTemplate.CategoryID + + -- Nullify + SpawnTemplate.CoalitionID = nil + SpawnTemplate.CountryID = nil + SpawnTemplate.CategoryID = nil + + self:_RegisterTemplate( SpawnTemplate, SpawnCoalitionID, SpawnCategoryID, SpawnCountryID ) + + self:T3( SpawnTemplate ) + coalition.addGroup( SpawnCountryID, SpawnCategoryID, SpawnTemplate ) + + -- Restore + SpawnTemplate.CoalitionID = SpawnCoalitionID + SpawnTemplate.CountryID = SpawnCountryID + SpawnTemplate.CategoryID = 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 + + GroupTemplate.CategoryID = CategoryID + GroupTemplate.CoalitionID = CoalitionID + GroupTemplate.CountryID = CountryID + + 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 + + UnitTemplate.name = env.getValueDictByKey(UnitTemplate.name) + + self.Templates.Units[UnitTemplate.name] = {} + self.Templates.Units[UnitTemplate.name].UnitName = UnitTemplate.name + self.Templates.Units[UnitTemplate.name].Template = UnitTemplate + self.Templates.Units[UnitTemplate.name].GroupName = GroupTemplateName + self.Templates.Units[UnitTemplate.name].GroupTemplate = GroupTemplate + self.Templates.Units[UnitTemplate.name].GroupId = GroupTemplate.groupId + self.Templates.Units[UnitTemplate.name].CategoryID = CategoryID + self.Templates.Units[UnitTemplate.name].CoalitionID = CoalitionID + self.Templates.Units[UnitTemplate.name].CountryID = CountryID + + if UnitTemplate.skill and (UnitTemplate.skill == "Client" or UnitTemplate.skill == "Player") then + self.Templates.ClientsByName[UnitTemplate.name] = UnitTemplate + self.Templates.ClientsByName[UnitTemplate.name].CategoryID = CategoryID + self.Templates.ClientsByName[UnitTemplate.name].CoalitionID = CoalitionID + self.Templates.ClientsByName[UnitTemplate.name].CountryID = CountryID + self.Templates.ClientsByID[UnitTemplate.unitId] = UnitTemplate + end + + TraceTable[#TraceTable+1] = self.Templates.Units[UnitTemplate.name].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:GetGroupNameFromUnitName( UnitName ) + return self.Templates.Units[UnitName].GroupName +end + +function DATABASE:GetGroupTemplateFromUnitName( UnitName ) + return self.Templates.Units[UnitName].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. +-- +-- ==== +-- +-- ### Contributions: +-- +-- * Mechanist : Concept & Testing +-- +-- ### Authors: +-- +-- * FlightControl : Design & Programming +-- +-- @module Set + + +--- 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:F( ObjectName ) + + local t = self.Set[ObjectName] + + self:E( { ObjectName, t } ) + + 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:F3( { Event } ) + + if Event.IniDCSUnit then + local ObjectName, Object = self:FindInDatabase( Event ) + 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 ) + 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 #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 #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.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 +function POINT_VEC3:NewFromVec3( Vec3 ) + + return self:New( Vec3.x, Vec3.y, Vec3.z ) +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 { 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. +function POINT_VEC3:GetVec2() + return { x = self.x, y = self.z } +end + + +--- Return the x coordinate of the POINT_VEC3. +-- @param #POINT_VEC3 self +-- @return #number The x coodinate. +function POINT_VEC3:GetX() + return self.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.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.z +end + +--- Set the x coordinate of the POINT_VEC3. +-- @param #number x The x coordinate. +function POINT_VEC3:SetX( x ) + self.x = x +end + +--- Set the y coordinate of the POINT_VEC3. +-- @param #number y The y coordinate. +function POINT_VEC3:SetY( y ) + self.y = y +end + +--- Set the z coordinate of the POINT_VEC3. +-- @param #number z The z coordinate. +function POINT_VEC3:SetZ( z ) + self.z = z +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 +function POINT_VEC3:GetRandomVec2InRadius( OuterRadius, InnerRadius ) + self:F2( { OuterRadius, InnerRadius } ) + + local Theta = 2 * math.pi * math.random() + local Radials = math.random() + math.random() + if Radials > 1 then + Radials = 2 - Radials + end + + local RadialMultiplier + if InnerRadius and InnerRadius <= OuterRadius then + RadialMultiplier = ( OuterRadius - InnerRadius ) * Radials + InnerRadius + else + RadialMultiplier = OuterRadius * Radials + end + + local RandomVec2 + if OuterRadius > 0 then + RandomVec2 = { x = math.cos( Theta ) * RadialMultiplier + self:GetX(), y = math.sin( Theta ) * RadialMultiplier + self:GetZ() } + else + RandomVec2 = { x = self:GetX(), y = self:GetZ() } + end + + return RandomVec2 +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 +-- @return #POINT_VEC2 +function POINT_VEC3:GetRandomPointVec2InRadius( OuterRadius, InnerRadius ) + self:F2( { OuterRadius, InnerRadius } ) + + return POINT_VEC2:NewFromVec2( self:GetRandomVec2InRadius( OuterRadius, InnerRadius ) ) +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 +function POINT_VEC3:GetRandomVec3InRadius( OuterRadius, InnerRadius ) + + local RandomVec2 = self:GetRandomVec2InRadius( OuterRadius, InnerRadius ) + local y = self:GetY() + math.random( InnerRadius, OuterRadius ) + local RandomVec3 = { x = RandomVec2.x, y = y, z = RandomVec2.z } + + return RandomVec3 +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 +-- @return #POINT_VEC3 +function POINT_VEC3:GetRandomPointVec3InRadius( OuterRadius, InnerRadius ) + + return POINT_VEC3:NewFromVec3( self:GetRandomVec3InRadius( OuterRadius, InnerRadius ) ) +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. +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 POINT_VEC3. +-- @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 POINT_VEC3. +-- @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:GetVec3() ) + 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 POINT_VEC3. +-- @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:GetX() + RoutePoint.y = self:GetZ() + RoutePoint.alt = self:GetY() + 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 + +--- Build an ground type route point. +-- @param #POINT_VEC3 self +-- @param 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 ) + self:F2( { Formation, Speed } ) + + local RoutePoint = {} + RoutePoint.x = self:GetX() + RoutePoint.y = self:GetZ() + + RoutePoint.action = Formation or "" + + + 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 } ) + trigger.action.smoke( self:GetVec3(), 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 } ) + trigger.action.signalFlare( self:GetVec3(), 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 +-- @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", + } + +--- 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 } ) + + LandHeightAdd = LandHeightAdd or 0 + LandHeight = LandHeight + LandHeightAdd + + local self = BASE:Inherit( self, POINT_VEC3:New( x, LandHeight, y ) ) + + 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 +function POINT_VEC2:NewFromVec2( Vec2, LandHeightAdd ) + + local LandHeight = land.getHeight( Vec2 ) + + 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 } ) + + 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 +function POINT_VEC2:NewFromVec3( Vec3 ) + + local self = BASE:Inherit( self, BASE:New() ) + local Vec2 = { x = Vec3.x, y = Vec3.z } + + 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 } ) + + return self +end + +--- Return the x coordinate of the POINT_VEC2. +-- @param #POINT_VEC2 self +-- @return #number The x coodinate. +function POINT_VEC2:GetX() + return self.x +end + +--- Return the y coordinate of the POINT_VEC2. +-- @param #POINT_VEC2 self +-- @return #number The y coodinate. +function POINT_VEC2:GetY() + return self.z +end + +--- Return the altitude of the land at the POINT_VEC2. +-- @param #POINT_VEC2 self +-- @return #number The land altitude. +function POINT_VEC2:GetAlt() + return land.getHeight( { x = self.x, y = self.z } ) +end + +--- Set the x coordinate of the POINT_VEC2. +-- @param #number x The x coordinate. +function POINT_VEC2:SetX( x ) + self.x = x +end + +--- Set the y coordinate of the POINT_VEC2. +-- @param #number y The y coordinate. +function POINT_VEC2:SetY( y ) + self.z = y +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. +function POINT_VEC2:DistanceFromPointVec2( PointVec2Reference ) + self:F2( PointVec2Reference ) + + local Distance = ( ( PointVec2Reference:GetX() - self:GetX() ) ^ 2 + ( PointVec2Reference:GetY() - self:GetY() ) ^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:GetX() ) ^ 2 + ( Vec2Reference.y - self:GetY() ) ^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 + +--- 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. +-- @return #POINT_VEC2 The new calculated POINT_VEC2. +function POINT_VEC2:Translate( Distance, Angle ) + local SX = self:GetX() + local SY = self:GetY() + local Radians = Angle / 180 * math.pi + local TX = Distance * math.cos( Radians ) + SX + local TY = Distance * math.sin( Radians ) + SY + + return POINT_VEC2:New( TX, TY ) +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" ) -BASE:TraceOnOff( true ) +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 = MENU_MISSION:New( 'Scoring' ) + self.AllScoresMenu = MENU_MISSION_COMMAND: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 + +--- 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 + + + + + + + +--- 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 methods (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 represents the GROUP Template (definition). +-- +-- 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. +-- 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, 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}. +-- +-- 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}. +-- +-- 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) Retrieve alive GROUPs spawned by the SPAWN object +-- ------------------------------------------------------ +-- The SPAWN class administers which GROUPS it has reserved (in stock) or has created during mission execution. +-- 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. +-- +-- 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. +-- 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() ) -- #SPAWN + 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 ... +-- @param #number SpawnHeight (optional) Specifies the **additional** height in meters that can be added to the base height specified at each waypoint in the ME. +-- @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, SpawnHeight ) + self:F( { self.SpawnTemplatePrefix, SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight } ) + + self.SpawnRandomizeRoute = true + self.SpawnRandomizeRouteStartPoint = SpawnStartPoint + self.SpawnRandomizeRouteEndPoint = SpawnEndPoint + self.SpawnRandomizeRouteRadius = SpawnRadius + self.SpawnRandomizeRouteHeight = SpawnHeight + + 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 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. +-- 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. +-- @return #nil Nothing was spawned. +function SPAWN:SpawnFromVec3( Vec3, OuterRadius, InnerRadius, SpawnIndex ) + self:F( { self.SpawnTemplatePrefix, Vec3, OuterRadius, InnerRadius, SpawnIndex } ) + + local PointVec3 = POINT_VEC3:NewFromVec3( Vec3 ) + self:T2(PointVec3) + + if SpawnIndex then + else + SpawnIndex = self.SpawnIndex + 1 + end + + if self:_GetSpawnIndex( SpawnIndex ) then + + local SpawnTemplate = self.SpawnGroups[self.SpawnIndex].SpawnTemplate + + if SpawnTemplate then + + self:T( { "Current point of ", self.SpawnTemplatePrefix, Vec3 } ) + + 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 ) + + return self:SpawnWithIndex( self.SpawnIndex ) + end + end + + return nil +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. +-- 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. +-- @return #nil Nothing was spawned. +function SPAWN:SpawnFromVec2( Vec2, OuterRadius, InnerRadius, SpawnIndex ) + self:F( { self.SpawnTemplatePrefix, Vec2, OuterRadius, InnerRadius, SpawnIndex } ) + + local PointVec2 = POINT_VEC2:NewFromVec2( Vec2 ) + return self:SpawnFromVec3( PointVec2:GetVec3(), OuterRadius, InnerRadius, 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. +-- 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. +-- @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 + return self:SpawnFromVec3( HostUnit:GetVec3(), OuterRadius, InnerRadius, 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). +-- 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. +-- @return #nil Nothing was spawned. +function SPAWN:SpawnFromStatic( HostStatic, OuterRadius, InnerRadius, SpawnIndex ) + self:F( { self.SpawnTemplatePrefix, HostStatic, OuterRadius, InnerRadius, SpawnIndex } ) + + if HostStatic and HostStatic:IsAlive() then + return self:SpawnFromVec3( HostStatic:GetVec3(), OuterRadius, InnerRadius, 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. +-- @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 ZoneRandomize then + return self:SpawnFromVec2( Zone:GetVec2(), Zone:GetRadius(), 0, SpawnIndex ) + else + return self:SpawnFromVec2( Zone:GetVec2(), 0, 0, SpawnIndex ) + 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 + +--- 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 #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. +-- local GroupPlane, Index = SpawnPlanes:GetFirstAliveGroup() +-- while GroupPlane ~= nil do +-- -- Do actions with the GroupPlane object. +-- GroupPlane, Index = SpawnPlanes:GetNextAliveGroup( Index ) +-- end +function SPAWN:GetFirstAliveGroup() + self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) + + for SpawnIndex = 1, self.SpawnCount do + local SpawnGroup = self:GetGroupFromIndex( SpawnIndex ) + if SpawnGroup and SpawnGroup:IsAlive() then + return SpawnGroup, SpawnIndex + end + end + + return nil, nil +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. +-- @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. +-- @usage +-- -- 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. +-- GroupPlane, Index = SpawnPlanes:GetNextAliveGroup( Index ) +-- end +function SPAWN:GetNextAliveGroup( SpawnIndexStart ) + self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnIndexStart } ) + + SpawnIndexStart = SpawnIndexStart + 1 + for SpawnIndex = SpawnIndexStart, self.SpawnCount do + local SpawnGroup = self:GetGroupFromIndex( SpawnIndex ) + if SpawnGroup and SpawnGroup:IsAlive() then + return SpawnGroup, SpawnIndex + end + end + + 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. +-- @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. +-- @usage +-- -- 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. +-- end +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.CategoryID == Group.Category.GROUND then + self:T3( "For ground units, visible needs to be false..." ) + SpawnTemplate.visible = false + end + + if SpawnTemplate.CategoryID == Group.Category.HELICOPTER or SpawnTemplate.CategoryID == 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 ) + + -- Manage randomization of altitude for airborne units ... + if SpawnTemplate.CategoryID == Group.Category.AIRPLANE or SpawnTemplate.CategoryID == Group.Category.HELICOPTER then + if SpawnTemplate.route.points[t].alt and self.SpawnRandomizeRouteHeight then + SpawnTemplate.route.points[t].alt = SpawnTemplate.route.points[t].alt + math.random( 1, self.SpawnRandomizeRouteHeight ) + end + else + SpawnTemplate.route.points[t].alt = nil + end + + 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 + self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].x = self.SpawnTemplate.units[1].x + self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].y = self.SpawnTemplate.units[1].y + self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].alt = self.SpawnTemplate.units[1].alt + 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. +-- @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 +-- @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() + + if not EscortBriefing then + 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 + ) + else + EscortGroup:MessageToClient( EscortGroup:GetCategoryName() .. " '" .. EscortName .. "' (" .. EscortGroup:GetCallsign() .. ") " .. EscortBriefing, + 60, EscortClient + ) + end + + 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 GroupVec3 = EscortGroup:GetUnit(1):GetVec3() + PointFrom = {} + PointFrom.x = GroupVec3.x + PointFrom.y = GroupVec3.z + PointFrom.speed = 250 + PointFrom.type = AI.Task.WaypointType.TURNING_POINT + PointFrom.alt = GroupVec3.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:GetVec3() + self:T( { "self.CV1", self.CV1 } ) + self.CT1 = timer.getTime() + self.GV1 = GroupUnit:GetVec3() + self.GT1 = timer.getTime() + else + local CT1 = self.CT1 + local CT2 = timer.getTime() + local CV1 = self.CV1 + local CV2 = ClientUnit:GetVec3() + 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:GetVec3() + 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 EscortTargetUnitVec3 = EscortTargetUnit:GetVec3() + local EscortVec3 = self.EscortGroup:GetVec3() + local Distance = ( ( EscortTargetUnitVec3.x - EscortVec3.x )^2 + + ( EscortTargetUnitVec3.y - EscortVec3.y )^2 + + ( EscortTargetUnitVec3.z - EscortVec3.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 EscortTargetUnitVec3 = ClientEscortTargetData.AttackUnit:GetVec3() + local EscortVec3 = self.EscortGroup:GetVec3() + local Distance = ( ( EscortTargetUnitVec3.x - EscortVec3.x )^2 + + ( EscortTargetUnitVec3.y - EscortVec3.y )^2 + + ( EscortTargetUnitVec3.z - EscortVec3.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 EscortVec3 = self.EscortGroup:GetVec3() + local Distance = ( ( WayPoint.x - EscortVec3.x )^2 + + ( WayPoint.y - EscortVec3.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 + if TrainerTargetDCSUnit then + 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 + else + -- TODO: some weapons don't know the target unit... Need to develop a workaround for this. + SCHEDULER:New( TrainerWeapon, TrainerWeapon.destroy, {}, 2 ) + end +end + +function MISSILETRAINER:_AddRange( Client, TrainerWeapon ) + + local RangeText = "" + + if self.DetailsRangeOnOff then + + local PositionMissile = TrainerWeapon:getPoint() + local TargetVec3 = Client:GetVec3() + + local Range = ( ( PositionMissile.x - TargetVec3.x )^2 + + ( PositionMissile.y - TargetVec3.y )^2 + + ( PositionMissile.z - TargetVec3.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 TargetVec3 = Client:GetVec3() + + self:T2( { TargetVec3, PositionMissile }) + + local DirectionVector = { x = PositionMissile.x - TargetVec3.x, y = PositionMissile.y - TargetVec3.y, z = PositionMissile.z - TargetVec3.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 TargetVec3 = Client:GetVec3() + + local Distance = ( ( PositionMissile.x - TargetVec3.x )^2 + + ( PositionMissile.y - TargetVec3.y )^2 + + ( PositionMissile.z - TargetVec3.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. +-- +-- === +-- +-- ### 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} +-- ================================================================== +-- 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 +-- +-- ### Contributions: Dutch Baron - Concept & Testing +-- ### Author: FlightControl - Framework Design & Programming +-- +-- @module AirbasePolice + + + + + +--- @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 DetectionGroupVec3 = DetectionGroup:GetVec3() + + local Distance = ( ( DetectionDetectedObjectPositionVec3.x - DetectionGroupVec3.x )^2 + + ( DetectionDetectedObjectPositionVec3.y - DetectionGroupVec3.y )^2 + + ( DetectionDetectedObjectPositionVec3.z - DetectionGroupVec3.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, 30 ) + + 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:GetVec3(), + 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:GetVec3() + local PointVec3 = POINT_VEC3:NewFromVec3( Vec3 ) + local Distance = PointVec3:Get2DDistance(POINT_VEC3:NewFromVec3( FACUnit:GetVec3() ) ) + if Distance < MinDistance then + MinDistance = Distance + NearestFAC = FACUnit + end + end + end + end + + 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." + 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', "Dummy" ) + + -- 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:GetVec3() + 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: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} +-- ================================================= +-- 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 +--- 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 + + + + +BASE:TraceOnOff( false ) env.info( '*** MOOSE INCLUDE END *** ' ) 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 new file mode 100644 index 0000000000000000000000000000000000000000..3705d115573f98893e6eea5fe9df953b6283a200 GIT binary patch literal 201355 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 KG!C9lufF&*eBB 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

XcAdKQ3()xW-V{hr3E-r6Wuv@Rr%ZTV26B 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ˬ|58A$Rhm5OJv0DzBkoUUN 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 Date: Fri, 12 Aug 2016 07:16:36 +0200 Subject: [PATCH 15/16] Documentation --- .../DCS World - MOOSE - Cargo.pptx | Bin 0 -> 2745952 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 Moose Training/Presentations/DCS World - MOOSE - Cargo.pptx diff --git a/Moose Training/Presentations/DCS World - MOOSE - Cargo.pptx b/Moose Training/Presentations/DCS World - MOOSE - Cargo.pptx new file mode 100644 index 0000000000000000000000000000000000000000..1b6971fc885a95c66cb1d553eea1a39d5f0d84fe GIT binary patch literal 2745952 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

6hG-8)A~ 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

d+;mwtMZ1_gbyt_-+T%R{TW&6xL?C*KBc$ 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

e?nudU|3$cY+1IBe8d=IaV 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 z%0D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

8G40Kn4@spJOGC8!_)9qT@fRp>b| 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�S3D&NCL*>*qXg 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

r? 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 z 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

eZRD&qMBZE{M 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
    9n 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
  1. I5?bXoFENr8|fxn}YML{%wOyH;raw zZZMkVnNoqL+gdlaL2`O%E>;4D!Pr9^ng%vC+c&h?er>V-+HM;cX>ehD1dk<=QVoe? zUF=frP=t|@jlu0a4=I=@QV!BeqaCC@7LvUcEDBxL#&pn7uYoJH3(=A+1=(U%qs|TO zlQu~yo~asw&W+FUC@DrEt$%}n;YcIiJ|x9r4DMp@@}wN<^uP={?FgYv2S__azXD4v zM7eN(1s+3>mq6qhA+HBeU>eFmYr3WQ7rh{c*pCtegA zb14V|7ZVBV1bv)nC|0QL3awiqY`I`LltBg()(pu|a+FhL1M_1wrO&VcLaErr#4;SN zvqu=53N_u-vAPSdOH2<4eVf0M&4tDvtCU1N2pX4zIt*5YVMwmW1YS`G-LaoB-j%1`JbM0m(pOXeW><1T#_1FtM_tr-a8EF)vU#fSO8Bc#{XULmNUJ z5dbeXJLr~-s@Wp-&N!0~pP+NaE&w-xL-pLxqX=Vk9NlYW8nG8Zs{)1-=xglgAZR2u zU>nF;!UK(+3MyfccX$G~`60-#^fJ2!Ml4B{I3&o8AdQ}t7;zhZA4KaZ%oqmNDCmNC zj@pTWaW{t^&KT#6O@(#{5s8X)Qh#+C^Qd7-BmtpYZ`R(=y-3WLdr3DRwljYPt zQ5VuU4VxzBCnIdrCs6D|(b59ZW2e`gP<%N7p^X`RtSl|SE;NEfjZTD$K!zm9ackTL zH5U(|;RUV7z$z$MU{2JLUj$O(QD`ovt%4S!vO<@V8HRa=kf3X}p@zFrQARK@@E)~B zgX8N=85*D)3^dt7%r459@hioCXahy>H`nVdTh>1KV`K3Sfc&1 zSo3kI{*z+e2Z?15&U|LvgOiA+kIx?W;FNI>OdB^2^gHE&ho(I?aq-l}uRJ#6nMY?n z|MIsn!Ie8V*6#YD%=~S!{?*Xbhv&XLY3|D_U;S#%vUi_*>-(49`*GRxAI)C$%H$a< zC(T^>)RdeKnJelAZhEmT`dw4K$RyB5Fl_Cph9KltR#N2e@&aMG-&<}aDP z^4al=mOVUe-UClfdvM}RknjT&r#>)Y%D5*dJn-mKfZ>N811e8`_{o`1OjFpEC357hiqm#h0F4{lenJ7p#17@roCgKKuF$ zZ+-s4n_oTu`j@NU{NmZSzk2DDA3yv#_~-D1U&4|%N;1MXS7es&tT$cmI_f#s|9@_I z|2cU7-&d@EKi~V8W7qBo4CgJLSsUb0cy4#aP4X zDD??>+u@-4og1q=)>WE*Db@a3s##mC`n9nA$AYHsbDMt1ZTvp7;k%65FViZ%NGbgy zx#a!0tdF+kd=Q)ZVPwKb5%C{NVm}Ox_;8c#{k5_0uTT6mvUo#rSA^=SRC@;k5X)1H z!54v1&5bQ8eBowK7$m%r(i;F7N(oP_#oU)s@`%`VN-Ba=I6g6W0Ckva5-c~IBj7F? zSA~BpPj^$8Aqrdy1Ax=5FaW3BSg9d20Jm~{E=;lzn-X9uEy->kLd%IiwoA+D944fq z+%PH;XB|mFJ{m|=-0ZPMtHA~Ylw-Bt8Xe=MnxnOG4_q(PZwI85*3 zh8ooa51oW5UMM9X$wQ4olu@4BfSQ1)K)ox-;11#r7bGX-zeB3`;S~y_7i%1aCuO5E z)Z_u3h8w+7gEy3WXEXd69*?$op5bmobqB)3%Iu{tKOL3 z)>)wJ&Ta3=)tK`%mK>EC^qbkDqpPzLRBSG2HRZRMb6QQfvfGSljjFVIRceDetx2Cy ztCHnb`K1>6Wt418DOkHLJ20y_Aia1~TG6JAlJ&_Y8`4TcGOGd;^M8>g6ts5uMW=qc zKIFrn{onm={afGu^47P%{wR+PNXZUN$qP=--?%yB>tOjuYXkgaQ=)S!k}6v=8ubP0 z?Xv9ht>w*$HSOD~@UwASc}-GDS$c79QDsg+MOsF2YD#HgN_llkc|GVirMi{JAeA(x zRkS6RwWL(Er&P5kRkduZYD}nV1^{oXs*fou2T^aSsE#YEjIXGRuc+T#T)m~RDyE<; zIv-cb_fq+m(yHjZvZ$Ogd1gseR&jJzA&EDuA}+f+F1u!PUiFsTinyGzn5@FM+>*@& zl`%P`5m`m@tb(zuSD`6cxI)r1Ixk#1Wxsdej`MIIxYxbE&%O7KYwvCMzFyD1KF@&x zPtSns;DG1Y(D2cI-{D@*u>s$)KHrf6?~y^z(E<1IA>Xk<@5!N&tP9tH^?x86Tt|Lv&tw_`*1j`j~99~?Q* z=hmKcY%}ypnft>XiaAB=i;D+zgX@%Za}y_QaldBFrUKOhBPW0n6HvuHz&zXxVB;2; z25{)4%dyUiA&sdQOg97&CUjBa2}n3dc-29_h2#N%5dom9t4RSsvH)gXwg%~3{`iFX z)-Ceqo}n) zsxWz;D-fjJ3@T)RG0+{)Va5j)^naSBLR79GjdQ)mzCj~m5KvhSQ%ut;OKRnbl4G@_ zIAy$~L)bN$lr9h$FDPkU>kvbr)xM2w(n{AAD3JIWpt1+39h+LLC_MKIR4#wC zA)1$FPJZ2AL?+{nKz({dE8Uw~Y<_KHej*GlX5xJnZNi&c`+--1Oe1e+x-ZmM=^7_f z+NEl0u_6aDQ&@pmfW(ZU1^F-WRdy&$-3RgsYVRikLDz?V z$)q(yOvMaM+CZPuvCO^TD=F3waw#J}!}NIm5dA-C<{CTyZ9qs~41&Rp?StGe(Gtl> zUwTMDn}U18^ku~w0H6vMajF5#AwP|Ub=D8bUEfC3#A$G1Cff6%|${@(Knr*d)?HE)BS=xmA(@_+)G);0M!hpa?~c&Aq8KXUkSYfv@Dm+G zTcu55u*wXm^&nn{VsQI_>pjDY(DLF#S{0!BhZ!(wZD1sn1hnvnGdAty7zyZEdYWO( z$Tc)9gNm{ID2$E>+|1Z!#xI3wxg$5J20eho z$pCRSDAQOY>DtPKRp?fMrrZ?7S`OVP6f*+4l$I0}s%E-x#h83V zYIx#KT9r{4q3mMxqM>{;nrBC#bTULx=aid=g{{LCW`d?g??l>Nb!begbdknUxo(68 zd6F&t9%*OR`6BetjG@BP1v(Tux4{8bOlU~7#Io-QoO~2|8K?=x5${6VN~v?Zq$V#G zk_e+up(g-Go7|BG4DTU{2?Jx09_C|JK!X@P&;_BsLy;9a98^P}Ogk*2w>k7wv@MH) z0!U3z!rZ~_=yZ=%ZRZXJo(v_AyJqz5L~B5S!_6|2I|AcMXOGZemO;OFhESuT&p)~* zMB6Jh-|}xi`9r>b#%G%zdFGvo%ioy1^raaqU!S?^&DpEpp7YXMb66B9W%io>9?2W}M-(3FuJFmR|^_nl&Pno`I{G`QCO;|c<+Um*EpC3Q* z=|`WK`^b~?pP2IWjHR#6c>3j;OJBzCljgoYcljqzO@C$X(`#P)Xj4K_Yq5F<6v`Z> zrGD4Zz{K1s%U*nZ&NELgSpE3?C>KAxM<-UuTERF@|iX7 zzw-6hGgmz`Zo*R!jGz3#q{$CVm^1se?>7~gJ_*fw?~@;*{WjKamFI;0RJJ9oGAW`e zJ*p}}Qkx!Kl9p6lQj=WL^hH3#E8qU~WqA1FS6`X3c-iAK7R-O)wQqvMpO`so;>_6- zrp}nRX!%?3fA#TKKfV3lXN#6SJ8$udMa!O>KKto8i=Usd<=Icaw&aEP z7eDvjf@j`Z_UyaMo_XuV*S~!8{k1Q?^W)3!u6yg_z%PG}T^EuW6j_vzuc$J^A}T_c{M(pY4B5SO4C8>VDzw-n{Oc znWjq_=JWB&Bl6lklDh7YN>gB&(XUjqp+LEzp#A6E`XBP@zRRlqKC|-619$nt zj3YAhD3j9ZXnAIeKIR4VVJ@Q>p=vt>R3u^22dD<-joDpgbke3#7yqR*vjN0Qa1GUY z=wS#bHG8BGybQ91RENxTgqyr!Cagh}^OT3YT=)~sq*ym0Fbxhoiq=~Egt*f}pwU>3 zb`Y`-Vu9-mS|CX84A6UmDS5c#N!|53zk($?MuUuQ?tVh#8l52a0KvDoKLm?1)=>1R*x3{%K^t)}~aqr_{D2R5iv|)-!Urn%tXE(Uee)t0kpQ zl~BxHhh&Ca$z@b7|w2@`m{GIso&Q;)<=sm9YgCTZ$_DW4A@7Cs>9^cRVsc$N}z zfDwh5R^0HyJY~95yoE=3OEvdH+kvCZP71&iR#t0Z>mcSrsGiy2H>ljfJf4l*8>Dmd z$(rGf>fwzl=8@WfuYka*X)}a`$I&sJK8t}jGVWJ9Ow+UyC~GIgYF(hs5CA+6Lvj1H z4Q&Jr>V1q#&;V2MC+2zaYajG$qnR4=nTX2}THFtA0$_vHg$*2ZDiVop0>o+=xQhk} zdvLkf%aZ>1rDmSAgu`H>0cxeO_Q#x=m50Hd0v^=L2 z*cql80yl;UYb`W*Mw4poSYPD^Kpj8`&`S##kR!_AiqX5tLf}=vC#{I|f2P|lR%H!i z8Y$c<)?MMkj~8?YzLl!!i4BE5L^nkE;qf+LW=90pT@`^ruChfNu#nqv8=qzX%UOvN zTEU+*4)bYZix*f(+Qgp#S|AGGx-gT9x^P&7obB{X0Iox6rlU2;4eOH=x`ISA1e9ga z4)L3M{FoAeVT%6-i~UsmLLg@XTTlv$gmHE^Tubr{$I?jCI$(Pc0ESkD_+}X}m$cC7 zNmf!LaFkJ|XkJ>#b;3$KsjD`g-(S-~z zVt6VRNG|YFdIUHnTASS9jI;pHSWeLwc6^uHW?EOI!Hbth3TDHCEjQRB@O-uCKCw7) zGh4W^aWTfDv}5s{F43@j7oLglQlp}kn=3B2t5IO$I*KGTFlZ_!O#UIM(XKE#6vA8y zm=VH@DGJc=_8WK`2+4>Z_tiDyz-3DwbP$>Cve0JCuq znhT1o&=}CawDV9kF+%R!!i+XXECacXP=7^(>l~4Kl!}&TnU;Djv<+%e^ctPbGEz~d~&K#ep*8AWSjE)Rxx_63=5 zN;-N2G`H3@ocSuQdBsQJ6IXsZVc9#ApMG=7iZ`dPczw>Qw`V{1?wnUXnDg5EbKY1p z`;GUOe*E>4kG@>;$yZN*{`JZ)zJLC^Utaj(*XMrt`Pm22_7pU$$~30!7q9&k5%Iv3@e}4P95;UY z15ZtP;EBoO9-BDs(P%N@?(=WXeEP+SbDw=;#^NWYFPSjo=?T+UJ~{3A zl^^&^tGk11yVgYKFa0=h($Y24=Dj*+{&O=yfD2a4o4Ir{+$WG=;sX2=LN;y*{3S8?=aP6yOGac{R>YpxoaUnVD9QTo)_wQh*PkwX zZuzR$Rxf_(*=b7_zWUDFUw!=1rXRnRO2Xqa^4EsSSH1Smti`J)&s{QU=8~y%R?S}Y z%Jg|JPMN)W@~mek&ssHg&gz*9UYs@m<>_;toi=mX%2lr~Tk+ETh0jl)vwHlrWfNzu zm@)r_`77RB`of1R-u(Qn?}FkRyNY(5Zai_ze8YKi_-_|~`=|L?yE z#((?gzyAEsf8+Yg|NeXUzyI0$AOCaA_MhrYzM4}58Qtd-4aZ|!c11RH1(qoNirP07 zx2-R1-jLU@F}v0;x85(SDj>5osyIe)3lVG;?S8ZSsjYUC`VOw&P7`!HuCjr8xptR#@& zX_&2MZ@9%v*A?!vadS{%@67$fH^2i0tUlZ@K=S= z8)+PkG~82YNANDfIY3Bk$&Hz;Azng_4A~`Sn;c;#Dx*X=$Q%p653tIm0ipm>QBG%I zZvQcjHsra*8=zx!5~0_`EdXLOVHonn1!z&j@><+gR?w{#S?bmlghavBX;E#|CNV`j54yV;P_WQ@+K zlB5-fWfTWx((1cDF=uUJc3^5zczRi2LP20ceo%5zNNRCNYEf`%K}dRWa7vLZvob8T zC@i%w8UR^T8`Y7;A)lWSU&YT6R2 zd8O2}Cs(&8*R`b7)F+mfrj}&om1X8v=jPRxb8&rKX=6fpYeH%3wo*DsZ!4{jEvnd3Si7a5T84UU zO-lwF=a$4~7e{B7DKbhFxh3+ff~efmn1a%{yoxP(wXr$XTk@(m=T>aat&Gd5!WENM z5s_9bOD&Ql=1UTD<;mH>@hO#-?tOQMditDu@3{8e{o~r->)zWd4m!H)IWph_@gDE> z9_?e&=97cIGmiVGY@^4Ae8Q#K2l~C@{d0bOTj;iJH~!;qmq-76_5Roopk*^hQ6ol_m5dej@w2LTYpcpoR2V% z?E^v-V9aA|a1$+1>k1S*ESON3AeizbOclc%Fp2zhuCP56aj?ewsq^92*{IYY*h zvw08>$b0OgRMa)_MVPH*G@ zKs#>JI5vrM*Lk=`o6Wz;O3OTr+n$hiUvQf@R0*vTfNl5FxB%Ph+j+7SiWH!+<7sSm znwR-l6g5QY3TzwN)Y2cq#8r$31~~_*#eqq!6pSg~^wKUc@e90f-Qu!?fZO z&zN?Yp0RWu9+YYZ8&phkc(Xfwyn5}H+M>5bGoqX?N+ z6q_pk7!WFL)YO44G?^0BxFt;T$MVd$EVM0MsRf(S&aAd7px>iYP&-XOxC_{f#SD-S zUZpcR;7TW6jZQQU;fgfky#zBrO9UDUZBD^K%z!EYT9$Ed7=-?1!IdWF{R2jaxhtvkN)| zmO8rZYI%%v1R0(ltz>eI4G0I-jYvb71pblY=(DP&(uT?cJJB-X?SpI|R9tu+L*HUx zD%wrMeTN8xS0eNh36n;;qV=8_gD(njsKIT*J}lgKq4!9^nHU&iZw2~UI96Y##b%P2 zv4tNZ2Gx|CpiKDwpQZv0&nFi_uy{3?P1F}p_Arc=O)6LyNX9J&yomXw(U>TeTuX08 z1)vTZ8~D@g2Hl};0qu%069+yQ+vExZBp0RzL%5-2 zGNT>i8N&mr8@d}EjS(J-Nd%SAWDhk#%G;R_mb$DzL98fom*k%z6LII6ULr`-qd_7s5lC(ZJ>^esg1f| zenl9)F&5w0R)|833Yy2wUT{7xxxpFN4CbrcM@%a`M z4@-+(fmbu|fldMJLEJ+rhf#{DaVJi9Go<;%dyz%6KmO(M)o(tw_=PEpU!1<=rD;oE zn!IGy^p!8of9dTx&%d?swT~9P@yXIPU#$A<+huD$TCwJ%l^=Zg^m}U-zxC#VmtR`= z@=J4{Up;^I%2~@7PhYxd;(}S@=gycgZ^o0eCQqC*ZQ`sMbfN@>h*Y|C%aXpf%_PDvX#ZPG(C<~%TE_P8g;k9*{aagR)R;ECzuo}BjR z{MDP%s~%nW+`|**KlJ4MM<*_Oa?1P|aG|4KuB^BMD#+5JnIP|BKmW{bB z%f8(7@cfmJ&6qcH$+9P=On>-^2@_||dFF*zUViWW4}SXfi=dESW8;G}^P_57B@zRmd#uGY+Pn(T8%ojUY$~_ zOsj3rYSm{pYQj^p9-BS;(HXOznm6z9nKQ>d^~A%I#!p>1XW0uY=RZAv*20;S=T4it zXx`MNvu7-yyZqG^Z+-gi_ZxqG@3Rj-{N~FKzy0y24FL%$8D&*f83|GTU%dX)dn-SE zan3idEm`-@vbZ1Kiv8j3@ULH${q)w4AFTZF)kTFFa-*uCx;X0Bb!$HP_T9ks-z04K zBya6UB_ZDyC&;#KRcs0kicZVkl2`id>mNQbdC`=atESFcyl70{niJ| z-}z+uyB{ro|D$I<`RwIye|&#^;1@yC;Kaie9lLKi4h)PQwB6g=KWx4-(0RjUx#2KgwHhwl3>W(im-|&`@9HiN z8qW{tPv2D?zteK;X6^AS<;O1M>^_mZ_hRYsfy6!6!%bHdCTj>*NlbPz<>eMSL2yf2 zIOPISC`(PW@BPo}v;Gn5aX# zb0|7|B6k2ZPKMD!lY#VzGJ7%i!BJtLaflj0UlQi(7cdE^GtgEHdT#FcD^I8d_0hfvm#Qg#IP~e%vFqFc)B$g{Xclk93R*Kk1$QdYl4du+F91+y@}Q{Pl@R$0fZ1q#Zatu0sIn27G}N;(v%#2mIu|3+ zX!Hz6e+lmxe39|JfrLhnc$Y^|qFZjh7s}0O!Z4O#`rR0Cvp38*5{fP}deItCokLJ; zZhsKzL-2Jo8jx|(xF7G8>qnVz3oRyqGtBHoeaBJ?p)@U{?HK2!{lD0I2yp{ z4Fn$3uby3x_TlQOw{O<DY)|t2EHGUTu_me!qe{1fmpRarM^Pk`Q zVdJZx{qWW|zr6n0+PA)3|K=C#-}vI!4}SIkDI#t|d}?@BiM*gHzM?&)P7`0&5RqLH zT~HaBS00>642~?S1^uSfD3hzysTIn!YE@EITXJ1{O1&zzP6;|C3D>q|)T`2}Yg1~g zQcCl)%5rndbF<0{vns2T%4)Y&G$hr~w>hD@Wov0YV|}YzK)hSanzocTZv#SCv?i9f z#utkZOB=V90ft-gmc+`|trhig#Z{5{718 ztn>!7dV`c+|5jI!l83kBTYehrMm;@H{6yktl*(Ua4`_1-C`SU7!@(-tkMh%Jn`ij~ z_-U!;)<6KAa6O?w^tK@;;!^qo+wo+#zmlhy^N0|Q=yVO;zXO%%LD#0%fsM_5Oc;jR zp^2EO*FH|F7QUStrndoxMH+oPx0Y$2tYON2keEU<1V)n5+-jGqhQgEsU`qg`MA=V+ zDwr0?p|fplyA!PL2bNH=hRUmP%K%6;odI0I3DoSz%v?OmNM()2!U0l?_ z5N)>HG-W69?|I|n}Kb_x5f|%Yzd$RA!AX*Lm+~2h0(x8hKJgXvO$do zb4v9C3N4t>E>Qxd!2J9?jKQT?O@}0gAt^O`XE=zRizt^CETu9qB)6z(%OWI@e|X>o z$&eTZHm2PY<-z^*Rbb2VR285su!ljgIxB5iI!6SZLLJ;DK=DM*Bmy~IRdF*uXN@yjKaAor-Lx9u?GDup;mKp! zuoOl&?-W*9CSyZKn6V^djbx`w)HV>2L_?a)f-3kMM}X1N*4B6YrC%r|O0T;rK5OQfQKSm6aNfhr4kC{RHro6$2333Mhr zT+To;Ji!XiBx)LYye+1ooasqX40n{pCu76nMbM>aa;MFKBXevpxS+g3gA^IUJdyYc zpc-}I{S1_qh&NJ)Gdd#}8S9EPapXiBhB0xtPeEs3XSmrV>j0+OQFpcvYR6$_i!ft0 z4Psb^i(Jhz<`1JYGqeq!5(GV!=4%%yU5+nWfchj`%-IM72;awZO^xmdGj-o+3|gv^ zG?mAghU38J92m?rgR#op5Wd1K?|`m@VZad^Wpu@uy!bBO7-^w_S7D+Mh%kC$ETb~L z4{He)p$LmNhCPBY06pGIRE92Sut#)2^|VExVGVd7lvG*f)8NmOV3d(bF@QET6V`$<+Dtr!Jf~c|NYWljqHwwqU{3 zIdi7Wo-uR&teFdDPg^i^#^Tv?mjW{9%v!Qw!IGs57cZN?aNfKHv*#_GJ!$5YY4fH{ znKfm`-04$hO`1G&()8IgCr_U?dHRfL^A=2>zi9U2r@veq&|uzM(5y`?t8LzUIHN@~ z`I%=PoH6^M8S@{UGW)?PvmTfo>kOrJ4p_S|{%7tdR? zVA;wQZ@m4+^5u&cFJ3rf=JZ*!=ggQhck;C9v**sAK6&cQiBqOeoH%v-_<7T(e(?5N zAFp|T+N4Qy=FDBVaLIG4SHJq=^DjKTbm{Eri)YSQHfPrI1#^}yoV{%E{NNzJXO=Jf z@ZEP`T=~rE6-!sGSh8%%!llbs{NNXqR@1tzqCTd$Dz>CLzOn(ohb3oBUbJ}9+}Ts- z&3b(D6OT=N^noWHdUVp04^Mbx{FJ92ed>{?rcInQclzTqo_cKhQ%_EN^0CQ}%~&vf z;nQdu;#(_TeRt)XYo34q<7eJ}_u04Kef9lM-~RIZ55D>7<@Y~&;k`94uKDo8 zpMUx2$6wz2`kNm%Zu;z}@4o)^r=R^d`bmR=WI>9k(7c?qw#KrPC-&NXga7!~fB)~l z|M`!9|J(Jie+~TWpEv%`|2=d6Ka7`e7IbuOEvt%5Nt48HE@^2#bmI<`#l!u>$K8KE z>b&0*SqVEXZKBy?({9q>0a{{N9RqK>5A2S)nU41Gh7%1 z6}KM0*?#O+)4}U4`>)saT(8)3J%8twwC&e6TW-lMeKBBQJq0rNTbW|rB?lepg;~|$ zl>gyT%u|SifG*@l&V76$hlDq~n1-FJCng;v&)9n5X4H~}(mCDtBsP25 zF9LemZbmn0unPsCZ?MD+G9(bnEFwl)Y?7joGAJR;>E(?w(yN?NOAwVEXiU)*y5TS_ z-L|2~Nz87hKh!f%6-q@LJK9!Qr=eWoewGS_Bv+#!eIf}W#s*yg#1gFj2Sz#LGL--=L2^P~Jz^#aHf+~myV)aBB zXw5TvfX&>>!SbPVZc+Cos!nIM@6T`FQK0J1S9fMBjalu6JZ)Ew(v08p+D-ZG=Dapz zp3@WGFv zAN>^e#d_)I8^b=|82*)?a*rsPEy zRBSD)ORQ;4u5OPjs!ghB+gw-|lUJ2c(EtWct57Caw58UllWWxYnOdz%scugN9@n*{ zR5c`(73Wl!7S$9L))ZxymZq0grc^W}3fxVu10=U3RkbF9bxWI5%38qE$(8N+@7Bu3 z&1JPIRZWQ{jS0m~@%UNRmQdE3P}YJ1F#5NsDz>047ovelfAqjLakbuA}|lL%ps;z3#)k?t^z-2YQ|RdR@D2Iu7)E58QDdy6HI9=RGpu zJ#HO2-0wL&=ssy1Jv}&jvfp=d@ZQ0@&ZfP`kK0C%JMJHM{C@u4-!6~-=NaekM+S!X z-*O(f>pjpv!a(AF-+sJ>xI21qVC3-7eP)(s25Il%{?UECqg^*flRNH4YIss6^e7&{ zW^i-l=pjiU9R8TCm@m*XgLN){t;dfE#)(P6(2F2CgmIXl3++xP?6;&}{)FZe2!Iq+ z%uH_#F$Lf;9y3Jk3D)?6fV+Bc7;Pk&!XAlcG>nn$7`^o16BZh#-4bc!JORQ38JHcU zbK})`7oQ{ikhyK719daO8-FKtlZu0No&ddfos!3}aaxIh6c&}o*5KI)%z+d!(o zY1~gd!Mnr(ytMj~!>z&MuXq>4OOVnQ#skA$j4{?Y18E!gfP0D1N`Nl@OX|iGz^kZN zpqgAt?`whIxH(ik9HbrzX!iyPwhz+KuOvXE4FhMEW)$i1;;aDDsW zMs5(;19&)`lc%>TnT|^&-*SX&yy5LUDJDz=c!w#SJO^08JXM1cdQc@}9hozRcnJg@ z0E&P*07X#x=_somlB=yypuq!jonT$6<3JjqAn1r8l=NWa0!ANQ3B6xQUL+pr80ouMAQ+>B=`P7R$)qO6ceJ*N+8h;F+nKWijlV>S{PajzGd{H zou!yInC)0%Q2--IhAzqyj5_ER2CKw~dBuSNTu}glgda7!gLzZXQ=;U+tq>*^>!&*| z!;_p)BY;2@XIRCtHftHq0qPS2k~u z1qs{i zM_PJ^KBb9a(2v`ROO0Keuw_(z&x&En2j4@#1He zEncx`-U{&Tyje>Zpbh3MTfFFnXIHFVv3S+u`Ky-9U9ouX^2H0DdwR+0XO=$u%z|fE zFM8(LB`a4gUH!r{&pfw$`Ex5)z5MK|S6=#VgI`>BsVu!XI4vJnL|$oJVO4Zq$#ZKy zUis?lFTC~E3va*k^84?ue)ElIUU}t>4?cMB)6d`i{Hu?C{Q1Kl*M9ut`mZ(yejhB^ zposC0-V(AoF)}qfE~_XZyC^ZMFukBOAv-^_xGcY-CbzsUtE{1{Nn5Ejl(lK|8e0on zR3+_(a#crhn<=+ZTc9)+C@tmYUAZb#nX$7>-&LmRC|7q^YIfBa_f;A9S9kVQbnL6@ z?5W;2uIkPsb=^lBI*&E(I9A_zxM}B+x}Arcb{}uvf3jisvG&8~tGW*=51(!8 zIiWsyx~1oY?!;x&nd_=UXVnMKm`_~oI&;f>{F?sYxsH>UwMS02_Z)87y{~=mfyV8- zYfY9at)aA~wN9rmZ)~Y;ZLMsnt7<5$2w1+-hTE>_sLV; zM^Bs^aGrCHo_7A}!oB}<@&5lD9=x~vmUGV?*PdSY&R);%LEp}P&+cA#%dv~5%LChQ zxOU$3cHQ)J-E?)`aGI~$3>OEq=k6HK_gXIYTh8_CPW5(N9O}4i?Yv?$oaxn^=xsi9 zx3=d_>7Lu!-M6+`u0B|ZH=<(%<>an6)>&FXaT~zNi=_<@ksC*# z@Ig>ORzpyTl0$}IapcXE7&I)qqs{I}vop%#h&12L*B`8?%s+W%x?Pu#wCZfK~o zb5FAQN`%FRMOI-Pq2wWR3b6yU10|a1;Ve{YI{l(Be6q=dDvMMI3}g+3fU+=9MQ))9 z7p(;44+X<*;!%v3mUhsnPJss19QyiLflZLftR2@BE(*|R=pn^jLMjO&ZJ|jx6+^GZ ziZkY;E@U;Ns9;FKHdhDAPbTYzs(^lky26cGqa(;@XYY(XUjR1nk_)}b6+(FSph@v4 ztQ}EeOBxrt8?T^m8kDXMx=)9wZ0t2PG)tFExOvmkF3hvxX4Wx;ZH|_wg_hvKHFreV zkWo|Nz$H|3^gFZ{s0#E^gu1UETbM8c-N7dA5s2EcRNO~kdNX@~aWKHxCpFyOraPah z+LzVVnbl^_Q*{@pyK~jsbF|yDaA`X8blq8PrhH9Tp30orrq5Gba+PMFZ+4p@yTy>% zpwF(;W;SRt8#OsR?^lyjr_659X4R?ED%;n|l3x38-D@BJ^8QzTU#tuNI#BjgX!Lgh z5udIL`{1Xbx4&Hf!S}&yeh3PP&k0N`2uRQSIWGDAUqe6qIrxKL0zUH(m1X6}mDX&l zYTj1f9FbWbomoz5t!YcGZBMCcNvmj2t5l{`DHAK(62Zf@s+6j>l=8~Fs*<9bg1pMS z?DE3&%9@1ox~--4G#6LlTdj#@O*|{ON|{pD3W`pyQpT4z#aFf@R<|ZrHsj8%#SQVL z&BE~8kWkT>P|>obq+x4e{no;ot%X$y`PFf$`F`@PkkYZa6|q?rQJJNivnv3=l8jtg zR$)YLNn~DWRDKEMc1(ViBDXBIusWu&a!X-lY+ji(BQFHpyE!pzYjRL@!X`z`28E)v z=g`4E$KihGvBBZvLnB8Ae9Q&i>)PMv+IQEv_pWQtZ3q8**U{7OI?(S11|RKrA06=E z{*$(Q`)^vC_8vLqxOb@E32Z)&hgfOUJu-OzX#f2~z4wmXy??NO^uWOI!G2#4V~Kgd z5@47~w(huk`i2kodXEl{9v=E@_V#`m=$Gzz+=&ZR4+b0TAso*^U zjzE=bBM_0c6S~0APRks(O^*ru6*S9>#u9=ne`lU^XNbWU%#*|2!3@)7Gyz699vA?81*BClGp*+9nPyLq&#&xMN97Xd$2lm{s6RB(IKnNsw}NjzB%? zLOg~#DlEn}nz9)Gj}{p5f#SQ3;+@d61gcgiCAW;oSfFBG~6cLq>s_Onf;0l+Sj@7wDU>7=J7|ppi%kHr6>6;A*vNi(y#g_+{oHdQH>Ohte@1ji z=m35|deqm&^P*{8ryDJXjVP*<15$WL1F@i9V1R-2Vs_l)U@&l>9%`02AeZj$=z8=! zZkFh6a&;n5)Pr5+6={^l@#$pdMS>+bG1)>W$&Y`FQ z{CJj+EzBgGyUc>R(Mr&Y2)BPY(Fp~`9ZG`$Ad^W}7>#VCJsCIS9(;?A0KClyu>u7P zABx^VAJJAXPi~Axw zT``uyq|Q6p`)(H=y4`ZYZoWCPv+q9++5UE*|F>P&oTk&)iglgq<*^G_K0kNq@;S?v zPn|b+#=-@&7B5=3bm`2wb7#z&Icv`B8TbeP&Y3fN_RMM1rc9YOX$t6g-rSk9XH17PT!%9v>u&3zo)&C}RA=BmAXN0kY`8h}gB^lJ((X zfzkLqE;MqhUzkD~w=Fa_K74axh+?xOdYdFBQNB4@u_av+mnuuh3XV$**_;%enk(Oy zxh11GE~6keJ%3wnaa?9$e12(6RzYl5QA}oGWJYduUU6i0X+&mOcuGNda!GJv;iiPV z(DdSQREI9>Pu%J_-`{bmf5+9qo!16;-L&nwVc&npvE%yS-d;C@&F(ny^Wf0P z!2#$%qlfxN4%{8yeG|HoXV*=;h)EOqNPFFT`bdxXe1ESGf7)@=vFny+#|>BaHQV0X zzFoImyRKPrbzdIbb$w{p^}&uyck$94*R9*H59rU`XgYAVx#x7#{*%gM7gfivsE%K1 zJ9Jic^n&KZWzF%6rnA=#r*4?f-?5y(WjKAUlZAbfErv|;JY`%-G`plZY&n0s=Z+gC@4jK{x?wk8v6?RSt503ipTE85?(qKG!#%f0_uauYy!WPO z$94PmYqs5YoV#zi_uTgGxjVY&7TUzObCAoU;D9}Er+f(_gqmPzSeT!YR#Ur zg`J18O$U>ehvM5$MYdnsV(g2wSR*?;F&1~E-W6@S7iAocG>rnKAeJF6B8(6P!;}~u z(6^}eK_GyHqb(y*{OMybiwF`SpmumA7RXYMqGJTpR&D|?`(zfcxO^dIXNcJotaD0e z6A5rKg(` zHzv^FSorX8COP8b)^5sZ??`L2BsZIqo6Skh z9SP0m#AbbBvo5vGoYrbgZPKQ-Xj7Zisg0V{dM&Q>dR1~=dupRPqd}9^pv$V&X4h)6 z>vWklnw)y0EUEP4pF`Jt>A&V1zmL}ieXuTY%`blM|Lph4+Msv7+W7I$VLwY#HpS-# zF$}oyyYSd|f7tNp`k-&ZB7#z~V@s-q*|r@&Hy6|-m$W1mHv?jMd{I?fdX*|kI4vhv zsggm$#Jomm`{SsamC7@1ZQnOUw#FNw}Bi{^<(m617>(YaL-xs{5%%9z56 zs9Y>YIbrb`p>auJSQlaw{34?^Mn~+n&09lzjFz232- zgAewN9_hP(Z0O#x!O>&=BPXo*{r5wI_j>5_4B>_)fa3k2?e~(NyHVN!Oc}WaBZnCZ zFy^^OhS6>WL}8@)!gY+mC)r}K85tcKs{dnR;ummWD0iS3Z5zsc8E}ChNiEa+(PP=* z^3&M^jSNP`;N-R!SPrz@j!EQ&-X*pO-8`w=AP(-*Q2+*OtRXxM1tXc%7q0gz^rKLO}=-#B}Y2l%?28kG5239cK19XZx1+Wa# z+x>xs8uuopBM77isxtdfmkC5l9@U;%S$JPr=oJDk~Z% zL<17`25EoYi6|=QVbx>}-C@KmpwyTKxs0$B-Q-SBbp*nn*j|$e< z7`37EhN)cu(I6$y2@;v8m?bNq&BmbL7LQaj65L{gEJgd^HpXi(qIe)c!=u8v`K3Wq zyMn;6IxoJfU{d=Kz&=QA_18GY zVPr^5c3RbR19F1pkd#rKz&2-;(HV_3kGqBrp07jO9f-w7e?skGBxuLzh%wru^ZYacP8{5}}m<|Cg8_DF+z(p`Q{Z8qB3oINn zAY+A-)5VKKgf*1|*NBo>Wd^GpgvK*$tiY;B=HUc$f|u|)8!Zl;X+{g6oc7pG{E3E2 zki66Y?jp~Veg!v17{QYu3hoy~fo3~=WabWUjKu?W1P~=9h_X!-y5VT!F!%%r!NxJ$ z<+STM1=-T{9fhLmtO{n{<}QPRX{9}}=FzcuRN8w@C>1Sl02sN^F3}Ir3D$yQ*c3V& z;Tim6@RGNoSkMI;3m7Gf#Aug;56z75K`jXOya!7x-J2ymsda2sb4#=|V9K!^qSK9Z zx0V91EqEs2hZ#zx8nllm#^AwliqxPHxNAPP<%}Kyw(;mo;}H0qAjbiQnc$F_hs6dq zcl%HV2^v#vMK#Cj0Eu+!Hrsx2walatS;EQC=RdLTLkT1*qp zDQamPel+!?<{;iIvG`&HC<-Th+Q!iW zpiLPV9vEzbdgm46hd#!j@fd~*W4NNH99S68fT$@ZGJ6y15hghG3D7tL^CC^qcfC>g z#p0HAa2yKV*BxudFOFD|DTg~9RJ@p93bP;vq^)trnucRQp(bi<7+rGa#G(!a?MH^8 zWrkKtr+{d)C)SAaJEJYmhz=)68&89V9n;B;fM|<1#yk>B2!|$ZRcyD%cA>^^(^1F0!;arhxc+q7`MdJqnVvfitfu>KI+cgcX0_;IifT5+CI1krc<-y9SH1E6 ziWlEm@!Xq>o_=A@;^lJ|E}OMr@$7{QX3Uv8XWru3^Ont;w_@ttg|p@_o-ud9ta*#) zFIt9c!P2J}JpIhPrOT(zoj+|hKzGU9Ma$+aT0Ud`(&-D9OrO7a>g@Tm=gbF1PoFt` z)eEbatay6as@30b49ag%$7B^rQuDXu*GA`8N99&T=9EX~mPhAT#uZoNm#EyT$n469 zta3$Gxjd~)Z)<8$LiYODlyxylKgqVOjZR!2oxDCO@fYcqjgiUg z70K%(lYWtH`5`3wyWof)LZiM7iTExo;_D#AH-Yl+L!-Y9i@|rpQVYXVOJrH)p&6xd zMXgEIx{OA1iEdA+esAOMlTAHmTMu8>oW5l^e|N`C`@a6+!}j|JhkiTZ_{%Z-pU?RI ze%$ldBi7#z_1`~g`yF5g4W_3L8joY|ZO{JO-d)$7_zBwFbH}~&j&;u+J2aJ@w`|?l zZM$weci<*@gibfpiNia&K<>)Ua?zx!&R_k4qSV0*>~Nr?z&~&a~p_j z-**RPv+urc-G9ru=caA%E$2@B^`;GF#!vid&yAsdH-`3K@85g9fA@`{o!5rCuk?3c z={26Y)^_MrOV8=%o>RD54xDa3d=BW_dhiT>Q64?3I&ofo>Vo>zrPiaT>-QgR*mtZE zw;ej$bntZZ!87`EH@mM5?YKI$1EdWyzvK=VBl~Z9fxi3i;2PO?d!*;? z$Zlf1;{bWs*K^wqX70J`*?-rw@2+>}O}F{7&2q)Azc8q~(BE;*W<1-gJ9S5U;->cG zZS~RXZHF(n9K71N?|jvc)8*Sw=IV~8X-*~S&Tli`inrJ>Pq7L_8%JV{_hXFr;wXsh zF(&I)^H8kW77hM1TjO^O6z(4^=^03|fS+A*%zY3Gcj(;F+eachha);cXbe>Vpwc%O ztV;`_n@9=qONgSe6tK!2K*$!04>*mVywmIs0|OhGJPFEQIKIApB+Lw8b~8>&SXmVv zUJ*<|PiZN>*l}N&Tj?wvY4KsDgJ2Xs*i12~Fgr54`zkvR>-Ow${r&Iuzy0Uk|M=V8 zzx~hN{(I?Nw>KLo6=fYv<3!HXgZVuXI#-Nv&jE*X`;RtTuQ+Lmuov0k6;UBrZkVc2 zjvG3>K;>|=mo`tMC)7Y2FMdJwu`&w3VyFS0Fs+qd&?b?}foSa}$I=5_tTJQC|CzFj z(a7O6yfbwf6D1hA%Rs9v-q^w4#k%ZdQu=47>z-AfuseNO#fXAYyp*)t4 z8ME+Ti3NO4pK73QDCoBXZG!51WX!BVrvO~gyCi1Davo$qVk5a{OYpIsv3Z9VnuUeO>FQowNX>%Dkxh}q}o)N#`-XfCl){=%TMGc#a>Ne-sZqBWW%cl zxa9E7DdDjR0TIz@mDLCC3?3P99=z)SxPpEU@falE0UBu`|LyyGA%fgk8UVCCpx-{% z8PD$*yni`9G^{;(L4W$%-n*`zfsvkm-+{rA1O50e9iETaexs}N(7nUf`v(T^A0E1Y zgf`)OhX+TF4BYGK_3YzrAjFz$F95iIr0JAB&NN6nQ->Q+z`Ywrx6x7};hq$uaOvSp zzYIVr2WPN`&MY(t(|dn7TuYZ}aIe@cqG13UCVdbD-9b7LW$B0P4aLn+S%pJB24@Jv ziRs0Fi65pKqBkPYS7{9!%g|1eWJVj{C~jkBDW-ivA)ys}LaDL4L$ua#3~%r%rVNk& zfZ|6_Kr*(*8LAoP8NV8zl&7+JBw%>G&mYu^lF-P<*ppy`Jy_3FUQBRBdS}8yJdo&4 zqbmy^&Zr7I!wvAc#=|3ywW#EvoIi77ZZBvh66ReV6`V)JN61PEuERcw`e83 zCqQk5_##Z|oPlEzph_O6jIWCWhpc{@!5}(v(`*|A{MOM$H&l2&qxyIe)24!2+s3vJ zY^*iCo!y%V+u&6uy`%59#tx7M`l3xjfY2JpCeXV$11Uf`mI}+twAeaa?y*p6d-hKp2w z5bNQj8p2GdZUD3fO&aRBFsuPG2g#87p$KpnUZHkI0Ed|Rm3CAtTJ%FUIHQFrRe1c8 z!K4tHpdDd!72elQg?1#`G#Vu=OEmPM3V1JrOR-cqr51pbE!}hWeOR9e34lPX4H&6v8WM#mRRr`hnJ|Z7n06r2Xo~`70Ea-c20jFJ z3Dx(8GSZkgqapwvpll?{%yml$eWOhR02Zc2mKMkgRYMq5Q9(Q^N{`C&>-dE0H=iTc zBy^ksx3O%1w@~en!r}#Xh8wL>9e9*A){Mn4?D6vO(p(O5TlMgV7x8ok}%W`dYNQ_o_heK*XS6YMFwqQJN z4WKDPbaYT>^BUp?13s4OgA&;RYo}#mf=X#aFos)Nd|_B;{k%AdAUmh zk~Dg@cHN8OrntyrD%_j@2&2_|Vws@J6+!bj`4gSBTQ&*SBzyO3R)@NPW6SP3g)D{f!Pz$iMr#-!%XYsd9u{N8aVz3WzP=bbF`_1y03*&XNdI?m>q z&y-kBRc$|8z3XzrzAMf9F175xtUi8Qb>h1I>>d4uUh|c~?bqx(Z#nkdb%CG&GdphB zu-0P9JnsDcnDhQg_wOg1e>&~`+m(_3KI8t=ap(OL?%xmFMz(_}Zdg064esi7?dQo2 z!~3~lyRZQ7z3JF_b!gW$Tlb}*uJirs!xvljo>ul;DAnv~-gCNKzpt_DSh;Fk%H;T&T^XKL8kSii$tsoQRz?=qDhg{im$z)GXxUQUoYrK> zY%%62yNb+vE4Lr4-*cRcQa^yQAT*E-K!QTH6x95}8&dd778tme>3=J?mBb5 z6EJ!9M)&z!otN(Jygpz#f5UwC`u20zcV4`)`|>UP?7nzw=jB_wFZb@b)VK4(on061 zw(URJvj3#&;OX{*r&7_fybGNr&9qj2JL3;|_ZW!0zJ6@1#Pw%}01EbL4m@1~%xu@5?uXlL&P0yZN zUI6XhJD!e9{pRz%yRN!>ZjbK1=GlJ5x&6AM`?|B^vhZZSWHq1d>pC~sd0|k0^0xUx zpZRRR>0H0zT%Z2*UE|rl?u+)Fmxj78^jXf{>^gJJeCoRH+^jp&XS(FPF!GPf_y5s#-O+mDR*vaJuJMfWoLBKByZC~%f#})X1(wz7GcBm?iTVh);OAA87+WIeU;p0!mw(*6|L5C({_g|U-;+D9MVkjA%}xncXQ-!uMzbfjb0k_g)X+3) zq3FXU^aq;0p=U!}6Zk=f9m8SNbBOfvj$!($2-K9BM#2pM^$`V>6CM;KP!m|*;X||F zcYK1|LKzPQ?4(LZR~mc~pIdw~Jc_&6|2CfU-KgB&%lh~NvD8opk&KqkP#xjZuN&pYS zQHU`ws5s9BWC^LSYf<-MnQ0g$3}a#~T*Kk!kub9t4H#l}2OFSwd7+etX~%-7@pfi- zfu_#g4RolHwtcL>T-WGgiZU?0|CloxwIi&y#(@OQ<;?bdS#3MA+P9}Q88g~C6I+bi z8g&T``oy-*Z7rQ!8ZF!EJGN9CwpQ!nYudNfYPMFl$5*Q2t2EoHG>Ns^t(EPYtJ`BM zT4KtYx7I4RRJL!eQf@A9-B#6>)}T(SRRMal>$KVR+MEV;1_-%Um0G9yNs;*Gw;Nyo zX2Zwp!@uy8e&sLyDp>xVEcU0UM8CwG*s}U7L&y4`|W>v`2isk90@{}S)N}((% zFEYI-BC|-IQ6S6Ai^wR5%_)t|E!&b;u{F11YfeQ}T5;I69C>nK*w*Zz=%j$i_yGB4 z{gKl>w`@oH+#uVYKIeWcroFCxw;g+LIk9ByyTe6c_Z&O*y2`i3zdsx3&(BO9eO=8>84 zD87i1#x8Ja0Hc2aimni*ZO63r0hq&?t;QA7ZUq3-ex}E_yg=nJqP35{!~hCjgjmv} z-m9?O3)c+?t9TDNoavYGI@AvL&}kW13$zO}c$q>~?*U#)^}aBTm-|q9PmqQu(a=z5 zpm94;;|U_rlBGqSf0|x{d4d|sIUK0>1R8vy#?b)Onpvi424{jR)Clx$7(0H&?bl}Y z*SP(8%rZ*omZ)7}N)J=q)5h)#H~Plhn9<0n3#u8Y9umII^Z}9371lzd zCmt29V>$pH;oCka@s9wVYpMb;erq{R%) zLhRR&bz_WlZH=Mp0xfb9pf7|zdwtBA*3G4wc2rkfCzc93T9Wk6gjjaqw8R8*V~YVN zWc0ZeA)rj^gSESE z=J8>afoHKsW08giBX&S&5;Ss9p)t{PpfWng;t4vmfR{RunQLAQ)?fxR+XSvN;Te7b ziLtZvU`XmZbne0%S)M;U!y~n>Sd%*nov0bYLqs15pLTo&V~M#;xW%Io%@x_{#RAZUg5^QOqWsZJ%jAqQ+oMIZprPpdt#`&iFF{9en;fmpt&Qjolw9OM zMtKZWI|!U?J?Ko{2x@pE^eiBP(pjCGO=w0l^)~Jl@Wd(H$$bPUd{7jqHDXc#@5i7f5P6b@*6apvJDx=iCCj4+QIqj5ShLKza)W~S_ha_Nce zK-K7a6y0Ty@3@t*{c7>ftMxrM3yjCgx1X)rajs$irTV>Rn-5&9-g%~O*V(3h7gYzY z=#Smj9lNbQc2jq%S9AQ1>ges3qc@dDZ>x^p(Vyrwob1&dzoj{GM|bM3;p|=Gg*&E; zce}4yyRKThu30-TTe~kic3gAsy6)M1-390c&F;94%f5p!?AhDr?!nqQI12pOch`#r zxaW@hP`~HkkngY!)aN@iNCMf{JF@eRci-Us{e!>n@4t7{@#jNB_wg-YA!uaBbyw#V zN5?fgU>itaI@7B?eoK4eR_nn_jeE}2?>yDC=UnZM6ZqM*=S6ZN}}*X=&mwEuWx z&+(Q6r<)F*YCd?n^~l-gL+8}Tu3FCa>QCO%pS;m|;cn}JGpZx!JI>xz?mf9Rzh>9P z{{6Q-dvCgq+kd~{``cO9?`OS#xpeOz=Y4-YX1jNO^dH9^za1R(9kGv|aNdVvvFGNX z`Rp~r$;)Kg^S5_i2d)nt=yUTNhyeh+wfo}jU03^dUIF^vvYfrR{qps$%hz__=-qj( zZ+|b;5pe9tzFtRr&+(=m2b*>sYT0w7e)pjgpXp=rlO6W&bJA?7@Eb&g=dAZV&Ca*1PLk-=3?3-IoB^H!Nqb>yKSf9XQp} zbG+@~Y31Q_&HGL??mpUzC+s=gy5~Ui&ONQ&`<1&6f@a(I9aHWI0{hyN|Z*Jk+vtKMGc7-qqN(yLsonw%v!CI`=4dA8Ih~t~Kv!=-OA`-BY`BUqkoa zrtN!`yAL*)yX(w58#?wh?dai)cI)-s9SXCp%AGH6A*r*?&@d z=#28l~q^SZP9y1f%~ zb6KI3W3sf)TZHw@=)8ZY&mu2^*!`&DOds?XdqobNZB?bV&UtvPu^ed1dC z(M#&%*P0HTZ`gaLaqqeM-KQG&o@w5Hu5r&v)uBuJ6E}9H-bbw?ao+k4|X z24cGGkrv0+&JoC#Xk1ttgwG54&CD%4$g4=oADdh^6laFCVS;z=u0qZLBO%SYJdxdy zInIbqS`q1_)8T>)gskJ2_pv+nV>^ zGVVKc+v~RfHq{5@8yRFx>+NhiTl=UH4H)XvwHi zL?>2NJmin3jbSkDKMV-u`BK6h1O?p-JymKLJ6wQaCS#f_7Y-x6rXzrkU~*wdr&ZSE zg|3AH$U8=)OmM|>0VI}TT;XP4pq?Stc&5U9U#4e-v!ZiYPS+$fHN$`6_%t4G541XkRE} zbg)7p8Ou#uH!Dl_05*uoDN#!I_rmc;JEwzSijmFqo!{&PPx-I!He7ttmyI;Tf(YMci`1SLj{P^Bafgk)5 z@?B`0EWI?oN*P<(x}_TEOZwebt%@se-d5g{Qm@Qx)}=LS(;C(3b*ilT_Pi!tT19($ zmGZN7vNylju;$00x4z%__IK+(+Y}~AE8?j|HOjPFO;WWgwN{y2*Pd9@vaPCRb6I0_ zepPgSMNC0OY(aHm3Ba{&YehqHO>=x%ZBltdQe|^e1rNd=g*6{Lj&OWs#Z1zslodbE~4W%N6N`iq!mwv_jBq zL~2Q7YKbDbSPmG@D2>P}kIpQO&MJ=2uiTtl9+g!Tom~>0Sqz4kr4-3h3M7fS;R!hb zafusZ;(`*BIxk)0slUBWVCvpp_x^tO-aB@zELdvy-f`@{1Nyb^?RC=8hQHhPfr|S) zEeFqRzdUfrI=XMrd&qh3wC^ux@BR1r-~I>JN$2m34IUajX&XIZ^BuR19J7ub85lX- zJ9_XAom>vwg*bH`zT-L!+1Tsa(>t2I>$YM$0~0V?h4a5dX5ps17{;7O(0s_Z%nRV9 zvx6B7%+Leo2j!#_7F|wl+UG)<3f>#4V$L?Gj!@fZ2WIRpt+i6HC2h#uf#O~dNq8)X z54s$<&L%N|t3h$jAh52%M~3#LXxPH8J1}#TTiY&b`d=guenxJv`AyY;w_g zMDGIT(R2*Z)d563q4X$*1Q?O&hk?6cM%0w)2JqxiqdQpV3jw7YJmA$3&@3(3d?2m6 zv~P1q0M7(t2WWZzHp|Zp@F02I;|@^*YDNOo4hRP@Bw8#)?FrY7h7rp->&3|_ZV~YB zl>lXhk2kImjSbi()p<6x(*WzQ0kz_bUO+oDfobtTo_`k19C%FWOmA~$5%9~q?+LnwL4V9r;Q62_A!v2oGzx;F}p6f0>CNUM`>1-f*&+P zG{$O4K@q|)n&wS2qcNda1J9ypfMrG~5sAv&IT3XIb;klttL^nh*V}1(&4DEz+nz*@>5vI2Z8#R;q zM4>!}VVTAe1IYO!BA2eAs3vedwrdo%h!pO@W6V#FR&fv%2t*j%Jkd-5OIDJwiul2Iz{qM|ZhnyF3vVdo&aRqcyV25!K1xQM^bL$>@U? z0<0o-l4K|ga1gbmFSYPL!`ur^*P2cJlwQi(io0*b3~wR_3RhEf}Y{b%_cE0W*g^cF24jI{K<^j8E%(vU zlHzoq&6azcyGBv+xDM!fK8Q#rm$ERySR9QB-e{;kW*5{rIlaGtyHH=yBQCrGI%0%{ zC09VF0b7olVbQi0JZe}(Wdqm8v@xg=u1MyX@kVVQi3SftVM0@Pcod!Ps7_{i!FTaG z5U*6{4%eW9&=I|{mQh9`2z@J5Z;$L6!Hd}7o$RlO4o6f6rjjkD)4iqB6KiyU{}mQ@ zY_~6JyIs-ci0K~M+&Q?l%bwKXN;M6ncHPO?c_Vl4t+K;+TTb`u&h(kj3|KB$O{WLU z7i=Au9KguVE4GfSw(Wq`YtEh5UENo~mi8T29bK2K+ppNWF55aT4s~9lX=^(^ylUTh z-M#aMYsYox_UrbYx9q!aJI1cPcWCL|d)vG3=J0_#_j-r~i~`zy3);D3&u#afJDzTQ z0${j1<|Re{E3C~7DC+n1-1X2+^tNN?O*d}qy5Z=)iLdx}-vV9uKoofLuA9zXw_Lk# z04F@#uQ*UzT)S_&cV4Bbv-_%b*A3UMYp$J_?YMvERmZLy_>+C-4SV-BTlckr9ajcB zFZXv|y1V`Apygt}`O@9atGxvH3;#c!{sX#^>&)|pJ>TxWyR$R4m7^ugavsmvp4pk5 z-SIeEl9knxy48_nGlBpKf;s1$bB@R;6cP#MoO8|rIg;$44hohmwch_z)c2lq4^E*_ zsJeCQ*1gZ~K50)myRQOEk1w|l%{KIm)OGdMs13z!vbe(1#Nw)g`u6Jk}~*VK{Q z(30EOQ7YEtHp??=nv2?HdCeVpP3`G5jl~`EdbX=dp{>>!YqZ_9+U^RCp<2^b&@QLa zDnnIET9w5uio#}jZbL_AbqmgrRo9w9QB~Wsnq;Zo@TrPa2kS2w3sHe}Sbq*gVi)->TfIC(}jj&IGYZ_lZ1#YTRk zw6GQbYRhhy6?CYI+f<<93YoS{!d9yd6-sTLzDL|Q)~M~5^iH&NjR0sn`ll5mOUls| z#qgqdc&>eDu5EZuF|#F^Tv1M~DkhehnRWH-20oeDb;%UIZs_NCG*cU0i@WUfIy<|m z!Pog+9AjA6V&^uQxlR4be&@czwXYd#EHkzbl{FmdJ=NktxG25qDj7RfUe6d z?Xhb|crUs)Y%{jIBS$Br=40@@ZgHEPUsF!aOGjr^Gb;+T;rv$5hI!h0W6^tm-gRg6 zz%#t>)GpHM6=}SWZuXcztR36S?c7OH?8d1rQ5sXMlI*R57%;8-Yz!2zM?#G)LWe2F zF2u;D#<1hX)I)YLYFn7bfx!x67ui2!lER?PVv=$Sl_!`_G0<@{EDd@>wL1dD%R2Y~ zTCJPMIwI7zbnR|a_f+51`0*E?J3jm3@cv)cZru6fl$kiBu7oD=Cs0xs>}#t=H(udg+RcJp{r4{sjR~Vu?T-%1lDZR$}{9 zasFVmw+k&&|eG`jcy(aqKuU`I_m)`s7Z(lnX79R;*1e#Vh$5pk)SGFbBwI|k4 zcB1qqX?ndlwXQXtNVp@ptmU`wT>0bqfcLHo|MdR#H+(~*i)+bqxVAm1raiq{oL1hR zT-usg){<1w8duR2Q{E6;(HK|R5>v+I;H_wiFRxE1Z%C?Y!TyAj+QhQPxYF9#;>yJ0 z;?&}-?DFi~s{G9AlC+A7_|l4)(yI7!{Hs2;5~r%eUqr< zyrjI~#LSSy?4Y=G|LCM^(J^tARsH*xzCFydlcnzr8@KlF|rVbHJ*A3DY@CnF|| zbxw?IG~(@ghW0$8JMQ5f*YK8OY}eJh>n`g*h$SdE!E43&+jH(_+;UGax}unH(N6~W zgzW0ZTLA9Di;OOMo#L@k1~Pwzm153Anj+?O`p8{=at|07m~;kI9P5&m2OTWK@%SXW z=&!AsJ8T43pe!->iqSDvUxnie7!s!fp~*dbnKzJvEps9$1<$hwsO-LSn=hqd19H3Z zNzM@{lsXig=^~#3sXbWX@$0}&@@w^zI(@`$e>nx^0&N0NuZtPhyP~>VU9*X;l#QuTU zdRc76(;)@8j50+5ae?lvUT#vyApt54HTQeZ$VrPH-Y3#-D>3` z54iz~xK;FB07ft+P;rNmOC&~86HjdekdP@gu$+Pwf#EreYl)d+VUuYAb!&oQ3I}Zw zF^+KZUbf&fRDKjjSs6&#i(AHhfhWT-MM*Gu0QFr0`7vMWMc+jQ_+;ZmYdIk*d!!Ci z0(y*@tc)1zbux$ra=Um62Y@gP*g#4%Ou)7X3UkGU%Tn05#cUf8g$znTj}(i@0q6}U zzA{CCQ33pf*0i=nOTvWK?ob(s$Qe#bU21tgIe%J(q;4gvbyAfR2T}|nJUC4JnDW8N z#P7$OKnK17#eeZD>oms%7ytnO^hrcPRG}4dTArXqXyR?i zA5y1~GtC0whbk%VNSKOiB080moT$l~&P{2Y6y)!UQ}M{Q-yEv60>UX=GkI~GJyvrl zCSZ=hg`_&58J$A8&}2^=0)ks|vc>$38^_H?fW0XK3P=?X%NnY-MgTZTWkEhy6qJ~J z!0<0xF(LIbMaiNCKU#^T6*w~H4?&k^N!$sB@W+rCboPw!utX5)RIVtsmsBF0?Eu{} z`iu(giI${w2LZ$pib~}WFz5rW2!%sLrUV`lz+3GMQ@ey(2sdWZ^D{1@aU7%c2$kM& zIVp!BGDeWzz>2pZl66M2j&RlDsMq?N)(ttH`)n+PB#- zy3;zb-!XY4n>I@(O_C|IY~H3^uqhWU?5bVA?(8JRsYAEwFl@L$n7U1;e$%bnbZN1% zdOH@*1Pxal!&x_h?VJ=@;yZFl#E)41+5ZnzEWPTeYK*51480+jaco}l#X zID2+6v%0WBhNE0kg#H7sanp%E_U(F%+cpk&&paNv41`=l!vF8HhyDf7wNr4QZO&wR z$I-b7`gL@z+k3aXs;OP&)HXA>t(e(VFKp{qkCZc85c=^K$>fH7Y8!kjpWaZ+uF0mC zl{0JX{JwH(M>4jmn%mJX9qLz2s<|!2%({4V5uc!3F;_rY9pm#&gA>xp<&NR`j={Nl z<7kb3u&!&kzH6vX+gGK~m5P;R9jY3+uA+k}X;f8+HB~YV$g`Lt8?(i2>TtF)a! z-!g@|SfR~sQ|5LsxvkQiR%wM?TPkHrWo(VQvy$y9XS*si#wxbEUfWxx?yP0IYSdko zDsZ*7NR0Y0I5{{uzgb$`uB=e$i^NQkn9XaI=e9^e!nxqm#`etG*0icdkZ@j;G_OUL z*P={o5ErzoOWW01HLaO7E%Z~fG^<6HP|*;XR}ocM9*~e7kz1bKC>Q3Gr<64%mo^2( zWhIr=M`oAB=2xayHYFC<qw~uO+m#7`>LyusngDT+6_G+rU!Wz;fHbig5l$)`8vlbh<Dok3qa_|}bnn>6E_%z}yN3?%=-YRX9FqB8A7o09^Pc@IDG1OP zA|N(~O*gPTNODlTq2KoCww>&jmEAP!cWvE=-p(CQ*UoY8-f7pKr~ANz z*QaaO4xx5{GN8C1r*`k)<+AqgIs3NlLwoMQ9XB@mcTgD@P+zmW&&+Pg##cH<7n?^` zYWkO|`j%=3HY@Z?`Rdtx-BNznYKn3(QMMExp*`M5L;qD4~)tl3$*S~jh(MKBT|s63p?>E*pgJ) z8dtF16|BLu>kh%?FjLSZ?heN>KyI=Lrzw(bi#R|H(NH)*TBPPYcQ8dlVBj;}P{vCp zok|C0nNana=@2cIV#>hef82I3m^2~B$NZ9=UY zh#OlgPpnr)*GQvl<%j>>|Ks`pyco={Y746s zN7TqfRnnLmX=GKKsIo1hsQC{cg#6&&e*eo?-Vzm6CD*osW|L~$QW`pv>)KN4+EZ%V z(&{?W8>PwBt-xUL>L1Sg{_3sEzj^1{TmHg$(iOKPRW~Qsv?rIhB$hO%Rdl3PV7o1` ztSPa)DY>jUsg#RERnnYX)xlA5eIn4es395q%NkQF8dFMZlZ%SeN(!^f3$n`dGmA^o zN-APImMyL`QgcVq4DW{ zk#T-eu~)){^5Kd8t)sym%OK`fTEC<4SD0$McdQs^diU&o`}V;-&%lmzXxB-hp|?$? zYQ14~ci8M1v7Qdn>fQ;BT6^C9UH8b*@z{ZvG6wHE$Bx{CN6ulB7iXZ@(TC2#L-**B zXY9Z;y63{@@V0$$+u1aB5X~F}sz^f{!jLS-XKZBaD<_|Jbmkxe2l6!lK9J^C;tG$<{rRc^vd5g-LKJJEan)y@DKSjaX3+!0Kc8f1}2aD~1tw_lT)eZfsE zjSoD?s0uRopxnZ8fQBm&7Cm!E(kgHc0D1wLoG+Z@MM#Qq2Z$FJ4V0hw%3Quu$h6eLH~_#!cu%x>7X8PCn+j-i1avrq zB<>)PE$4+B2t>zW0C+B=Dh15~9!ng4lq=2|ARz~7(svSCQ*69IiQBK8s&}1q*JfWX z{jxuo2$T#=TNo zgw!0V0I-olj5Moc+)GBnAg*vw1eu;5hAAwNut9VJsV$uI8Ky+IGRX0^Ajwe}!)aoH zngB>Hb`-f2lcBF8RB8*69R*4$taKPK0|-jasKjyr7$6p(TyQChZwPvc1i(NVv$MG2 z7~;c8HHsriPm9S3tq}vZAih($rlW9x92lOAnQ2M@1O{^n74QgAJwP4#D3YbC zSxDiRxiwdXDL`Qk0eetx7_zknRfbaxWbzcHiz-2hfF%p~2V6u!lDNngmT<&h_(nNJSVmT=0i0MvH^*!T)-n3~!rNO=TY50sO$v=EITw?r%LQF1G_ zJQ?MZDKGjr0hrPWXrV=7nmou*S*S$K0b0N$06aym0?mfVO#&8GHB)UBsDKHT$1QUT zb4P^QLjcAS)ADhc%8or@w5(3qUxBZr4`;~X6Hl2UY@xyw0Lj8*EupNLViaTefWX1o zhtL`XzzG#lk@lFLrkZShX}y4Phco0%%NY?^dEB7G>1k0JipvFY1a+hA4k|Pn>O;oW z=v|<@a0M9&yIM zQb;F`rAgFa@bt~3kL990fis0@G)~Vz`dEc69Q4a|j6jT==uw!(@w~|Tj=Bd(I2ho~ zknRE<23U^9p+GWZ#cd5yqfKlO*TOk72hzdPS_y?phVVge5M(k21MyJ^YvpSpf#G0I zJ&v9rw?!z(o-l&?gPZGSM*d}&{?e4tsg8MXku&fYC|?}oE` z-9dWbHCrbI?6nUa9S;C^x9L~?iru(Q+kJbke(c%w3~ZnDZyn>Oo^|Kowuj>S>=6c^ zQ7%Wtr0ngc7{B(uZCB3*1^eyWb98T5dnkDfh}YJ==P>R=WOo>MoL$?5!FnzL66eBd zZtR^*uao7pu_dbBv?fJ~%2=fAs_&cbT(uO)y0NoB+8tjlE@%4lB-+H<_CmQatxhf; zUe4>#_HJ6F6N`n)<x;W-C*oFcwJlMRG&FOq0xWu;#+reyvgWmCL(onO=ZvJ=@#TGgPBD=C(;v z>Y9`5+mai_33VOO6%Fw8;`%u`*Yp#-8N5)|AHf zr25vR`Zj!~HA_<(TC!Wj1!83dV?f=?8AFLmU!v5Vl~Pq_5x8B(pyW%H720m>sZe!Q zXnU%)eZ^vJu~c8d_5%J%cdaz|C1ynCS3rj2r4=UEw#1b;zZ)7EonPe}l^T{_%u6Xe zFNpO|$hru5D?Yna*5w|GT$+_BpF}r7+GqgfX~YvqYH|ubvf{O0(iV3o>-Pn0GP>mxQ*60Hsn(~ z(#dW4zvjx!3?`Ri6%Ui1X zEoL5!y``L4M*(AN7WVXu`?_TcL#qy?euMTyylUIV-ip zg~~3?(rz!U5V`T$y=~Ru%+s63rM;m|+sK}C{Kzw5@=jS!ryaK@>^CQzx5pi~M{PGJ z?6*9F<2z>&B2$kPe9Jv!+UA5tWz*y3+OqZRdW@T{o?VEr#~3Ag_sJqdzhTp?+cc|I-HKJe z;ZUzxWs7^7b&GoCP`z}hoIjGz9f@ZTWOJ7Gse`tuo#x5S#_`RT(VeEj?b@Ezs_wNi z!&-@ctw6Jy%dBL|7Lp|kvGU~@6y-{!Y+0yU7pb*8tMI3`R*vuzlK40K`O4W^J?Hv9^V6ewvb#Kdu? zMYSl8ZXQxP#Olp2i!(90w~-f=F0*p<-w046dte zfw<_|0s*E=vJCg&zy7#1=L2;XXgY)(zbUR2w_zhSzQ)04Ak2_>5G~t|ZWmuu! zh+0)t0~^!GM%JlB)rzQUSyZ(YpQ0Lts74)DDUPXE#Wpe#72>E`WmJVSrb6-ZwWy!| z=R1G&6$&bvg*6>Q98)PFU*8IGM0uO2qB)|h=?@?9e)zv%eltKw=v&`G8r?c^YOOfE zib`sYB!LjODWkSIuBh_8fbieHcm0Ei#KfBRxbphgiu(AEf(om}1+Us9iduVuCI#bwEb9QT%IrkCX; z6y&88SH$KNCKi;(7ga_TR)yzQ^0LY(7({w;P-;Fey(l!TBs9GwB)yQISsa>OEXXbs z=2cO)-kh?ioU+)wifC-)R>kC3M`o6ZGK*sK%HncLqsfxF?5*p;;n{hiSvdhInL#Nz zy!2dtW`1Z!0Y9@aC^;vD!Yk$mCTIF5rUxWu0dzwWb3>8}gqh`$Syd4k6=7*5qO1}@ zYIaykPDnyVU~EEQT*8&;=$?(;{$2CXKBizt_pY^P*M=#Zi*!I~ihB1w-TU@|Lo!S0 z$CSNoDOTx*4(uala`Z(ZzQw4r@9IDB3>_Q~9e79gy%YOHxhNCnld&W3=z)8f*4Lba zdz4<7oQ=1w_&R_=X2YE#UjvT>DP2J<`Wp%7kk)vdq5{}MHY1Ro$gtlYtaSLw$>|sT ziLd_ni3BP#Gxk^7DCCu#;;mAE8ztC}Y=()VL9pCUA&kpcPC8q3R0wQezxxosAlSz^RKJ6vPgv729|cdoYnd?wZa_)nhz3210TW8Ju#h zBq8TMx*bY)Z1I!hrkoTy3jio{0pkP3I5}mnq%gE(tm?SV_Jcj|j&R{9osN?DwTl`3gZS(K2`cgO1Xib3?M zaFvx0%9WWpn`Z*oGyhL+sQ_^0g7gpg#4yciDQ%brcyeMz^bfQn{sJQ9XvY>Jp@_kh zn^Z=MIDyg*5&)dg-#K?jQh6!dAzbd$5K?mD>ZB?JCV|XD6_hE5w9w!I`F^Mhpnkwp znlJ;27!URf6%=asI%nU%{!sqhIH|8u5;+ZZNw@h`TW0W?b(iW+-MSy7u=Qy-eI|UlNlO|v% zDu%xZFsl(r1FZ>Y!$?<5H~95c2*^1tD>sLMT0TS3M%=fgQj=qkI0w$>e9YO#(trvi(O}GU#IHwb)5XyX@vXboEc@(Z0 z)iy&w;Xo7&@5N!%@(OenT8^qJZOiVg$^0M}jg|HO;2Zy<-Q<2A)Gf0K2lXVuE|x}5i}O3e)E-C- zcy>baD8|`vUDCKxND>v!LuPp3c040mR#$<0J(0|D6mu+6d&vr2>!xR+#Zy4DyTdf( zz!r`ts0P-eE4qX#63EbRIZs`C5cLOi0B0zKHk~O%L>e)T+*@1NHhK!4%TecJ_d$QXNbzRej zW!vEH4a1V9LOq(@rY)5B00}zR?Zz!Ta3Z@^m(|GRHfyt**}OJwRtuZasLW~A<+QTd z&6?b1Oa_pH17Hc(vH=-qHpY}<7gHu^X#Xt3)UzJd_&Nie)+@7crw zo}M*l_nH$MMh?{V>lW>%S+j1^Q?y}6=ena8)!z8pY8t0HI|K3XwoS8U0kpGgWU#GS zC(CW+;t_74#yG2(Jrs{^v<@xQ_s_Hr%{BGSwDiul_Rq8pa2aui=R1ZL8oQ_D6B~-@ zRn^S8d}>uWyFu#2nN8{Rs&oqYwBA0k)G@v!n^;y(t*K@=aF}d-K{_$lF+9`JJJH@V zF72IaF$^gN=GwZ)>N*E&4TJT@(T46ZN=s`TY3dno?wxGzo5UWJrrybF!%$7vNTs&F zwsWLXKY)!Y-9V+LuUg$x%XHVMIxAJ$tftls5G+b#2RJsZMV8s7$dhQYJ5*VK-sbjP zsXVhomL-;FbjWig>O7eyy+fJYB1@Mj(mSN-E#iz;X-Z>TQbSuxqc~r}R%rVwG<}u2 zzG~edE>~q3sx}Oks=Lb6J=L8<)%romI%S&9n$G_E?$L5>Uy-7#QaeG^k9=}XIklx6-)1LKc9jzwvhfx5+?Hx) zQ$4?>T>wsRmaBX59K|E^`qcy7${zL?D-G4^Ud8a@;JRgU-!px9+`n|F=%4Oc++XzE zTRHi3-gUQk!=j#9?ccI2dGD{Be7xxSXzBQqdC%Q>@4YG4?J3uX)1JFi&O1}id*kMt zR4lhgtvA8Rpk;z&(~Y5{(;*XR`NJXW?LpIxzN6y-^XY)~W}oS1-@dnV$BxhbeXnuH zK5%dXoW^j`cW|?N@6@>GHEz3%+m7b(MakTjcEf7icN_QI-8-J{9aksb?rn#bOK1#2 z#&E=}&;lH@WMLL9irFLOoLM$~q+YbC=FPIXqmG&Vw#mJY+5MJ@owo7A#?hU+(d~-C zwTj-&GUIx&eyzx`Rx`LGojDlTvoG6kY@OaW-T#aEqt9%g{?+-{FP#s*s_k9P(d;BD zt-3!CPa+t-U#+IT;=7s&c$cFLc=Mh1I&7SrA5ek zqI9R>YF9K%3zuP-3&62zcckW2#9+4akkK#e#@Rr&m|QT?lYqnph7htRBHfKJ_LxFB zK=LMYAz&?zr+<+n5>7~tDqbE-*2R1XDrA%8jU>MqasWnw+(Nuc7DJelXla$EHUegF zwVlVh0Kz~$zlfz78;`X|Xpb?WV0MFuMeI#LdlI7dLaYaV1E&d4$$|{i)-k60aHS`d zjHR8|m5xBQlgGLtN&72YL8Np3IuUf>*Gvlt|62QvDflc`7Vd)dU7QL%NP#Jgaya2% zI47DB;s;MjvJIpo5oSZlUS#uBOp_s|mW^&=BO92AdSy(r8i-5uTceDrSI5^gF*WjN zf?+C=Rg(BRRaA{EvR00N#a1%^_Z|K}{`SJ3d_}_Yw&1+#^HG`C67qvHN_m;(LLg#E zV@zf1<*3Xb{@cs{=e3JzHSH<2ElE`~luAiTl_a6OHMzPYm11JGr&YE4gr&Xb zAA2P{?JZx?AI|yx=8a3gf8&Ff&t3cN+ZX@v{ zetBeWX?RWrFQX(lttcQNJ1`;3FFrLWF(W80J1{QSH!e3IsVF$Tgolz*5|&lLOU)N% z6^CUMhNR^OrvR0U1(_uv+pz2+VOF6qxA?L!_G)xuaC-Lj~ld4K(>cpFH8^YIgD%O5~drROrwhX?x^j?nDunv zz%^{a=W+kRF=x8s0_#p4o}!E&ct#IVjyWF+*YKXRf7?2==Nuy27Hr!gqU7ne1rW%A znkq|>${vW$kN&KrP@{leh|qY&LaA#Jrc&AzIX1{Ep7WcqQ9tCCq5>6G>^~z$z;FOw z0R<)H4p6!&nw{K^mz2X}hp)^^ZWlO!k}BCkSn?wPWr58ClpYXr5K9r}a0WVtJ4p~{ zXAz`u`IF}z0VkjuFF08{bC~K2NLD&2I~o_&6)#Jm#DZ(lG7~AWJs~m&kACGGk2%+a zAgP7ZCp$pe0oVr0a`udV;6pA*J~3RHHG8Or6zy5DNKXW{D`Lj0F#lkfGl;LB9)VZ>&OU| z1;7kmMQkf@4KWuSj`J7PcIVlJO^~fFPO6J;U0FzEqfgoJ8hak|7aS4GPXi znRTt8z6dpG(g<fw02=2H_EeOCH<|^hPi=4C^LXkDtro$Oh2JYs4WpS7nF z2PUD3$WDp$mR5n19H&Vu%UDAJkT@++SxNrVR6W`NKih*OhXOn=)(IvIW-Os<8WAW> zF`gG@O|%u-4JfN2%oedOk=6sUCGTGi(Aq+%DJKtDZsh{4L2DD(29<%SKxMRkEVqS& zC*^K>7Gwp@xwfjDkqk)ENr|0E%W4MQMc|L%H;Mvc4Pi{+_D~k@n>AQr0ZT_HoDmGz zk~FB~=YtzmTktr-)MjdPEl}2iHmA^QEHOW4WlfrMp&GQwwKm77)SWaQQpT#i;B(T_ zQz7Ll9x)0%YjTmbVdN43)KPhi8eE1XT795p1yZMDx+Rxmxg9r!e*ri-6*P4fe2UnU zNbQM;J;v84_E@NLk)5{MhC2#U90?c-ltPS3D-oEvEtJ(+Wd*=fpJ$ITkVU9ZaKAW1 zv=%ZGCE*C=G{zAsZv+5XeM~)&B|E)vjYFvM0@p=oPjm+I-L-QM%N46}#xPD?ov$R& z4b$z1Yt|C<>nXZ5K~?v=3H87BPyXqv{y%#u$Tz7$Ja(v>x5;NsvgrfW+#$PYQZJGl zs(Rr-yJ*oaSPjcA{fet|-9<@3*B!b|mtoth+ajk~Aa3`LcX0paz|QHw_6bPx>sYmW zC;j_mY1y}XJhXSSZ})WI@aEvbjh7^5!T^p|MO_y?Rr$E)2Bh^*v$AHk-saqj`-L4IrVb$8XW@qR2^V;>9^=w+LQZ}^S zz2@lKcIj4+&OlecW;xqFgH`RyA$S$r?9x8ADg4!j^~{3Wuwyrh1cKJxRa;RS1Rn6~e*N&K#eRg$Ev%H5Bs^)f;3tP(NUD?96 zY;H?FyDgjEl1=Tj3@jDNdyB-z)Ea52qC2s?HLkQdqfQ=M+}P4Lo!+DXhP4eYibs~D z9*eSR_xRb zptN)j*QtA|R6P}{?sAo}MmJDr04omFb&k{#LhOY4(sJ7TLEV`~~ynmbY&T9ay;A@(I#w`MkV zq&2iPI}+hh)f+%|2cdXSFM^p&#Qe>L>SDqUZty1Po- zTgp(i%b0Hbw?t*g6RXRW#-QZFjC#2+w<;t(|J`6AFFE&|Uub+@b+xjmRN0AA#&iLI zfzx3A3NXKZkQM^)**#io9I4h1)pU;48b_;*Xs(H-zL|!enR?@NgK@g4XBOLyy|Z_!H`_S4(9l0uKQL3%J5ITO`{$ba7Ml9zn)~M)`lssqCmRN) zng?f^`(|4D=CKp!X&qW_9b9f5SZNzvX&+u~9$0K0U8x(GZy21%$=ipQn|kNl2AA3f z@wo`bZXH?f7+Gr{S#BMkYa3k<1C_^?+C~;S&}}A`CDZG&neFzmRq%Gl#FBJ^fLSrI zEgoG5HcLiVRpz%P>NQ>$8=x<=*0bL{kL|AuL3 z%R0R6XzQJ<*AMlq9O#$!K+A@u?d9W-cJDpdyZ`0>{Rg}EzC_vj@N?|hx%L6)|=zj(=qD}@bJLl$*ASd;1T7`VitA{8)nGOkd?v4J^L=> zzTHUX*{;DO?~v_QpZTQUc4|EG4w!HE?49-<-0I%D(ZzXB=+|8Ob*FCKPMo}H$JnLc zc43=cHmR2_%&JwnY-YgE8xH-Zi^XU_@NCwuU;s2h7Vq662kgOp=LqF2bz(?q8{4Sv z+bA-wB`an!wM*%WOPU1W&FI3v|3kt%1b z&XmzcFw&6ca{}>=9~+lld&o zXrMuWD`pvgOrRP!3WfX9cpxo+S1A{|(iy~pQ8^1BC1tt8yd24%2r=V8dd7b>PEto} zoqS9pnDjN+>EvmF&33W^gRH{X$*vn5u5k<0&QQ+zC|Che*v;3R0BeKQ-e5IF@Cs${ zt34Q!8-yQ%cE~yQP+DcShNvCa@ss8_MCBo?W?HAVhG{(l?J@pKVYfIBWS-nbiIf;O znRYXjGCG{ol;B2!2&6qEbK`_$UJl;2MyXA)ty59;nuK~Lxq(TjS0^?xvGod3oiwh6 ziEUxyn$$6M%IF$-T!SjQRu)w&O=wg{Hz=d)6tQ*6$Z7&$VYyUPD*cbQLjURaAG{V2 zEh=dX&93|TA3pf`ug?9;AK(A?*Dn6!Z{Pgqm)`mDZ(jN7umAjmfB*e2e*Z>TR%t?Y zOLAR1N>WV+xHqLhjf=eZt_sdw;lFgw z_oa9JU%3$Q$M=1I^S;mT&jlX%YFQ@uk(tWmSpA zB`Nt?nI$P%WocOzS#kMU(FG-I)bQCLbbFS&r1p2thi4N1-lN=V}; zXY!IWLsPOt5;6p7xuMvfmJ^ndAC{6WNX+ELrH966@uE}C2ZqQ-rhB(6XC}(MkpE?G_EFZ~BN3B}VQ-;G^+{{bZv?%g@A88{Nk_W4@$E5b0mJCNQ5 z8ZVFH*Ep}sY<>iOq=AEED1cB((Lfnf@!I^umD@gW40KI0=w~2;9KD5OAQ(6(q;Y>kb5> z%dC6?W6JOC2Y$x;h}*>d5*ypd*;wodkZ|CqB2#c28OogCl|(~WM=I3sioQi>?_yh)n)d8+36hBXTf=|>LsEkIyg9wyc zucMN*UPd7d0;E>*J(k!whYPnaDn=&dV7=oI`H8>Sa51r(|?Z8J?)f zIRE5!3r_nDO8HCiAv|HE%LOKZ%|K<=aAFTLHo_QND8>FEciBinUktS#a`gm4sKA+~ z2sPM=v})m`j|Fpb3NhkXE13ZUuGAn9`h*;hIRISBR~&^gb|K@41}9L2Du5`!l2pE= zh*Q|WZ#>o-#saEMoK-UsBWMJ`DOBKKip0xTm{5U8jZ36*Vf3dYF6cD0Zeb6is6v!w zl$4w}r^-R`Avhyyf;#NM&60Zw8GD1=D3c*MKXU}my)78=g^cTKCtlVO%2+AnFQEK$Sg^wS}miA*=(FnUHlwXmEZTpQX^y ze6$R(oS`KEqGjBy%pIvd6{@_PU9pE-zW}#81gwKkkPPs)MY5FQRYYs#$GBNMqY%oA zo+rN)h90MDLCoi|eJL{C87@HDBlm9}VfCx$vz6l;som}9l}1lAU( zGAC&)DY~O9!%C$$FEY@Uk!eTuAf>p@|nl694#q zU`$bK_nNzN4Fu>0*^+zhmX~72?vPiWansqqd)&7}rI(oYxNjFT5SW(p06aME**or| zI6B7zdnd#Drz3kOqX##Jcd>C3#M`^=892njDbw4}5uUR?Lt>M>w;Aq#T1(>Z{H5oREfg!4bd$FNeK0v{SdI{iq zK;fq54>SuF&4QU-1Q=6DEzRfu)lE6gGD-_sR4Xj3jwq^)t7?g@ zZH}sLh^nYbs%uH9Z%?glkFRV6HIS~XCi=&Mo>qN)|_07w`>o?I`^YE=Pi zLCfeB)Hj%Jv{EsnFV%EnCw{__rA!a{K(W$Ttm-OJV*@;ID3a?6WV#}`Ay=%)q3*#} z$c^Q)o=Qc3xvaNZHHa(LY6ePGU0}arm62LTH-sKo(>YwJ8>rF^RcHrmjN_I1k(y30 z@KjCLL|xYeZFixJf|kL?RmSleBlb_$cTF{PPXUh`dI^=eNXU~7-IL9|a}B+7we%JH zDXe#W-(+3?WL@`oWB*Kj?^I*|RCE6f0K2(=uA!UE0h;?3>wBgNxqD~pyQgamqYcJc zuy>siL_OO)z~S`ZeEk4mn*g|Fbg^}Kv3Y2wePXd?WU*z4(>a5X$5vZMmpVwjyeuBy zY#&{hOl*lqSIJ0wY(+A?**?C}F|kpp9f>Tck1cJLj;<$Eckr^y^5t5|B>p(BnqSwg z?&DR04B9?2UntRLx2khHG^)AHfnC?Q>3Gh5Ytemo&UQL)zq#PNy?*1Pt=pe(-ulbd zhkpeOZ{7ZE_wHx=cRsg%^1$->1M_EJ9e(^^>-HzZJ4eHNmZQIXY5MfR=#EJo@ZIBw3kZ)+E}>$;{BTe=fe8v^AKCIVV_)<6UT zc3LpMglb&^tusvT5^6ockg_z#LajGU4f^$lYe*^xW$gf0Oe?`;zvc?ldw`A{Gkd}@ zp#z37?P@Vsk{}Vmc=>9Md&z#7W?rENTx}O<9pPYLvaQc7mYYwO%$^X|LoT?)v|wZG0mqX6GiW(XSFQ+!d2+taP|b%CblQR;6zB8a4?Azm=UP)1Ztd=a31oE%n~79ifZkNZ`DUN zC}SF#xF$BHRsjG`1oYObl3R2MjSN^fs$LmcBaLlP#WpBKl^szvGRpm1D~qX?#ns4T zE5%Wz?J=d||9mItAAfVsCm}Z;Z$xI(Suzc8k}A)&H4rB*^Yf$QW+b&}+&j?^k~T15v)IHg*gQYncq zX^tyy66KW#r{o00XZQ#cu7t*242iiA68)wx|Ib%LUc18k(|dk@z8Lt*#o%*6;X&!y zG3C{*TSKmN;q|J(2X>}T)z`(KNW3Qo-oOV0z@3es};DS5nv zTz*PHa8g!Ka_05;l#ry1(1bK$YK|Z=D&^{;_F(QKV9$%Utap#n7Y!b{ySI)iI|qi$l$L+ga?+0j zIHu)_yJrtT>%sP@$un~39zJjn9XN*$T%-FgoUMP~g<-01mx5sRohgBL?Y*0}_VI%h z<}h4Mi!VXIKZ-4CCs#)WbuE7-1riTr9Aw$6w1JR;2Ane1NlHXYfo_Wn4a_aRoxV0wkUw*=c~pOIK03 zfwCb=8X~~<9Mh8amFf)GW@&{Ba&kDAbA;5Mq;ADk$PPJx(`*N+aeja(=YK(d6Ql{k9P;EUvawiT5 zklXwuW?!ill@F9rwx>`zX9%pI9Ojo>k5CQL(sMdwJP}f!qcKUP@1bf-%v@|UTon7g zL6m}yOaRaU&`-V*fU1nC&9LbZj@m;@-GU+{+1N;c9`7lpHu;@SW< zd$9D7NRNDmfq68hXY&7(AtLJ!>awLvvG;sTb0bVB@#;5LG>h2dI^na7yPbCRMfft+0-N{poTP_+fz z4%`*eLL=!>fw&xcafZ;iBByXhorNH6l?nA1Q6fGo(3gB=S@Krpbc4i-n8Sn&+Jpd@ zXqGc72IPnsr%3Cii3&Ka^>7MUfD_P*uqsk>jDn_!P*|fF3hE$WU6fM?ofXJLj@C|r z+QHX4cv?%C9{X)!j2%c1<`-(nM41o~cuy{%UJjUv6}d~Gmjg;I;Vc-~E<~-=_^&O3 z@EDT~=?o*aj!3ONO6L-394JDvt+j`;7CaR4{^gXkR0j=usyUb;-8FCyuo{deWhq29 z+25)Fhi+1pt8qu*GU3{fvc- zn8F=K%}aL_E_af{xg3v%vQ|(dt2tuS5^ajqCqnfJ4HKA>S$nvS6xY-aN_)7H;xlo9 zo$P2RGzf+I#IT|?^C@r$^*6HU){%xbLQBTOG$L@(DMM6tQVydFX(^W@+7pGI6zfDc z<5C&h1bPoe0#jo$CYv-0t*OK8PMT*tO)x(_1&tdpPb&ztoZ*brp&4oA0v!(pj~h)C zt#ON#c;fa5txc$N;8{edts<>0O6!Q#IU;q|Xq`PpZBAtma=Z8PjXPDtrsgRNvv#v{ z`}UyulL6Dmqn1y`tsjk9?~YhM9Ch9uu~UG=Dc9|3JA}dyC+)XK4!r~WF5{+^V&UvM z`nDaND@T1hm>~#aDTM67je&z3L#A6JrrX#avD_N7-kz}E9k+fsY&sb<9S@T2;xQQ) z9eTzN+~X!MWgR#?88dk&%qLUUn`5TaNl2VWH^+}|jUC>Yu-+QCeTb8AVQFvRB>L5( zoHl(yo32FGYgn-Yn+==h%Ff}mCPikm3i4miCg+Pw?m(`73e|YVz}C)nb5rkRa-BG> zUY=g7NUM>jH7GNiH5KY1>EJRmx2;<}&`^}xBg2N-uzu9Fan!wT>RLZCtQJbJ*Q}^k^)~2+o4dxO zql;}L^U7Hu?uu$^4TYIp0}LysHzeaL;)!L+_!0^;yDghoV`jEhGds%ZZTaMua&{Y+ zC{pPXYMY`;D#LP%L(®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

    R@LKtyZI#D%9jeMc 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

b?~?zy$Vep$gLjhe<;<^eZ2<9SW&RDZv7 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 BFj@cr delta 185487 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