From 4a406604bd4bd17cdb6176963f9c8abfe1b8d470 Mon Sep 17 00:00:00 2001 From: TommyC81 Date: Mon, 20 Dec 2021 15:59:56 +0400 Subject: [PATCH] Core modules formatting (#1670) * Update Fsm.lua Code formatting and minor typo/documentation fixes. * Update Goal.lua Code formatting and minor typo/documentation fixes. * Update Menu.lua Code formatting and minor typo/documentation fixes. * Update Message.lua Code formatting and minor typo/documentation fixes. * Update Report.lua Code formatting and minor typo/documentation fixes. * Update ScheduleDispatcher.lua Code formatting and minor typo/documentation fixes. * Update Scheduler.lua Code formatting and minor typo/documentation fixes. * Update Settings.lua Code formatting and minor typo/documentation fixes. * Update Spawn.lua Code formatting and minor typo/documentation fixes. --- Moose Development/Moose/Core/Fsm.lua | 905 +++-- Moose Development/Moose/Core/Goal.lua | 89 +- Moose Development/Moose/Core/Menu.lua | 444 ++- Moose Development/Moose/Core/Message.lua | 318 +- Moose Development/Moose/Core/Report.lua | 27 +- .../Moose/Core/ScheduleDispatcher.lua | 199 +- Moose Development/Moose/Core/Scheduler.lua | 218 +- Moose Development/Moose/Core/Settings.lua | 235 +- Moose Development/Moose/Core/Spawn.lua | 2964 ++++++++--------- 9 files changed, 2646 insertions(+), 2753 deletions(-) diff --git a/Moose Development/Moose/Core/Fsm.lua b/Moose Development/Moose/Core/Fsm.lua index 644a04d63..e34e39eac 100644 --- a/Moose Development/Moose/Core/Fsm.lua +++ b/Moose Development/Moose/Core/Fsm.lua @@ -1,9 +1,9 @@ --- **Core** - FSM (Finite State Machine) are objects that model and control long lasting business processes and workflow. --- +-- -- === --- +-- -- ## Features: --- +-- -- * Provide a base class to model your own state machines. -- * Trigger events synchronously. -- * Trigger events asynchronously. @@ -13,66 +13,65 @@ -- - to handle controllables (groups and units). -- - to handle tasks. -- - to handle processes. --- +-- -- === --- +-- -- A Finite State Machine (FSM) models a process flow that transitions between various **States** through triggered **Events**. --- +-- -- A FSM can only be in one of a finite number of states. --- The machine is in only one state at a time; the state it is in at any given time is called the **current state**. --- It can change from one state to another when initiated by an **__internal__ or __external__ triggering event**, which is called a **transition**. +-- The machine is in only one state at a time; the state it is in at any given time is called the **current state**. +-- It can change from one state to another when initiated by an **__internal__ or __external__ triggering event**, which is called a **transition**. -- An **FSM implementation** is defined by **a list of its states**, **its initial state**, and **the triggering events** for **each possible transition**. -- An FSM implementation is composed out of **two parts**, a set of **state transition rules**, and an implementation set of **state transition handlers**, implementing those transitions. --- --- The FSM class supports a **hierarchical implementation of a Finite State Machine**, +-- +-- The FSM class supports a **hierarchical implementation of a Finite State Machine**, -- that is, it allows to **embed existing FSM implementations in a master FSM**. -- FSM hierarchies allow for efficient FSM re-use, **not having to re-invent the wheel every time again** when designing complex processes. --- +-- -- ![Workflow Example](..\Presentations\FSM\Dia2.JPG) --- +-- -- The above diagram shows a graphical representation of a FSM implementation for a **Task**, which guides a Human towards a Zone, -- orders him to destroy x targets and account the results. --- Other examples of ready made FSM could be: --- +-- Other examples of ready made FSM could be: +-- -- * route a plane to a zone flown by a human -- * detect targets by an AI and report to humans -- * account for destroyed targets by human players -- * handle AI infantry to deploy from or embark to a helicopter or airplane or vehicle -- * let an AI patrol a zone --- --- The **MOOSE framework** uses extensively the FSM class and derived FSM\_ classes, +-- +-- The **MOOSE framework** uses extensively the FSM class and derived FSM\_ classes, -- because **the goal of MOOSE is to simplify mission design complexity for mission building**. -- By efficiently utilizing the FSM class and derived classes, MOOSE allows mission designers to quickly build processes. --- **Ready made FSM-based implementations classes** exist within the MOOSE framework that **can easily be re-used, +-- **Ready made FSM-based implementations classes** exist within the MOOSE framework that **can easily be re-used, -- and tailored** by mission designers through **the implementation of Transition Handlers**. -- Each of these FSM implementation classes start either with: --- +-- -- * an acronym **AI\_**, which indicates an FSM implementation directing **AI controlled** @{GROUP} and/or @{UNIT}. These AI\_ classes derive the @{#FSM_CONTROLLABLE} class. -- * an acronym **TASK\_**, which indicates an FSM implementation executing a @{TASK} executed by Groups of players. These TASK\_ classes derive the @{#FSM_TASK} class. -- * an acronym **ACT\_**, which indicates an Sub-FSM implementation, directing **Humans actions** that need to be done in a @{TASK}, seated in a @{CLIENT} (slot) or a @{UNIT} (CA join). These ACT\_ classes derive the @{#FSM_PROCESS} class. --- +-- -- Detailed explanations and API specifics are further below clarified and FSM derived class specifics are described in those class documentation sections. --- --- ##__Dislaimer:__ +-- +-- ##__Disclaimer:__ -- The FSM class development is based on a finite state machine implementation made by Conroy Kyle. -- The state machine can be found on [github](https://github.com/kyleconroy/lua-state-machine) -- I've reworked this development (taken the concept), and created a **hierarchical state machine** out of it, embedded within the DCS simulator. -- Additionally, I've added extendability and created an API that allows seamless FSM implementation. --- +-- -- The following derived classes are available in the MOOSE framework, that implement a specialised form of a FSM: --- +-- -- * @{#FSM_TASK}: Models Finite State Machines for @{Task}s. -- * @{#FSM_PROCESS}: Models Finite State Machines for @{Task} actions, which control @{Client}s. -- * @{#FSM_CONTROLLABLE}: Models Finite State Machines for @{Wrapper.Controllable}s, which are @{Wrapper.Group}s, @{Wrapper.Unit}s, @{Client}s. -- * @{#FSM_SET}: Models Finite State Machines for @{Set}s. Note that these FSMs control multiple objects!!! So State concerns here -- for multiple objects or the position of the state machine in the process. --- +-- -- === --- --- +-- -- ### Author: **FlightControl** -- ### Contributions: **funkyfranky** --- +-- -- === -- -- @module Core.Fsm @@ -88,195 +87,194 @@ do -- FSM -- @field #table Scores Scores. -- @field #string current Current state name. -- @extends Core.Base#BASE - - + --- A Finite State Machine (FSM) models a process flow that transitions between various **States** through triggered **Events**. - -- + -- -- A FSM can only be in one of a finite number of states. - -- The machine is in only one state at a time; the state it is in at any given time is called the **current state**. - -- It can change from one state to another when initiated by an **__internal__ or __external__ triggering event**, which is called a **transition**. + -- The machine is in only one state at a time; the state it is in at any given time is called the **current state**. + -- It can change from one state to another when initiated by an **__internal__ or __external__ triggering event**, which is called a **transition**. -- An **FSM implementation** is defined by **a list of its states**, **its initial state**, and **the triggering events** for **each possible transition**. -- An FSM implementation is composed out of **two parts**, a set of **state transition rules**, and an implementation set of **state transition handlers**, implementing those transitions. - -- - -- The FSM class supports a **hierarchical implementation of a Finite State Machine**, + -- + -- The FSM class supports a **hierarchical implementation of a Finite State Machine**, -- that is, it allows to **embed existing FSM implementations in a master FSM**. -- FSM hierarchies allow for efficient FSM re-use, **not having to re-invent the wheel every time again** when designing complex processes. - -- + -- -- ![Workflow Example](..\Presentations\FSM\Dia2.JPG) - -- + -- -- The above diagram shows a graphical representation of a FSM implementation for a **Task**, which guides a Human towards a Zone, -- orders him to destroy x targets and account the results. - -- Other examples of ready made FSM could be: - -- + -- Other examples of ready made FSM could be: + -- -- * route a plane to a zone flown by a human -- * detect targets by an AI and report to humans -- * account for destroyed targets by human players - -- * handle AI infantry to deploy from or embark to a helicopter or airplane or vehicle + -- * handle AI infantry to deploy from or embark to a helicopter or airplane or vehicle -- * let an AI patrol a zone - -- - -- The **MOOSE framework** uses extensively the FSM class and derived FSM\_ classes, + -- + -- The **MOOSE framework** uses extensively the FSM class and derived FSM\_ classes, -- because **the goal of MOOSE is to simplify mission design complexity for mission building**. -- By efficiently utilizing the FSM class and derived classes, MOOSE allows mission designers to quickly build processes. - -- **Ready made FSM-based implementations classes** exist within the MOOSE framework that **can easily be re-used, + -- **Ready made FSM-based implementations classes** exist within the MOOSE framework that **can easily be re-used, -- and tailored** by mission designers through **the implementation of Transition Handlers**. -- Each of these FSM implementation classes start either with: - -- + -- -- * an acronym **AI\_**, which indicates an FSM implementation directing **AI controlled** @{GROUP} and/or @{UNIT}. These AI\_ classes derive the @{#FSM_CONTROLLABLE} class. -- * an acronym **TASK\_**, which indicates an FSM implementation executing a @{TASK} executed by Groups of players. These TASK\_ classes derive the @{#FSM_TASK} class. -- * an acronym **ACT\_**, which indicates an Sub-FSM implementation, directing **Humans actions** that need to be done in a @{TASK}, seated in a @{CLIENT} (slot) or a @{UNIT} (CA join). These ACT\_ classes derive the @{#FSM_PROCESS} class. - -- + -- -- ![Transition Rules and Transition Handlers and Event Triggers](..\Presentations\FSM\Dia3.JPG) - -- + -- -- The FSM class is the base class of all FSM\_ derived classes. It implements the main functionality to define and execute Finite State Machines. -- The derived FSM\_ classes extend the Finite State Machine functionality to run a workflow process for a specific purpose or component. - -- + -- -- Finite State Machines have **Transition Rules**, **Transition Handlers** and **Event Triggers**. - -- - -- The **Transition Rules** define the "Process Flow Boundaries", that is, + -- + -- The **Transition Rules** define the "Process Flow Boundaries", that is, -- the path that can be followed hopping from state to state upon triggered events. - -- If an event is triggered, and there is no valid path found for that event, + -- If an event is triggered, and there is no valid path found for that event, -- an error will be raised and the FSM will stop functioning. - -- + -- -- The **Transition Handlers** are special methods that can be defined by the mission designer, following a defined syntax. -- If the FSM object finds a method of such a handler, then the method will be called by the FSM, passing specific parameters. -- The method can then define its own custom logic to implement the FSM workflow, and to conduct other actions. - -- + -- -- The **Event Triggers** are methods that are defined by the FSM, which the mission designer can use to implement the workflow. -- Most of the time, these Event Triggers are used within the Transition Handler methods, so that a workflow is created running through the state machine. - -- + -- -- As explained above, a FSM supports **Linear State Transitions** and **Hierarchical State Transitions**, and both can be mixed to make a comprehensive FSM implementation. - -- The below documentation has a seperate chapter explaining both transition modes, taking into account the **Transition Rules**, **Transition Handlers** and **Event Triggers**. - -- + -- The below documentation has a separate chapter explaining both transition modes, taking into account the **Transition Rules**, **Transition Handlers** and **Event Triggers**. + -- -- ## FSM Linear Transitions - -- + -- -- Linear Transitions are Transition Rules allowing an FSM to transition from one or multiple possible **From** state(s) towards a **To** state upon a Triggered **Event**. - -- The Lineair transition rule evaluation will always be done from the **current state** of the FSM. + -- The Linear transition rule evaluation will always be done from the **current state** of the FSM. -- If no valid Transition Rule can be found in the FSM, the FSM will log an error and stop. - -- + -- -- ### FSM Transition Rules - -- - -- The FSM has transition rules that it follows and validates, as it walks the process. + -- + -- The FSM has transition rules that it follows and validates, as it walks the process. -- These rules define when an FSM can transition from a specific state towards an other specific state upon a triggered event. - -- - -- The method @{#FSM.AddTransition}() specifies a new possible Transition Rule for the FSM. - -- + -- + -- The method @{#FSM.AddTransition}() specifies a new possible Transition Rule for the FSM. + -- -- The initial state can be defined using the method @{#FSM.SetStartState}(). The default start state of an FSM is "None". - -- + -- -- Find below an example of a Linear Transition Rule definition for an FSM. - -- + -- -- local Fsm3Switch = FSM:New() -- #FsmDemo -- FsmSwitch:SetStartState( "Off" ) -- FsmSwitch:AddTransition( "Off", "SwitchOn", "On" ) -- FsmSwitch:AddTransition( "Off", "SwitchMiddle", "Middle" ) -- FsmSwitch:AddTransition( "On", "SwitchOff", "Off" ) -- FsmSwitch:AddTransition( "Middle", "SwitchOff", "Off" ) - -- + -- -- The above code snippet models a 3-way switch Linear Transition: - -- + -- -- * It can be switched **On** by triggering event **SwitchOn**. -- * It can be switched to the **Middle** position, by triggering event **SwitchMiddle**. -- * It can be switched **Off** by triggering event **SwitchOff**. -- * Note that once the Switch is **On** or **Middle**, it can only be switched **Off**. - -- + -- -- #### Some additional comments: - -- + -- -- Note that Linear Transition Rules **can be declared in a few variations**: - -- + -- -- * The From states can be **a table of strings**, indicating that the transition rule will be valid **if the current state** of the FSM will be **one of the given From states**. -- * The From state can be a **"*"**, indicating that **the transition rule will always be valid**, regardless of the current state of the FSM. - -- - -- The below code snippet shows how the two last lines can be rewritten and consensed. - -- + -- + -- The below code snippet shows how the two last lines can be rewritten and condensed. + -- -- FsmSwitch:AddTransition( { "On", "Middle" }, "SwitchOff", "Off" ) - -- + -- -- ### Transition Handling - -- + -- -- ![Transition Handlers](..\Presentations\FSM\Dia4.JPG) - -- - -- An FSM transitions in **4 moments** when an Event is being triggered and processed. - -- The mission designer can define for each moment specific logic within methods implementations following a defined API syntax. + -- + -- An FSM transitions in **4 moments** when an Event is being triggered and processed. + -- The mission designer can define for each moment specific logic within methods implementations following a defined API syntax. -- These methods define the flow of the FSM process; because in those methods the FSM Internal Events will be triggered. -- -- * To handle **State** transition moments, create methods starting with OnLeave or OnEnter concatenated with the State name. -- * To handle **Event** transition moments, create methods starting with OnBefore or OnAfter concatenated with the Event name. - -- + -- -- **The OnLeave and OnBefore transition methods may return false, which will cancel the transition!** - -- + -- -- Transition Handler methods need to follow the above specified naming convention, but are also passed parameters from the FSM. -- These parameters are on the correct order: From, Event, To: - -- + -- -- * From = A string containing the From state. -- * Event = A string containing the Event name that was triggered. -- * To = A string containing the To state. - -- + -- -- On top, each of these methods can have a variable amount of parameters passed. See the example in section [1.1.3](#1.1.3\)-event-triggers). - -- + -- -- ### Event Triggers - -- + -- -- ![Event Triggers](..\Presentations\FSM\Dia5.JPG) - -- - -- The FSM creates for each Event two **Event Trigger methods**. + -- + -- The FSM creates for each Event two **Event Trigger methods**. -- There are two modes how Events can be triggered, which is **synchronous** and **asynchronous**: - -- + -- -- * The method **FSM:Event()** triggers an Event that will be processed **synchronously** or **immediately**. -- * The method **FSM:__Event( __seconds__ )** triggers an Event that will be processed **asynchronously** over time, waiting __x seconds__. - -- - -- The destinction between these 2 Event Trigger methods are important to understand. An asynchronous call will "log" the Event Trigger to be executed at a later time. + -- + -- The distinction between these 2 Event Trigger methods are important to understand. An asynchronous call will "log" the Event Trigger to be executed at a later time. -- Processing will just continue. Synchronous Event Trigger methods are useful to change states of the FSM immediately, but may have a larger processing impact. - -- + -- -- The following example provides a little demonstration on the difference between synchronous and asynchronous Event Triggering. - -- + -- -- function FSM:OnAfterEvent( From, Event, To, Amount ) - -- self:T( { Amount = Amount } ) + -- self:T( { Amount = Amount } ) -- end - -- + -- -- local Amount = 1 - -- FSM:__Event( 5, Amount ) - -- + -- FSM:__Event( 5, Amount ) + -- -- Amount = Amount + 1 -- FSM:Event( Text, Amount ) - -- + -- -- In this example, the **:OnAfterEvent**() Transition Handler implementation will get called when **Event** is being triggered. - -- Before we go into more detail, let's look at the last 4 lines of the example. + -- Before we go into more detail, let's look at the last 4 lines of the example. -- The last line triggers synchronously the **Event**, and passes Amount as a parameter. - -- The 3rd last line of the example triggers asynchronously **Event**. + -- The 3rd last line of the example triggers asynchronously **Event**. -- Event will be processed after 5 seconds, and Amount is given as a parameter. - -- + -- -- The output of this little code fragment will be: - -- + -- -- * Amount = 2 -- * Amount = 2 - -- + -- -- Because ... When Event was asynchronously processed after 5 seconds, Amount was set to 2. So be careful when processing and passing values and objects in asynchronous processing! - -- + -- -- ### Linear Transition Example - -- + -- -- This example is fully implemented in the MOOSE test mission on GITHUB: [FSM-100 - Transition Explanation](https://github.com/FlightControl-Master/MOOSE/blob/master/Moose%20Test%20Missions/FSM%20-%20Finite%20State%20Machine/FSM-100%20-%20Transition%20Explanation/FSM-100%20-%20Transition%20Explanation.lua) - -- + -- -- It models a unit standing still near Batumi, and flaring every 5 seconds while switching between a Green flare and a Red flare. -- The purpose of this example is not to show how exciting flaring is, but it demonstrates how a Linear Transition FSM can be build. -- Have a look at the source code. The source code is also further explained below in this section. - -- + -- -- The example creates a new FsmDemo object from class FSM. -- It will set the start state of FsmDemo to state **Green**. -- Two Linear Transition Rules are created, where upon the event **Switch**, -- the FsmDemo will transition from state **Green** to **Red** and from **Red** back to **Green**. - -- + -- -- ![Transition Example](..\Presentations\FSM\Dia6.JPG) - -- + -- -- local FsmDemo = FSM:New() -- #FsmDemo -- FsmDemo:SetStartState( "Green" ) -- FsmDemo:AddTransition( "Green", "Switch", "Red" ) -- FsmDemo:AddTransition( "Red", "Switch", "Green" ) - -- + -- -- In the above example, the FsmDemo could flare every 5 seconds a Green or a Red flare into the air. -- The next code implements this through the event handling method **OnAfterSwitch**. - -- + -- -- ![Transition Flow](..\Presentations\FSM\Dia7.JPG) - -- + -- -- function FsmDemo:OnAfterSwitch( From, Event, To, FsmUnit ) -- self:T( { From, Event, To, FsmUnit } ) - -- + -- -- if From == "Green" then -- FsmUnit:Flare(FLARECOLOR.Green) -- else @@ -286,22 +284,22 @@ do -- FSM -- end -- self:__Switch( 5, FsmUnit ) -- Trigger the next Switch event to happen in 5 seconds. -- end - -- + -- -- FsmDemo:__Switch( 5, FsmUnit ) -- Trigger the first Switch event to happen in 5 seconds. - -- + -- -- The OnAfterSwitch implements a loop. The last line of the code fragment triggers the Switch Event within 5 seconds. -- Upon the event execution (after 5 seconds), the OnAfterSwitch method is called of FsmDemo (cfr. the double point notation!!! ":"). - -- The OnAfterSwitch method receives from the FSM the 3 transition parameter details ( From, Event, To ), + -- The OnAfterSwitch method receives from the FSM the 3 transition parameter details ( From, Event, To ), -- and one additional parameter that was given when the event was triggered, which is in this case the Unit that is used within OnSwitchAfter. - -- + -- -- function FsmDemo:OnAfterSwitch( From, Event, To, FsmUnit ) - -- + -- -- For debugging reasons the received parameters are traced within the DCS.log. - -- + -- -- self:T( { From, Event, To, FsmUnit } ) - -- + -- -- The method will check if the From state received is either "Green" or "Red" and will flare the respective color from the FsmUnit. - -- + -- -- if From == "Green" then -- FsmUnit:Flare(FLARECOLOR.Green) -- else @@ -309,77 +307,75 @@ do -- FSM -- FsmUnit:Flare(FLARECOLOR.Red) -- end -- end - -- + -- -- It is important that the Switch event is again triggered, otherwise, the FsmDemo would stop working after having the first Event being handled. - -- + -- -- FsmDemo:__Switch( 5, FsmUnit ) -- Trigger the next Switch event to happen in 5 seconds. - -- + -- -- The below code fragment extends the FsmDemo, demonstrating multiple **From states declared as a table**, adding a **Linear Transition Rule**. -- The new event **Stop** will cancel the Switching process. -- The transition for event Stop can be executed if the current state of the FSM is either "Red" or "Green". - -- + -- -- local FsmDemo = FSM:New() -- #FsmDemo -- FsmDemo:SetStartState( "Green" ) -- FsmDemo:AddTransition( "Green", "Switch", "Red" ) -- FsmDemo:AddTransition( "Red", "Switch", "Green" ) -- FsmDemo:AddTransition( { "Red", "Green" }, "Stop", "Stopped" ) - -- + -- -- The transition for event Stop can also be simplified, as any current state of the FSM is valid. - -- + -- -- FsmDemo:AddTransition( "*", "Stop", "Stopped" ) - -- + -- -- So... When FsmDemo:Stop() is being triggered, the state of FsmDemo will transition from Red or Green to Stopped. -- And there is no transition handling method defined for that transition, thus, no new event is being triggered causing the FsmDemo process flow to halt. - -- + -- -- ## FSM Hierarchical Transitions - -- + -- -- Hierarchical Transitions allow to re-use readily available and implemented FSMs. - -- This becomes in very useful for mission building, where mission designers build complex processes and workflows, + -- This becomes in very useful for mission building, where mission designers build complex processes and workflows, -- combining smaller FSMs to one single FSM. - -- - -- The FSM can embed **Sub-FSMs** that will execute and return **multiple possible Return (End) States**. + -- + -- The FSM can embed **Sub-FSMs** that will execute and return **multiple possible Return (End) States**. -- Depending upon **which state is returned**, the main FSM can continue the flow **triggering specific events**. - -- - -- The method @{#FSM.AddProcess}() adds a new Sub-FSM to the FSM. + -- + -- The method @{#FSM.AddProcess}() adds a new Sub-FSM to the FSM. -- -- === - -- + -- -- @field #FSM - -- FSM = { ClassName = "FSM", } - + --- Creates a new FSM object. -- @param #FSM self -- @return #FSM function FSM:New() - + -- Inherits from BASE self = BASE:Inherit( self, BASE:New() ) - + self.options = options or {} self.options.subs = self.options.subs or {} self.current = self.options.initial or 'none' self.Events = {} self.subs = {} self.endstates = {} - + self.Scores = {} - + self._StartState = "none" self._Transitions = {} self._Processes = {} self._EndStates = {} self._Scores = {} self._EventSchedules = {} - + self.CallScheduler = SCHEDULER:New( self ) - + return self end - - + --- Sets the start state of the FSM. -- @param #FSM self -- @param #string State A string defining the start state. @@ -387,15 +383,14 @@ do -- FSM self._StartState = State self.current = State end - - + --- Returns the start state of the FSM. -- @param #FSM self -- @return #string A string containing the start state. function FSM:GetStartState() return self._StartState or {} end - + --- Add a new transition rule to the FSM. -- A transition rule defines when and if the FSM can transition from a state towards another state upon a triggered event. -- @param #FSM self @@ -403,27 +398,26 @@ do -- FSM -- @param #string Event The Event name. -- @param #string To The To state. function FSM:AddTransition( From, Event, To ) - + local Transition = {} Transition.From = From Transition.Event = Event Transition.To = To - + -- Debug message. self:T2( Transition ) - + self._Transitions[Transition] = Transition self:_eventmap( self.Events, Transition ) end - --- Returns a table of the transition rules defined within the FSM. -- @param #FSM self -- @return #table Transitions. - function FSM:GetTransitions() + function FSM:GetTransitions() return self._Transitions or {} end - + --- Set the default @{Process} template with key ProcessName providing the ProcessClass and the process object when it is assigned to a @{Wrapper.Controllable} by the task. -- @param #FSM self -- @param #table From Can contain a string indicating the From state or a table of strings containing multiple From states. @@ -433,73 +427,71 @@ do -- FSM -- @return Core.Fsm#FSM_PROCESS The SubFSM. function FSM:AddProcess( From, Event, Process, ReturnEvents ) self:T( { From, Event } ) - + local Sub = {} Sub.From = From Sub.Event = Event Sub.fsm = Process Sub.StartEvent = "Start" Sub.ReturnEvents = ReturnEvents - + self._Processes[Sub] = Sub - + self:_submap( self.subs, Sub, nil ) - + self:AddTransition( From, Event, From ) - + return Process end - - + --- Returns a table of the SubFSM rules defined within the FSM. -- @param #FSM self -- @return #table Sub processes. function FSM:GetProcesses() - + self:F( { Processes = self._Processes } ) - + return self._Processes or {} end - + function FSM:GetProcess( From, Event ) - + for ProcessID, Process in pairs( self:GetProcesses() ) do if Process.From == From and Process.Event == Event then return Process.fsm end end - + error( "Sub-Process from state " .. From .. " with event " .. Event .. " not found!" ) end - + function FSM:SetProcess( From, Event, Fsm ) - + for ProcessID, Process in pairs( self:GetProcesses() ) do if Process.From == From and Process.Event == Event then Process.fsm = Fsm return true end end - + error( "Sub-Process from state " .. From .. " with event " .. Event .. " not found!" ) end - + --- Adds an End state. -- @param #FSM self -- @param #string State The FSM state. - function FSM:AddEndState( State ) + function FSM:AddEndState( State ) self._EndStates[State] = State self.endstates[State] = State end - + --- Returns the End states. -- @param #FSM self -- @return #table End states. - function FSM:GetEndStates() + function FSM:GetEndStates() return self._EndStates or {} end - - + --- Adds a score for the FSM to be achieved. -- @param #FSM self -- @param #string State is the state of the process when the score needs to be given. (See the relevant state descriptions of the process). @@ -508,14 +500,14 @@ do -- FSM -- @return #FSM self function FSM:AddScore( State, ScoreText, Score ) self:F( { State, ScoreText, Score } ) - + self._Scores[State] = self._Scores[State] or {} self._Scores[State].ScoreText = ScoreText self._Scores[State].Score = Score - + return self end - + --- Adds a score for the FSM_PROCESS to be achieved. -- @param #FSM self -- @param #string From is the From State of the main process. @@ -526,85 +518,85 @@ do -- FSM -- @return #FSM self function FSM:AddScoreProcess( From, Event, State, ScoreText, Score ) self:F( { From, Event, State, ScoreText, Score } ) - + local Process = self:GetProcess( From, Event ) - + Process._Scores[State] = Process._Scores[State] or {} Process._Scores[State].ScoreText = ScoreText Process._Scores[State].Score = Score - + self:T( Process._Scores ) - + return Process end - + --- Returns a table with the scores defined. -- @param #FSM self -- @return #table Scores. - function FSM:GetScores() + function FSM:GetScores() return self._Scores or {} end - + --- Returns a table with the Subs defined. -- @param #FSM self -- @return #table Sub processes. - function FSM:GetSubs() + function FSM:GetSubs() return self.options.subs end - + --- Load call backs. -- @param #FSM self - -- @param #table CallBackTable Table of call backs. + -- @param #table CallBackTable Table of call backs. function FSM:LoadCallBacks( CallBackTable ) - + for name, callback in pairs( CallBackTable or {} ) do self[name] = callback end - + end - - --- Event map. + + --- Event map. -- @param #FSM self -- @param #table Events Events. -- @param #table EventStructure Event structure. function FSM:_eventmap( Events, EventStructure ) - - local Event = EventStructure.Event - local __Event = "__" .. EventStructure.Event - - self[Event] = self[Event] or self:_create_transition(Event) - self[__Event] = self[__Event] or self:_delayed_transition(Event) - - -- Debug message. - self:T2( "Added methods: " .. Event .. ", " .. __Event ) - - Events[Event] = self.Events[Event] or { map = {} } - self:_add_to_map( Events[Event].map, EventStructure ) - + + local Event = EventStructure.Event + local __Event = "__" .. EventStructure.Event + + self[Event] = self[Event] or self:_create_transition( Event ) + self[__Event] = self[__Event] or self:_delayed_transition( Event ) + + -- Debug message. + self:T2( "Added methods: " .. Event .. ", " .. __Event ) + + Events[Event] = self.Events[Event] or { map = {} } + self:_add_to_map( Events[Event].map, EventStructure ) + end - --- Sub maps. + --- Sub maps. -- @param #FSM self -- @param #table subs Subs. -- @param #table sub Sub. - -- @param #string name Name. + -- @param #string name Name. function FSM:_submap( subs, sub, name ) - + subs[sub.From] = subs[sub.From] or {} subs[sub.From][sub.Event] = subs[sub.From][sub.Event] or {} - + -- Make the reference table weak. -- setmetatable( subs[sub.From][sub.Event], { __mode = "k" } ) - + subs[sub.From][sub.Event][sub] = {} subs[sub.From][sub.Event][sub].fsm = sub.fsm subs[sub.From][sub.Event][sub].StartEvent = sub.StartEvent subs[sub.From][sub.Event][sub].ReturnEvents = sub.ReturnEvents or {} -- these events need to be given to find the correct continue event ... if none given, the processing will stop. subs[sub.From][sub.Event][sub].name = name subs[sub.From][sub.Event][sub].fsmparent = self - + end - + --- Call handler. -- @param #FSM self -- @param #string step Step "onafter", "onbefore", "onenter", "onleave". @@ -613,12 +605,12 @@ do -- FSM -- @param #string EventName Event name. -- @return Value. function FSM:_call_handler( step, trigger, params, EventName ) - --env.info(string.format("FF T=%.3f _call_handler step=%s, trigger=%s, event=%s", timer.getTime(), step, trigger, EventName)) + -- env.info(string.format("FF T=%.3f _call_handler step=%s, trigger=%s, event=%s", timer.getTime(), step, trigger, EventName)) local handler = step .. trigger - + if self[handler] then - + --[[ if step == "onafter" or step == "OnAfter" then self:T( ":::>" .. step .. params[2] .. " : " .. params[1] .. " >> " .. params[2] .. ">" .. step .. params[2] .. "()" .. " >> " .. params[3] ) @@ -632,7 +624,7 @@ do -- FSM self:T( ":::>" .. step .. " : " .. params[1] .. " >> " .. params[2] .. " >> " .. params[3] ) end ]] - + self._EventSchedules[EventName] = nil -- Error handler. @@ -640,49 +632,50 @@ do -- FSM env.info( "Error in SCHEDULER function:" .. errmsg ) if BASE.Debug ~= nil then env.info( BASE.Debug.traceback() ) - end + end return errmsg end - - --return self[handler](self, unpack( params )) - + + -- return self[handler](self, unpack( params )) + -- Protected call. - local Result, Value = xpcall( function() return self[handler]( self, unpack( params ) ) end, ErrorHandler ) - return Value + local Result, Value = xpcall( function() + return self[handler]( self, unpack( params ) ) + end, ErrorHandler ) + return Value end - + end - + --- Handler. -- @param #FSM self -- @param #string EventName Event name. -- @param ... Arguments. function FSM._handler( self, EventName, ... ) - + local Can, To = self:can( EventName ) - + if To == "*" then To = self.current end - + if Can then - + -- From state. local From = self.current - + -- Parameters. - local Params = { From, EventName, To, ... } + local Params = { From, EventName, To, ... } + if self["onleave" .. From] or + self["OnLeave" .. From] or + self["onbefore" .. EventName] or + self["OnBefore" .. EventName] or + self["onafter" .. EventName] or + self["OnAfter" .. EventName] or + self["onenter" .. To] or + self["OnEnter" .. To] then - if self["onleave".. From] or - self["OnLeave".. From] or - self["onbefore".. EventName] or - self["OnBefore".. EventName] or - self["onafter".. EventName] or - self["OnAfter".. EventName] or - self["onenter".. To] or - self["OnEnter".. To] then - if self:_call_handler( "onbefore", EventName, Params, EventName ) == false then self:T( "*** FSM *** Cancel" .. " *** " .. self.current .. " --> " .. EventName .. " --> " .. To .. " *** onbefore" .. EventName ) return false @@ -691,7 +684,7 @@ do -- FSM self:T( "*** FSM *** Cancel" .. " *** " .. self.current .. " --> " .. EventName .. " --> " .. To .. " *** OnBefore" .. EventName ) return false else - if self:_call_handler( "onleave", From, Params, EventName ) == false then + if self:_call_handler( "onleave", From, Params, EventName ) == false then self:T( "*** FSM *** Cancel" .. " *** " .. self.current .. " --> " .. EventName .. " --> " .. To .. " *** onleave" .. From ) return false else @@ -699,147 +692,149 @@ do -- FSM self:T( "*** FSM *** Cancel" .. " *** " .. self.current .. " --> " .. EventName .. " --> " .. To .. " *** OnLeave" .. From ) return false end - end + end end end - + else - + local ClassName = self:GetClassName() - + if ClassName == "FSM" then self:T( "*** FSM *** Transit *** " .. self.current .. " --> " .. EventName .. " --> " .. To ) end - + if ClassName == "FSM_TASK" then self:T( "*** FSM *** Transit *** " .. self.current .. " --> " .. EventName .. " --> " .. To .. " *** Task: " .. self.TaskName ) end - + if ClassName == "FSM_CONTROLLABLE" then - self:T( "*** FSM *** Transit *** " .. self.current .. " --> " .. EventName .. " --> " .. To .. " *** TaskUnit: " .. self.Controllable.ControllableName .. " *** " ) - end - + self:T( "*** FSM *** Transit *** " .. self.current .. " --> " .. EventName .. " --> " .. To .. " *** TaskUnit: " .. self.Controllable.ControllableName .. " *** " ) + end + if ClassName == "FSM_PROCESS" then - self:T( "*** FSM *** Transit *** " .. self.current .. " --> " .. EventName .. " --> " .. To .. " *** Task: " .. self.Task:GetName() .. ", TaskUnit: " .. self.Controllable.ControllableName .. " *** " ) - end + self:T( "*** FSM *** Transit *** " .. self.current .. " --> " .. EventName .. " --> " .. To .. " *** Task: " .. self.Task:GetName() .. ", TaskUnit: " .. self.Controllable.ControllableName .. " *** " ) + end end - + -- New current state. self.current = To - + local execute = true - + local subtable = self:_gosub( From, EventName ) - + for _, sub in pairs( subtable ) do - - --if sub.nextevent then + + -- if sub.nextevent then -- self:F2( "nextevent = " .. sub.nextevent ) -- self[sub.nextevent]( self ) - --end - + -- end + self:T( "*** FSM *** Sub *** " .. sub.StartEvent ) - + sub.fsm.fsmparent = self sub.fsm.ReturnEvents = sub.ReturnEvents sub.fsm[sub.StartEvent]( sub.fsm ) - + execute = false end - + local fsmparent, Event = self:_isendstate( To ) - + if fsmparent and Event then - + self:T( "*** FSM *** End *** " .. Event ) - - self:_call_handler("onenter", To, Params, EventName ) - self:_call_handler("OnEnter", To, Params, EventName ) - self:_call_handler("onafter", EventName, Params, EventName ) - self:_call_handler("OnAfter", EventName, Params, EventName ) - self:_call_handler("onstate", "change", Params, EventName ) - + + self:_call_handler( "onenter", To, Params, EventName ) + self:_call_handler( "OnEnter", To, Params, EventName ) + self:_call_handler( "onafter", EventName, Params, EventName ) + self:_call_handler( "OnAfter", EventName, Params, EventName ) + self:_call_handler( "onstate", "change", Params, EventName ) + fsmparent[Event]( fsmparent ) - + execute = false end - + if execute then - - self:_call_handler("onafter", EventName, Params, EventName ) - self:_call_handler("OnAfter", EventName, Params, EventName ) - - self:_call_handler("onenter", To, Params, EventName ) - self:_call_handler("OnEnter", To, Params, EventName ) - - self:_call_handler("onstate", "change", Params, EventName ) - + + self:_call_handler( "onafter", EventName, Params, EventName ) + self:_call_handler( "OnAfter", EventName, Params, EventName ) + + self:_call_handler( "onenter", To, Params, EventName ) + self:_call_handler( "OnEnter", To, Params, EventName ) + + self:_call_handler( "onstate", "change", Params, EventName ) + end else self:T( "*** FSM *** NO Transition *** " .. self.current .. " --> " .. EventName .. " --> ? " ) end - + return nil end --- Delayed transition. -- @param #FSM self - -- @param #string EventName Event name. + -- @param #string EventName Event name. -- @return #function Function. function FSM:_delayed_transition( EventName ) - + return function( self, DelaySeconds, ... ) - + -- Debug. self:T2( "Delayed Event: " .. EventName ) - + local CallID = 0 if DelaySeconds ~= nil then - + if DelaySeconds < 0 then -- Only call the event ONCE! - + DelaySeconds = math.abs( DelaySeconds ) - + if not self._EventSchedules[EventName] then - + -- Call _handler. CallID = self.CallScheduler:Schedule( self, self._handler, { EventName, ... }, DelaySeconds or 1, nil, nil, nil, 4, true ) - + -- Set call ID. self._EventSchedules[EventName] = CallID - + -- Debug output. - self:T2(string.format("NEGATIVE Event %s delayed by %.1f sec SCHEDULED with CallID=%s", EventName, DelaySeconds, tostring(CallID))) + self:T2( string.format( "NEGATIVE Event %s delayed by %.1f sec SCHEDULED with CallID=%s", EventName, DelaySeconds, tostring( CallID ) ) ) else - self:T2(string.format("NEGATIVE Event %s delayed by %.1f sec CANCELLED as we already have such an event in the queue.", EventName, DelaySeconds)) + self:T2( string.format( "NEGATIVE Event %s delayed by %.1f sec CANCELLED as we already have such an event in the queue.", EventName, DelaySeconds ) ) -- reschedule end else - + CallID = self.CallScheduler:Schedule( self, self._handler, { EventName, ... }, DelaySeconds or 1, nil, nil, nil, 4, true ) - - self:T2(string.format("Event %s delayed by %.1f sec SCHEDULED with CallID=%s", EventName, DelaySeconds, tostring(CallID))) + + self:T2( string.format( "Event %s delayed by %.1f sec SCHEDULED with CallID=%s", EventName, DelaySeconds, tostring( CallID ) ) ) end else error( "FSM: An asynchronous event trigger requires a DelaySeconds parameter!!! This can be positive or negative! Sorry, but will not process this." ) end - + -- Debug. self:T2( { CallID = CallID } ) end - + end --- Create transition. -- @param #FSM self - -- @param #string EventName Event name. - -- @return #function Function. + -- @param #string EventName Event name. + -- @return #function Function. function FSM:_create_transition( EventName ) - return function( self, ... ) return self._handler( self, EventName , ... ) end + return function( self, ... ) + return self._handler( self, EventName, ... ) + end end - + --- Go sub. - -- @param #FSM self + -- @param #FSM self -- @param #string ParentFrom Parent from state. -- @param #string ParentEvent Parent event name. -- @return #table Subs. @@ -860,21 +855,21 @@ do -- FSM -- @return #string Event name. function FSM:_isendstate( Current ) local FSMParent = self.fsmparent - + if FSMParent and self.endstates[Current] then - --self:T( { state = Current, endstates = self.endstates, endstate = self.endstates[Current] } ) + -- self:T( { state = Current, endstates = self.endstates, endstate = self.endstates[Current] } ) FSMParent.current = Current local ParentFrom = FSMParent.current - --self:T( { ParentFrom, self.ReturnEvents } ) + -- self:T( { ParentFrom, self.ReturnEvents } ) local Event = self.ReturnEvents[Current] - --self:T( { Event } ) + -- self:T( { Event } ) if Event then return FSMParent, Event else - --self:T( { "Could not find parent event name for state ", ParentFrom } ) + -- self:T( { "Could not find parent event name for state ", ParentFrom } ) end end - + return nil end @@ -883,17 +878,17 @@ do -- FSM -- @param #table Map Map. -- @param #table Event Event table. function FSM:_add_to_map( Map, Event ) - self:F3( { Map, Event } ) - - if type(Event.From) == 'string' then - Map[Event.From] = Event.To + self:F3( { Map, Event } ) + + if type( Event.From ) == 'string' then + Map[Event.From] = Event.To else - for _, From in ipairs(Event.From) do - Map[From] = Event.To + for _, From in ipairs( Event.From ) do + Map[From] = Event.To end end - - self:T3( { Map, Event } ) + + self:T3( { Map, Event } ) end --- Get current state. @@ -905,11 +900,11 @@ do -- FSM --- Get current state. -- @param #FSM self - -- @return #string Current FSM state. + -- @return #string Current FSM state. function FSM:GetCurrentState() return self.current end - + --- Check if FSM is in state. -- @param #FSM self -- @param #string State State name. @@ -921,8 +916,8 @@ do -- FSM --- Check if FSM is in state. -- @param #FSM self -- @param #string State State name. - -- @param #boolean If true, FSM is in this state. - function FSM:is(state) + -- @param #boolean If true, FSM is in this state. + function FSM:is( state ) return self.current == state end @@ -931,14 +926,14 @@ do -- FSM -- @param #string e Event name. -- @return #boolean If true, FSM can do the event. -- @return #string To state. - function FSM:can(e) - + function FSM:can( e ) + local Event = self.Events[e] - - --self:F3( { self.current, Event } ) - + + -- self:F3( { self.current, Event } ) + local To = Event and Event.map[self.current] or Event.map['*'] - + return To ~= nil, To end @@ -946,8 +941,8 @@ do -- FSM -- @param #FSM self -- @param #string e Event name. -- @return #boolean If true, FSM cannot do the event. - function FSM:cannot(e) - return not self:can(e) + function FSM:cannot( e ) + return not self:can( e ) end end @@ -957,32 +952,32 @@ do -- FSM_CONTROLLABLE --- @type FSM_CONTROLLABLE -- @field Wrapper.Controllable#CONTROLLABLE Controllable -- @extends Core.Fsm#FSM - + --- Models Finite State Machines for @{Wrapper.Controllable}s, which are @{Wrapper.Group}s, @{Wrapper.Unit}s, @{Client}s. - -- + -- -- === - -- + -- -- @field #FSM_CONTROLLABLE FSM_CONTROLLABLE = { ClassName = "FSM_CONTROLLABLE", } - + --- Creates a new FSM_CONTROLLABLE object. -- @param #FSM_CONTROLLABLE self -- @param #table FSMT Finite State Machine Table -- @param Wrapper.Controllable#CONTROLLABLE Controllable (optional) The CONTROLLABLE object that the FSM_CONTROLLABLE governs. -- @return #FSM_CONTROLLABLE function FSM_CONTROLLABLE:New( Controllable ) - + -- Inherits from BASE local self = BASE:Inherit( self, FSM:New() ) -- Core.Fsm#FSM_CONTROLLABLE - + if Controllable then self:SetControllable( Controllable ) end - + self:AddTransition( "*", "Stop", "Stopped" ) - + --- OnBefore Transition Handler for Event Stop. -- @function [parent=#FSM_CONTROLLABLE] OnBeforeStop -- @param #FSM_CONTROLLABLE self @@ -991,7 +986,7 @@ do -- FSM_CONTROLLABLE -- @param #string Event The Event string. -- @param #string To The To State string. -- @return #boolean Return false to cancel Transition. - + --- OnAfter Transition Handler for Event Stop. -- @function [parent=#FSM_CONTROLLABLE] OnAfterStop -- @param #FSM_CONTROLLABLE self @@ -999,16 +994,16 @@ do -- FSM_CONTROLLABLE -- @param #string From The From State string. -- @param #string Event The Event string. -- @param #string To The To State string. - + --- Synchronous Event Trigger for Event Stop. -- @function [parent=#FSM_CONTROLLABLE] Stop -- @param #FSM_CONTROLLABLE self - + --- Asynchronous Event Trigger for Event Stop. -- @function [parent=#FSM_CONTROLLABLE] __Stop -- @param #FSM_CONTROLLABLE self -- @param #number Delay The delay in seconds. - + --- OnLeave Transition Handler for State Stopped. -- @function [parent=#FSM_CONTROLLABLE] OnLeaveStopped -- @param #FSM_CONTROLLABLE self @@ -1017,7 +1012,7 @@ do -- FSM_CONTROLLABLE -- @param #string Event The Event string. -- @param #string To The To State string. -- @return #boolean Return false to cancel Transition. - + --- OnEnter Transition Handler for State Stopped. -- @function [parent=#FSM_CONTROLLABLE] OnEnterStopped -- @param #FSM_CONTROLLABLE self @@ -1036,51 +1031,53 @@ do -- FSM_CONTROLLABLE -- @param #string From The From State string. -- @param #string Event The Event string. -- @param #string To The To State string. - function FSM_CONTROLLABLE:OnAfterStop(Controllable,From,Event,To) - + function FSM_CONTROLLABLE:OnAfterStop( Controllable, From, Event, To ) + -- Clear all pending schedules self.CallScheduler:Clear() end - + --- Sets the CONTROLLABLE object that the FSM_CONTROLLABLE governs. -- @param #FSM_CONTROLLABLE self -- @param Wrapper.Controllable#CONTROLLABLE FSMControllable -- @return #FSM_CONTROLLABLE function FSM_CONTROLLABLE:SetControllable( FSMControllable ) - --self:F( FSMControllable:GetName() ) + -- self:F( FSMControllable:GetName() ) self.Controllable = FSMControllable end - + --- Gets the CONTROLLABLE object that the FSM_CONTROLLABLE governs. -- @param #FSM_CONTROLLABLE self -- @return Wrapper.Controllable#CONTROLLABLE function FSM_CONTROLLABLE:GetControllable() return self.Controllable end - + function FSM_CONTROLLABLE:_call_handler( step, trigger, params, EventName ) - + local handler = step .. trigger - + local ErrorHandler = function( errmsg ) - + env.info( "Error in SCHEDULER function:" .. errmsg ) if BASE.Debug ~= nil then env.info( BASE.Debug.traceback() ) end - + return errmsg end - + if self[handler] then self:T( "*** FSM *** " .. step .. " *** " .. params[1] .. " --> " .. params[2] .. " --> " .. params[3] .. " *** TaskUnit: " .. self.Controllable:GetName() ) self._EventSchedules[EventName] = nil - local Result, Value = xpcall( function() return self[handler]( self, self.Controllable, unpack( params ) ) end, ErrorHandler ) + local Result, Value = xpcall( function() + return self[handler]( self, self.Controllable, unpack( params ) ) + end, ErrorHandler ) return Value - --return self[handler]( self, self.Controllable, unpack( params ) ) + -- return self[handler]( self, self.Controllable, unpack( params ) ) end end - + end do -- FSM_PROCESS @@ -1088,50 +1085,47 @@ do -- FSM_PROCESS --- @type FSM_PROCESS -- @field Tasking.Task#TASK Task -- @extends Core.Fsm#FSM_CONTROLLABLE - - + --- FSM_PROCESS class models Finite State Machines for @{Task} actions, which control @{Client}s. -- -- === -- -- @field #FSM_PROCESS FSM_PROCESS -- - FSM_PROCESS = { - ClassName = "FSM_PROCESS", - } - + FSM_PROCESS = { ClassName = "FSM_PROCESS" } + --- Creates a new FSM_PROCESS object. -- @param #FSM_PROCESS self -- @return #FSM_PROCESS function FSM_PROCESS:New( Controllable, Task ) - + local self = BASE:Inherit( self, FSM_CONTROLLABLE:New() ) -- Core.Fsm#FSM_PROCESS - --self:F( Controllable ) - + -- self:F( Controllable ) + self:Assign( Controllable, Task ) - + return self end - + function FSM_PROCESS:Init( FsmProcess ) self:T( "No Initialisation" ) - end + end function FSM_PROCESS:_call_handler( step, trigger, params, EventName ) - + local handler = step .. trigger - + local ErrorHandler = function( errmsg ) - + env.info( "Error in FSM_PROCESS call handler:" .. errmsg ) if BASE.Debug ~= nil then env.info( BASE.Debug.traceback() ) end - + return errmsg end - + if self[handler] then if handler ~= "onstatechange" then self:T( "*** FSM *** " .. step .. " *** " .. params[1] .. " --> " .. params[2] .. " --> " .. params[3] .. " *** Task: " .. self.Task:GetName() .. ", TaskUnit: " .. self.Controllable:GetName() ) @@ -1139,53 +1133,54 @@ do -- FSM_PROCESS self._EventSchedules[EventName] = nil local Result, Value if self.Controllable and self.Controllable:IsAlive() == true then - Result, Value = xpcall( function() return self[handler]( self, self.Controllable, self.Task, unpack( params ) ) end, ErrorHandler ) + Result, Value = xpcall( function() + return self[handler]( self, self.Controllable, self.Task, unpack( params ) ) + end, ErrorHandler ) end return Value - --return self[handler]( self, self.Controllable, unpack( params ) ) + -- return self[handler]( self, self.Controllable, unpack( params ) ) end end - + --- Creates a new FSM_PROCESS object based on this FSM_PROCESS. -- @param #FSM_PROCESS self -- @return #FSM_PROCESS function FSM_PROCESS:Copy( Controllable, Task ) self:T( { self:GetClassNameAndID() } ) - local NewFsm = self:New( Controllable, Task ) -- Core.Fsm#FSM_PROCESS - + NewFsm:Assign( Controllable, Task ) -- Polymorphic call to initialize the new FSM_PROCESS based on self FSM_PROCESS NewFsm:Init( self ) - + -- Set Start State NewFsm:SetStartState( self:GetStartState() ) - + -- Copy Transitions for TransitionID, Transition in pairs( self:GetTransitions() ) do NewFsm:AddTransition( Transition.From, Transition.Event, Transition.To ) end - + -- Copy Processes for ProcessID, Process in pairs( self:GetProcesses() ) do - --self:E( { Process:GetName() } ) + -- self:E( { Process:GetName() } ) local FsmProcess = NewFsm:AddProcess( Process.From, Process.Event, Process.fsm:Copy( Controllable, Task ), Process.ReturnEvents ) end - + -- Copy End States for EndStateID, EndState in pairs( self:GetEndStates() ) do self:T( EndState ) NewFsm:AddEndState( EndState ) end - + -- Copy the score tables for ScoreID, Score in pairs( self:GetScores() ) do self:T( Score ) NewFsm:AddScore( ScoreID, Score.ScoreText, Score.Score ) end - + return NewFsm end @@ -1197,7 +1192,7 @@ do -- FSM_PROCESS self:F( "Clearing Schedules" ) self.CallScheduler:Clear() - + -- Copy Processes for ProcessID, Process in pairs( self:GetProcesses() ) do if Process.fsm then @@ -1205,98 +1200,94 @@ do -- FSM_PROCESS Process.fsm = nil end end - + return self end - + --- Sets the task of the process. -- @param #FSM_PROCESS self -- @param Tasking.Task#TASK Task -- @return #FSM_PROCESS function FSM_PROCESS:SetTask( Task ) - + self.Task = Task - + return self end - + --- Gets the task of the process. -- @param #FSM_PROCESS self -- @return Tasking.Task#TASK function FSM_PROCESS:GetTask() - + return self.Task end - + --- Gets the mission of the process. -- @param #FSM_PROCESS self -- @return Tasking.Mission#MISSION function FSM_PROCESS:GetMission() - + return self.Task.Mission end - + --- Gets the mission of the process. -- @param #FSM_PROCESS self -- @return Tasking.CommandCenter#COMMANDCENTER function FSM_PROCESS:GetCommandCenter() - + return self:GetTask():GetMission():GetCommandCenter() end - --- TODO: Need to check and fix that an FSM_PROCESS is only for a UNIT. Not for a GROUP. - + + -- TODO: Need to check and fix that an FSM_PROCESS is only for a UNIT. Not for a GROUP. + --- Send a message of the @{Task} to the Group of the Unit. -- @param #FSM_PROCESS self function FSM_PROCESS:Message( Message ) self:F( { Message = Message } ) - + local CC = self:GetCommandCenter() local TaskGroup = self.Controllable:GetGroup() - + local PlayerName = self.Controllable:GetPlayerName() -- Only for a unit PlayerName = PlayerName and " (" .. PlayerName .. ")" or "" -- If PlayerName is nil, then keep it nil, otherwise add brackets. local Callsign = self.Controllable:GetCallsign() local Prefix = Callsign and " @ " .. Callsign .. PlayerName or "" - + Message = Prefix .. ": " .. Message CC:MessageToGroup( Message, TaskGroup ) end - - - --- Assign the process to a @{Wrapper.Unit} and activate the process. -- @param #FSM_PROCESS self -- @param Task.Tasking#TASK Task -- @param Wrapper.Unit#UNIT ProcessUnit -- @return #FSM_PROCESS self function FSM_PROCESS:Assign( ProcessUnit, Task ) - --self:T( { Task:GetName(), ProcessUnit:GetName() } ) - + -- self:T( { Task:GetName(), ProcessUnit:GetName() } ) + self:SetControllable( ProcessUnit ) self:SetTask( Task ) - - --self.ProcessGroup = ProcessUnit:GetGroup() - + + -- self.ProcessGroup = ProcessUnit:GetGroup() + return self end - --- function FSM_PROCESS:onenterAssigned( ProcessUnit, Task, From, Event, To ) --- --- if From( "Planned" ) then --- self:T( "*** FSM *** Assign *** " .. Task:GetName() .. "/" .. ProcessUnit:GetName() .. " *** " .. From .. " --> " .. Event .. " --> " .. To ) --- self.Task:Assign() --- end --- end - + + -- function FSM_PROCESS:onenterAssigned( ProcessUnit, Task, From, Event, To ) + -- + -- if From( "Planned" ) then + -- self:T( "*** FSM *** Assign *** " .. Task:GetName() .. "/" .. ProcessUnit:GetName() .. " *** " .. From .. " --> " .. Event .. " --> " .. To ) + -- self.Task:Assign() + -- end + -- end + function FSM_PROCESS:onenterFailed( ProcessUnit, Task, From, Event, To ) self:T( "*** FSM *** Failed *** " .. Task:GetName() .. "/" .. ProcessUnit:GetName() .. " *** " .. From .. " --> " .. Event .. " --> " .. To ) - + self.Task:Fail() end - --- StateMachine callback function for a FSM_PROCESS -- @param #FSM_PROCESS self -- @param Wrapper.Controllable#CONTROLLABLE ProcessUnit @@ -1304,20 +1295,20 @@ do -- FSM_PROCESS -- @param #string From -- @param #string To function FSM_PROCESS:onstatechange( ProcessUnit, Task, From, Event, To ) - + if From ~= To then self:T( "*** FSM *** Change *** " .. Task:GetName() .. "/" .. ProcessUnit:GetName() .. " *** " .. From .. " --> " .. Event .. " --> " .. To ) end - --- if self:IsTrace() then --- MESSAGE:New( "@ Process " .. self:GetClassNameAndID() .. " : " .. Event .. " changed to state " .. To, 2 ):ToAll() --- self:F2( { Scores = self._Scores, To = To } ) --- end - + + -- if self:IsTrace() then + -- MESSAGE:New( "@ Process " .. self:GetClassNameAndID() .. " : " .. Event .. " changed to state " .. To, 2 ):ToAll() + -- self:F2( { Scores = self._Scores, To = To } ) + -- end + -- TODO: This needs to be reworked with a callback functions allocated within Task, and set within the mission script from the Task Objects... if self._Scores[To] then - - local Task = self.Task + + local Task = self.Task local Scoring = Task:GetScoring() if Scoring then Scoring:_AddMissionTaskScore( Task.Mission, ProcessUnit, self._Scores[To].ScoreText, self._Scores[To].Score ) @@ -1333,49 +1324,51 @@ do -- FSM_TASK -- @type FSM_TASK -- @field Tasking.Task#TASK Task -- @extends #FSM - + --- Models Finite State Machines for @{Tasking.Task}s. - -- + -- -- === - -- + -- -- @field #FSM_TASK FSM_TASK - -- + -- FSM_TASK = { ClassName = "FSM_TASK", } - + --- Creates a new FSM_TASK object. -- @param #FSM_TASK self -- @param #string TaskName The name of the task. -- @return #FSM_TASK function FSM_TASK:New( TaskName ) - + local self = BASE:Inherit( self, FSM_CONTROLLABLE:New() ) -- Core.Fsm#FSM_TASK - + self["onstatechange"] = self.OnStateChange self.TaskName = TaskName - + return self end - + function FSM_TASK:_call_handler( step, trigger, params, EventName ) local handler = step .. trigger - + local ErrorHandler = function( errmsg ) - + env.info( "Error in SCHEDULER function:" .. errmsg ) if BASE.Debug ~= nil then env.info( BASE.Debug.traceback() ) end - + return errmsg end if self[handler] then self:T( "*** FSM *** " .. step .. " *** " .. params[1] .. " --> " .. params[2] .. " --> " .. params[3] .. " *** Task: " .. self.TaskName ) self._EventSchedules[EventName] = nil - --return self[handler]( self, unpack( params ) ) - local Result, Value = xpcall( function() return self[handler]( self, unpack( params ) ) end, ErrorHandler ) + -- return self[handler]( self, unpack( params ) ) + local Result, Value = xpcall( function() + return self[handler]( self, unpack( params ) ) + end, ErrorHandler ) return Value end end @@ -1389,35 +1382,33 @@ do -- FSM_SET -- @field Core.Set#SET_BASE Set -- @extends Core.Fsm#FSM - --- FSM_SET class models Finite State Machines for @{Set}s. Note that these FSMs control multiple objects!!! So State concerns here -- for multiple objects or the position of the state machine in the process. - -- + -- -- === - -- + -- -- @field #FSM_SET FSM_SET - -- FSM_SET = { ClassName = "FSM_SET", } - + --- Creates a new FSM_SET object. -- @param #FSM_SET self -- @param #table FSMT Finite State Machine Table -- @param Set_SET_BASE FSMSet (optional) The Set object that the FSM_SET governs. -- @return #FSM_SET function FSM_SET:New( FSMSet ) - + -- Inherits from BASE self = BASE:Inherit( self, FSM:New() ) -- Core.Fsm#FSM_SET - + if FSMSet then self:Set( FSMSet ) end - + return self end - + --- Sets the SET_BASE object that the FSM_SET governs. -- @param #FSM_SET self -- @param Core.Set#SET_BASE FSMSet @@ -1426,16 +1417,16 @@ do -- FSM_SET self:F( FSMSet ) self.Set = FSMSet end - + --- Gets the SET_BASE object that the FSM_SET governs. -- @param #FSM_SET self -- @return Core.Set#SET_BASE function FSM_SET:Get() return self.Controllable end - - function FSM_SET:_call_handler( step, trigger, params, EventName ) - local handler = step .. trigger + + function FSM_SET:_call_handler( step, trigger, params, EventName ) + local handler = step .. trigger if self[handler] then self:T( "*** FSM *** " .. step .. " *** " .. params[1] .. " --> " .. params[2] .. " --> " .. params[3] ) self._EventSchedules[EventName] = nil diff --git a/Moose Development/Moose/Core/Goal.lua b/Moose Development/Moose/Core/Goal.lua index c145d6de4..bc33246f6 100644 --- a/Moose Development/Moose/Core/Goal.lua +++ b/Moose Development/Moose/Core/Goal.lua @@ -1,89 +1,87 @@ --- **Core** - Models the process to achieve goal(s). -- -- === --- +-- -- ## Features: --- +-- -- * Define the goal. -- * Monitor the goal achievement. -- * Manage goal contribution by players. --- +-- -- === --- +-- -- Classes that implement a goal achievement, will derive from GOAL to implement the ways how the achievements can be realized. --- +-- -- === --- +-- -- ### Author: **FlightControl** -- ### Contributions: **funkyfranky** --- +-- -- === --- +-- -- @module Core.Goal -- @image Core_Goal.JPG - do -- Goal --- @type GOAL -- @extends Core.Fsm#FSM - --- Models processes that have an objective with a defined achievement. Derived classes implement the ways how the achievements can be realized. - -- + -- -- # 1. GOAL constructor - -- + -- -- * @{#GOAL.New}(): Creates a new GOAL object. - -- + -- -- # 2. GOAL is a finite state machine (FSM). - -- + -- -- ## 2.1. GOAL States - -- + -- -- * **Pending**: The goal object is in progress. -- * **Achieved**: The goal objective is Achieved. - -- + -- -- ## 2.2. GOAL Events - -- + -- -- * **Achieved**: Set the goal objective to Achieved. - -- + -- -- # 3. Player contributions. - -- + -- -- Goals are most of the time achieved by players. These player achievements can be registered as part of the goal achievement. -- Use @{#GOAL.AddPlayerContribution}() to add a player contribution to the goal. -- The player contributions are based on a points system, an internal counter per player. - -- So once the goal has been achieved, the player contributions can be queried using @{#GOAL.GetPlayerContributions}(), + -- So once the goal has been achieved, the player contributions can be queried using @{#GOAL.GetPlayerContributions}(), -- that retrieves all contributions done by the players. For one player, the contribution can be queried using @{#GOAL.GetPlayerContribution}(). -- The total amount of player contributions can be queried using @{#GOAL.GetTotalContributions}(). - -- + -- -- # 4. Goal achievement. - -- + -- -- Once the goal is achieved, the mission designer will need to trigger the goal achievement using the **Achieved** event. -- The underlying 2 examples will achieve the goals for the `Goal` object: - -- + -- -- Goal:Achieved() -- Achieve the goal immediately. -- Goal:__Achieved( 30 ) -- Achieve the goal within 30 seconds. - -- + -- -- # 5. Check goal achievement. - -- + -- -- The method @{#GOAL.IsAchieved}() will return true if the goal is achieved (the trigger **Achieved** was executed). -- You can use this method to check asynchronously if a goal has been achieved, for example using a scheduler. - -- + -- -- @field #GOAL GOAL = { ClassName = "GOAL", } - + --- @field #table GOAL.Players GOAL.Players = {} --- @field #number GOAL.TotalContributions GOAL.TotalContributions = 0 - + --- GOAL Constructor. -- @param #GOAL self -- @return #GOAL function GOAL:New() - + local self = BASE:Inherit( self, FSM:New() ) -- #GOAL self:F( {} ) @@ -104,11 +102,10 @@ do -- Goal -- @param #string From -- @param #string Event -- @param #string To - - + self:SetStartState( "Pending" ) - self:AddTransition( "*", "Achieved", "Achieved" ) - + self:AddTransition( "*", "Achieved", "Achieved" ) + --- Achieved Handler OnBefore for GOAL -- @function [parent=#GOAL] OnBeforeAchieved -- @param #GOAL self @@ -116,47 +113,44 @@ do -- Goal -- @param #string Event -- @param #string To -- @return #boolean - + --- Achieved Handler OnAfter for GOAL -- @function [parent=#GOAL] OnAfterAchieved -- @param #GOAL self -- @param #string From -- @param #string Event -- @param #string To - + --- Achieved Trigger for GOAL -- @function [parent=#GOAL] Achieved -- @param #GOAL self - + --- Achieved Asynchronous Trigger for GOAL -- @function [parent=#GOAL] __Achieved -- @param #GOAL self -- @param #number Delay - + self:SetEventPriority( 5 ) return self end - - + --- Add a new contribution by a player. -- @param #GOAL self -- @param #string PlayerName The name of the player. function GOAL:AddPlayerContribution( PlayerName ) - self:F({PlayerName}) + self:F( { PlayerName } ) self.Players[PlayerName] = self.Players[PlayerName] or 0 self.Players[PlayerName] = self.Players[PlayerName] + 1 self.TotalContributions = self.TotalContributions + 1 end - - + --- @param #GOAL self -- @param #number Player contribution. function GOAL:GetPlayerContribution( PlayerName ) - return self.Players[PlayerName] or 0 + return self.Players[PlayerName] or 0 end - --- Get the players who contributed to achieve the goal. -- The result is a list of players, sorted by the name of the players. -- @param #GOAL self @@ -165,7 +159,6 @@ do -- Goal return self.Players or {} end - --- Gets the total contributions that happened to achieve the goal. -- The result is a number. -- @param #GOAL self @@ -173,9 +166,7 @@ do -- Goal function GOAL:GetTotalContributions() return self.TotalContributions or 0 end - - - + --- Validates if the goal is achieved. -- @param #GOAL self -- @return #boolean true if the goal is achieved. @@ -183,4 +174,4 @@ do -- Goal return self:Is( "Achieved" ) end -end \ No newline at end of file +end diff --git a/Moose Development/Moose/Core/Menu.lua b/Moose Development/Moose/Core/Menu.lua index f0962380d..bd64eb773 100644 --- a/Moose Development/Moose/Core/Menu.lua +++ b/Moose Development/Moose/Core/Menu.lua @@ -1,9 +1,9 @@ --- **Core** - Manage hierarchical menu structures and commands for players within a mission. --- +-- -- === --- +-- -- ### Features: --- +-- -- * Setup mission sub menus. -- * Setup mission command menus. -- * Setup coalition sub menus. @@ -14,46 +14,45 @@ -- * Only create or delete menus when required, and keep existing menus persistent. -- * Update menu structures. -- * Refresh menu structures intelligently, based on a time stamp of updates. --- - Delete obscolete menus. +-- - Delete obsolete menus. -- - Create new one where required. -- - Don't touch the existing ones. -- * Provide a variable amount of parameters to menus. -- * Update the parameters and the receiving methods, without updating the menu within DCS! -- * Provide a great performance boost in menu management. -- * Provide a great tool to manage menus in your code. --- --- 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 +-- +-- 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 scenarios 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. +-- 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. --- +-- 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_**: --- +-- -- * @{Core.Menu#MENU_MISSION}: Manages main menus for whole mission file. -- * @{Core.Menu#MENU_COALITION}: Manages main menus for whole coalition. -- * @{Core.Menu#MENU_GROUP}: Manages main menus for GROUPs. --- +-- -- ### To manage **command menus**, which are menus that allow the player to issue **functions**, the classes begin with **MENU_COMMAND_**: --- +-- -- * @{Core.Menu#MENU_MISSION_COMMAND}: Manages command menus for whole mission file. -- * @{Core.Menu#MENU_COALITION_COMMAND}: Manages command menus for whole coalition. -- * @{Core.Menu#MENU_GROUP_COMMAND}: Manages command menus for GROUPs. --- +-- -- === ---- +--- -- ### Author: **FlightControl** --- ### Contributions: --- +-- ### Contributions: +-- -- === --- +-- -- @module Core.Menu -- @image Core_Menu.JPG - MENU_INDEX = {} MENU_INDEX.MenuMission = {} MENU_INDEX.MenuMission.Menus = {} @@ -64,23 +63,21 @@ MENU_INDEX.Coalition[coalition.side.RED] = {} MENU_INDEX.Coalition[coalition.side.RED].Menus = {} MENU_INDEX.Group = {} - - function MENU_INDEX:ParentPath( ParentMenu, MenuText ) local Path = ParentMenu and "@" .. table.concat( ParentMenu.MenuPath or {}, "@" ) or "" - if ParentMenu then + if ParentMenu then if ParentMenu:IsInstanceOf( "MENU_GROUP" ) or ParentMenu:IsInstanceOf( "MENU_GROUP_COMMAND" ) then local GroupName = ParentMenu.Group:GetName() if not self.Group[GroupName].Menus[Path] then - BASE:E( { Path = Path, GroupName = GroupName } ) + BASE:E( { Path = Path, GroupName = GroupName } ) error( "Parent path not found in menu index for group menu" ) return nil end elseif ParentMenu:IsInstanceOf( "MENU_COALITION" ) or ParentMenu:IsInstanceOf( "MENU_COALITION_COMMAND" ) then local Coalition = ParentMenu.Coalition if not self.Coalition[Coalition].Menus[Path] then - BASE:E( { Path = Path, Coalition = Coalition } ) + BASE:E( { Path = Path, Coalition = Coalition } ) error( "Parent path not found in menu index for coalition menu" ) return nil end @@ -92,35 +89,31 @@ function MENU_INDEX:ParentPath( ParentMenu, MenuText ) end end end - + Path = Path .. "@" .. MenuText return Path end - function MENU_INDEX:PrepareMission() - self.MenuMission.Menus = self.MenuMission.Menus or {} + self.MenuMission.Menus = self.MenuMission.Menus or {} end - function MENU_INDEX:PrepareCoalition( CoalitionSide ) - self.Coalition[CoalitionSide] = self.Coalition[CoalitionSide] or {} - self.Coalition[CoalitionSide].Menus = self.Coalition[CoalitionSide].Menus or {} + self.Coalition[CoalitionSide] = self.Coalition[CoalitionSide] or {} + self.Coalition[CoalitionSide].Menus = self.Coalition[CoalitionSide].Menus or {} end --- -- @param Wrapper.Group#GROUP Group function MENU_INDEX:PrepareGroup( Group ) - if Group and Group:IsAlive() ~= nil then -- something was changed here! + if Group and Group:IsAlive() ~= nil then -- something was changed here! local GroupName = Group:GetName() self.Group[GroupName] = self.Group[GroupName] or {} self.Group[GroupName].Menus = self.Group[GroupName].Menus or {} end end - - function MENU_INDEX:HasMissionMenu( Path ) return self.MenuMission.Menus[Path] @@ -136,8 +129,6 @@ function MENU_INDEX:ClearMissionMenu( Path ) self.MenuMission.Menus[Path] = nil end - - function MENU_INDEX:HasCoalitionMenu( Coalition, Path ) return self.Coalition[Coalition].Menus[Path] @@ -153,8 +144,6 @@ function MENU_INDEX:ClearCoalitionMenu( Coalition, Path ) self.Coalition[Coalition].Menus[Path] = nil end - - function MENU_INDEX:HasGroupMenu( Group, Path ) if Group and Group:IsAlive() then local MenuGroupName = Group:GetName() @@ -166,7 +155,7 @@ end function MENU_INDEX:SetGroupMenu( Group, Path, Menu ) local MenuGroupName = Group:GetName() - Group:F({MenuGroupName=MenuGroupName,Path=Path}) + Group:F( { MenuGroupName = MenuGroupName, Path = Path } ) self.Group[MenuGroupName].Menus[Path] = Menu end @@ -178,32 +167,25 @@ end function MENU_INDEX:Refresh( Group ) - for MenuID, Menu in pairs( self.MenuMission.Menus ) do - Menu:Refresh() - end + for MenuID, Menu in pairs( self.MenuMission.Menus ) do + Menu:Refresh() + end - for MenuID, Menu in pairs( self.Coalition[coalition.side.BLUE].Menus ) do - Menu:Refresh() - end + for MenuID, Menu in pairs( self.Coalition[coalition.side.BLUE].Menus ) do + Menu:Refresh() + end - for MenuID, Menu in pairs( self.Coalition[coalition.side.RED].Menus ) do - Menu:Refresh() - end + for MenuID, Menu in pairs( self.Coalition[coalition.side.RED].Menus ) do + Menu:Refresh() + end - local GroupName = Group:GetName() - for MenuID, Menu in pairs( self.Group[GroupName].Menus ) do - Menu:Refresh() - end + local GroupName = Group:GetName() + for MenuID, Menu in pairs( self.Group[GroupName].Menus ) do + Menu:Refresh() + end end - - - - - - - do -- MENU_BASE --- @type MENU_BASE @@ -216,37 +198,37 @@ do -- MENU_BASE ClassName = "MENU_BASE", MenuPath = nil, MenuText = "", - MenuParentPath = nil + MenuParentPath = nil, } - - --- Consructor + + --- Constructor -- @param #MENU_BASE -- @return #MENU_BASE 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.ParentMenu = ParentMenu - self.MenuParentPath = MenuParentPath - self.Path = ( self.ParentMenu and "@" .. table.concat( self.MenuParentPath or {}, "@" ) or "" ) .. "@" .. self.MenuText + local self = BASE:Inherit( self, BASE:New() ) + + self.MenuPath = nil + self.MenuText = MenuText + self.ParentMenu = ParentMenu + self.MenuParentPath = MenuParentPath + self.Path = (self.ParentMenu and "@" .. table.concat( self.MenuParentPath or {}, "@" ) or "") .. "@" .. self.MenuText self.Menus = {} self.MenuCount = 0 self.MenuStamp = timer.getTime() self.MenuRemoveParent = false - + if self.ParentMenu then self.ParentMenu.Menus = self.ParentMenu.Menus or {} self.ParentMenu.Menus[MenuText] = self end - - return self + + return self end function MENU_BASE:SetParentMenu( MenuText, Menu ) @@ -262,7 +244,7 @@ do -- MENU_BASE self.ParentMenu.Menus[MenuText] = nil self.ParentMenu.MenuCount = self.ParentMenu.MenuCount - 1 if self.ParentMenu.MenuCount == 0 then - --self.ParentMenu:Remove() + -- self.ParentMenu:Remove() end end end @@ -272,12 +254,11 @@ do -- MENU_BASE -- @param #boolean RemoveParent If true, the parent menu is automatically removed when this menu is the last child menu of that parent @{Menu}. -- @return #MENU_BASE function MENU_BASE:SetRemoveParent( RemoveParent ) - --self:F( { RemoveParent } ) + -- self:F( { RemoveParent } ) self.MenuRemoveParent = RemoveParent return self end - --- Gets a @{Menu} from a parent @{Menu} -- @param #MENU_BASE self -- @param #string MenuText The text of the child menu. @@ -294,16 +275,14 @@ do -- MENU_BASE self.MenuStamp = MenuStamp return self end - - + --- Gets a menu stamp for later prevention of menu removal. -- @param #MENU_BASE self -- @return MenuStamp function MENU_BASE:GetStamp() return timer.getTime() end - - + --- Sets a time stamp for later prevention of menu removal. -- @param #MENU_BASE self -- @param MenuStamp @@ -312,7 +291,7 @@ do -- MENU_BASE self.MenuStamp = MenuStamp return self end - + --- Sets a tag for later selection of menu refresh. -- @param #MENU_BASE self -- @param #string MenuTag A Tag or Key that will filter only menu items set with this key. @@ -321,7 +300,7 @@ do -- MENU_BASE self.MenuTag = MenuTag return self end - + end do -- MENU_COMMAND_BASE @@ -329,10 +308,10 @@ do -- MENU_COMMAND_BASE --- @type MENU_COMMAND_BASE -- @field #function MenuCallHandler -- @extends Core.Menu#MENU_BASE - - --- Defines the main MENU class where other MENU COMMAND_ + + --- Defines the main MENU class where other MENU COMMAND_ -- classes are derived from, in order to set commands. - -- + -- -- @field #MENU_COMMAND_BASE MENU_COMMAND_BASE = { ClassName = "MENU_COMMAND_BASE", @@ -340,13 +319,13 @@ do -- MENU_COMMAND_BASE CommandMenuArgument = nil, MenuCallHandler = nil, } - + --- Constructor -- @param #MENU_COMMAND_BASE -- @return #MENU_COMMAND_BASE function MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, CommandMenuArguments ) - - local self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) ) -- #MENU_COMMAND_BASE + + local self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) ) -- #MENU_COMMAND_BASE -- When a menu function goes into error, DCS displays an obscure menu message. -- This error handler catches the menu error and displays the full call stack. @@ -357,20 +336,20 @@ do -- MENU_COMMAND_BASE end return errmsg end - + self:SetCommandMenuFunction( CommandMenuFunction ) self:SetCommandMenuArguments( CommandMenuArguments ) self.MenuCallHandler = function() - local function MenuFunction() + local function MenuFunction() return self.CommandMenuFunction( unpack( self.CommandMenuArguments ) ) end local Status, Result = xpcall( MenuFunction, ErrorHandler ) end - - return self + + return self end - - --- This sets the new command function of a menu, + + --- This sets the new command function of a menu, -- so that if a menu is regenerated, or if command function changes, -- that the function set for the menu is loosely coupled with the menu itself!!! -- If the function changes, no new menu needs to be generated if the menu text is the same!!! @@ -394,43 +373,42 @@ do -- MENU_COMMAND_BASE end - do -- MENU_MISSION --- @type MENU_MISSION -- @extends Core.Menu#MENU_BASE - --- Manages the main menus for a complete mission. - -- + --- 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}. -- @field #MENU_MISSION MENU_MISSION = { - ClassName = "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). + -- @param #table ParentMenu The parent menu. This parameter can be ignored if you want the menu to be located at the parent menu of DCS world (under F10 other). -- @return #MENU_MISSION function MENU_MISSION:New( MenuText, ParentMenu ) - + MENU_INDEX:PrepareMission() local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText ) - local MissionMenu = MENU_INDEX:HasMissionMenu( Path ) + local MissionMenu = MENU_INDEX:HasMissionMenu( Path ) if MissionMenu then return MissionMenu else local self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) ) MENU_INDEX:SetMissionMenu( Path, self ) - + self.MenuPath = missionCommands.addSubMenu( self.MenuText, self.MenuParentPath ) self:SetParentMenu( self.MenuText, self ) return self end - + end --- Refreshes a radio item for a mission @@ -444,33 +422,33 @@ do -- MENU_MISSION end end - + --- Removes the sub menus recursively of this MENU_MISSION. Note that the main menu is kept! -- @param #MENU_MISSION self -- @return #MENU_MISSION function MENU_MISSION:RemoveSubMenus() - + for MenuID, Menu in pairs( self.Menus or {} ) do Menu:Remove() end - + self.Menus = nil - + end - + --- Removes the main menu and the sub menus recursively of this MENU_MISSION. -- @param #MENU_MISSION self -- @return #nil function MENU_MISSION:Remove( MenuStamp, MenuTag ) - + MENU_INDEX:PrepareMission() local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText ) - local MissionMenu = MENU_INDEX:HasMissionMenu( Path ) + local MissionMenu = MENU_INDEX:HasMissionMenu( Path ) if MissionMenu == self then self:RemoveSubMenus() if not MenuStamp or self.MenuStamp ~= MenuStamp then - if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then + if (not MenuTag) or (MenuTag and self.MenuTag and MenuTag == self.MenuTag) then self:F( { Text = self.MenuText, Path = self.MenuPath } ) if self.MenuPath ~= nil then missionCommands.removeItem( self.MenuPath ) @@ -483,29 +461,27 @@ do -- MENU_MISSION else BASE:E( { "Cannot Remove MENU_MISSION", Path = Path, ParentMenu = self.ParentMenu, MenuText = self.MenuText } ) end - + return self end - - end do -- MENU_MISSION_COMMAND - + --- @type MENU_MISSION_COMMAND -- @extends Core.Menu#MENU_COMMAND_BASE - - --- Manages the command menus for a complete mission, which allow players to execute functions during mission execution. - -- + + --- 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}. - -- + -- -- @field #MENU_MISSION_COMMAND MENU_MISSION_COMMAND = { - ClassName = "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. @@ -514,10 +490,10 @@ do -- MENU_MISSION_COMMAND -- @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, ... ) - + MENU_INDEX:PrepareMission() local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText ) - local MissionMenu = MENU_INDEX:HasMissionMenu( Path ) + local MissionMenu = MENU_INDEX:HasMissionMenu( Path ) if MissionMenu then MissionMenu:SetCommandMenuFunction( CommandMenuFunction ) @@ -526,7 +502,7 @@ do -- MENU_MISSION_COMMAND else local self = BASE:Inherit( self, MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, arg ) ) MENU_INDEX:SetMissionMenu( Path, self ) - + self.MenuPath = missionCommands.addCommand( MenuText, self.MenuParentPath, self.MenuCallHandler ) self:SetParentMenu( self.MenuText, self ) return self @@ -544,19 +520,19 @@ do -- MENU_MISSION_COMMAND end end - + --- Removes a radio command item for a coalition -- @param #MENU_MISSION_COMMAND self -- @return #nil function MENU_MISSION_COMMAND:Remove() - + MENU_INDEX:PrepareMission() local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText ) - local MissionMenu = MENU_INDEX:HasMissionMenu( Path ) + local MissionMenu = MENU_INDEX:HasMissionMenu( Path ) if MissionMenu == self then if not MenuStamp or self.MenuStamp ~= MenuStamp then - if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then + if (not MenuTag) or (MenuTag and self.MenuTag and MenuTag == self.MenuTag) then self:F( { Text = self.MenuText, Path = self.MenuPath } ) if self.MenuPath ~= nil then missionCommands.removeItem( self.MenuPath ) @@ -569,24 +545,22 @@ do -- MENU_MISSION_COMMAND else BASE:E( { "Cannot Remove MENU_MISSION_COMMAND", Path = Path, ParentMenu = self.ParentMenu, MenuText = self.MenuText } ) end - + return self end end - - do -- MENU_COALITION --- @type MENU_COALITION -- @extends Core.Menu#MENU_BASE - - --- Manages the main menus for @{DCS.coalition}s. - -- + + --- Manages the main menus for @{DCS.coalition}s. + -- -- 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}. - -- + -- -- -- @usage -- -- This demo creates a menu structure for the planes within the red coalition. @@ -616,7 +590,7 @@ do -- MENU_COALITION -- 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" ) @@ -627,20 +601,20 @@ do -- MENU_COALITION -- -- @field #MENU_COALITION MENU_COALITION = { - ClassName = "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 DCS#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). + -- @param #table ParentMenu The parent menu. This parameter can be ignored if you want the menu to be located at the parent menu of DCS world (under F10 other). -- @return #MENU_COALITION self function MENU_COALITION:New( Coalition, MenuText, ParentMenu ) MENU_INDEX:PrepareCoalition( Coalition ) local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText ) - local CoalitionMenu = MENU_INDEX:HasCoalitionMenu( Coalition, Path ) + local CoalitionMenu = MENU_INDEX:HasCoalitionMenu( Coalition, Path ) if CoalitionMenu then return CoalitionMenu @@ -648,9 +622,9 @@ do -- MENU_COALITION local self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) ) MENU_INDEX:SetCoalitionMenu( Coalition, Path, self ) - + self.Coalition = Coalition - + self.MenuPath = missionCommands.addSubMenuForCoalition( Coalition, MenuText, self.MenuParentPath ) self:SetParentMenu( self.MenuText, self ) return self @@ -668,32 +642,32 @@ do -- MENU_COALITION end end - + --- Removes the sub menus recursively of this MENU_COALITION. Note that the main menu is kept! -- @param #MENU_COALITION self -- @return #MENU_COALITION function MENU_COALITION:RemoveSubMenus() - + for MenuID, Menu in pairs( self.Menus or {} ) do Menu:Remove() end - + self.Menus = nil end - + --- Removes the main menu and the sub menus recursively of this MENU_COALITION. -- @param #MENU_COALITION self -- @return #nil function MENU_COALITION:Remove( MenuStamp, MenuTag ) - + MENU_INDEX:PrepareCoalition( self.Coalition ) local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText ) - local CoalitionMenu = MENU_INDEX:HasCoalitionMenu( self.Coalition, Path ) + local CoalitionMenu = MENU_INDEX:HasCoalitionMenu( self.Coalition, Path ) if CoalitionMenu == self then self:RemoveSubMenus() if not MenuStamp or self.MenuStamp ~= MenuStamp then - if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then + if (not MenuTag) or (MenuTag and self.MenuTag and MenuTag == self.MenuTag) then self:F( { Coalition = self.Coalition, Text = self.MenuText, Path = self.MenuPath } ) if self.MenuPath ~= nil then missionCommands.removeItemForCoalition( self.Coalition, self.MenuPath ) @@ -706,29 +680,27 @@ do -- MENU_COALITION else BASE:E( { "Cannot Remove MENU_COALITION", Path = Path, ParentMenu = self.ParentMenu, MenuText = self.MenuText, Coalition = self.Coalition } ) end - + return self end end - - do -- MENU_COALITION_COMMAND - + --- @type MENU_COALITION_COMMAND -- @extends Core.Menu#MENU_COMMAND_BASE - - --- Manages the command menus for coalitions, which allow players to execute functions during mission execution. - -- + + --- 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}. -- -- @field #MENU_COALITION_COMMAND MENU_COALITION_COMMAND = { - ClassName = "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 DCS#coalition.side Coalition The coalition owning the menu. @@ -738,20 +710,20 @@ do -- MENU_COALITION_COMMAND -- @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 function MENU_COALITION_COMMAND:New( Coalition, MenuText, ParentMenu, CommandMenuFunction, ... ) - + MENU_INDEX:PrepareCoalition( Coalition ) local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText ) - local CoalitionMenu = MENU_INDEX:HasCoalitionMenu( Coalition, Path ) + local CoalitionMenu = MENU_INDEX:HasCoalitionMenu( Coalition, Path ) if CoalitionMenu then CoalitionMenu:SetCommandMenuFunction( CommandMenuFunction ) CoalitionMenu:SetCommandMenuArguments( arg ) return CoalitionMenu else - + local self = BASE:Inherit( self, MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, arg ) ) MENU_INDEX:SetCoalitionMenu( Coalition, Path, self ) - + self.Coalition = Coalition self.MenuPath = missionCommands.addCommandForCoalition( self.Coalition, MenuText, self.MenuParentPath, self.MenuCallHandler ) self:SetParentMenu( self.MenuText, self ) @@ -760,7 +732,6 @@ do -- MENU_COALITION_COMMAND end - --- Refreshes a radio item for a coalition -- @param #MENU_COALITION_COMMAND self -- @return #MENU_COALITION_COMMAND @@ -772,19 +743,19 @@ do -- MENU_COALITION_COMMAND end end - + --- Removes a radio command item for a coalition -- @param #MENU_COALITION_COMMAND self -- @return #nil function MENU_COALITION_COMMAND:Remove( MenuStamp, MenuTag ) - + MENU_INDEX:PrepareCoalition( self.Coalition ) local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText ) - local CoalitionMenu = MENU_INDEX:HasCoalitionMenu( self.Coalition, Path ) + local CoalitionMenu = MENU_INDEX:HasCoalitionMenu( self.Coalition, Path ) if CoalitionMenu == self then if not MenuStamp or self.MenuStamp ~= MenuStamp then - if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then + if (not MenuTag) or (MenuTag and self.MenuTag and MenuTag == self.MenuTag) then self:F( { Coalition = self.Coalition, Text = self.MenuText, Path = self.MenuPath } ) if self.MenuPath ~= nil then missionCommands.removeItemForCoalition( self.Coalition, self.MenuPath ) @@ -797,13 +768,12 @@ do -- MENU_COALITION_COMMAND else BASE:E( { "Cannot Remove MENU_COALITION_COMMAND", Path = Path, ParentMenu = self.ParentMenu, MenuText = self.MenuText, Coalition = self.Coalition } ) end - + return self end end - --- MENU_GROUP do @@ -816,20 +786,19 @@ do --- @type MENU_GROUP -- @extends Core.Menu#MENU_BASE - - - --- Manages the main menus for @{Wrapper.Group}s. - -- + + --- Manages the main menus for @{Wrapper.Group}s. + -- -- 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}. - -- + -- -- @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 ) @@ -875,9 +844,9 @@ do -- -- @field #MENU_GROUP MENU_GROUP = { - ClassName = "MENU_GROUP" + ClassName = "MENU_GROUP", } - + --- MENU_GROUP constructor. Creates a new radio menu item for a group. -- @param #MENU_GROUP self -- @param Wrapper.Group#GROUP Group The Group owning the menu. @@ -885,7 +854,7 @@ do -- @param #table ParentMenu The parent menu. -- @return #MENU_GROUP self function MENU_GROUP:New( Group, MenuText, ParentMenu ) - + MENU_INDEX:PrepareGroup( Group ) local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText ) local GroupMenu = MENU_INDEX:HasGroupMenu( Group, Path ) @@ -900,11 +869,11 @@ do self.GroupID = Group:GetID() self.MenuPath = missionCommands.addSubMenuForGroup( self.GroupID, MenuText, self.MenuParentPath ) - + self:SetParentMenu( self.MenuText, self ) return self end - + end --- Refreshes a new radio item for a group and submenus @@ -915,14 +884,14 @@ do do missionCommands.removeItemForGroup( self.GroupID, self.MenuPath ) missionCommands.addSubMenuForGroup( self.GroupID, self.MenuText, self.MenuParentPath ) - + for MenuText, Menu in pairs( self.Menus or {} ) do Menu:Refresh() end end end - + --- Removes the sub menus recursively of this MENU_GROUP. -- @param #MENU_GROUP self -- @param MenuStamp @@ -933,11 +902,10 @@ do for MenuText, Menu in pairs( self.Menus or {} ) do Menu:Remove( MenuStamp, MenuTag ) end - - self.Menus = nil - - end + self.Menus = nil + + end --- Removes the main menu and sub menus recursively of this MENU_GROUP. -- @param #MENU_GROUP self @@ -948,12 +916,12 @@ do MENU_INDEX:PrepareGroup( self.Group ) local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText ) - local GroupMenu = MENU_INDEX:HasGroupMenu( self.Group, Path ) + local GroupMenu = MENU_INDEX:HasGroupMenu( self.Group, Path ) if GroupMenu == self then self:RemoveSubMenus( MenuStamp, MenuTag ) if not MenuStamp or self.MenuStamp ~= MenuStamp then - if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then + if (not MenuTag) or (MenuTag and self.MenuTag and MenuTag == self.MenuTag) then if self.MenuPath ~= nil then self:F( { Group = self.GroupID, Text = self.MenuText, Path = self.MenuPath } ) missionCommands.removeItemForGroup( self.GroupID, self.MenuPath ) @@ -967,23 +935,22 @@ do BASE:E( { "Cannot Remove MENU_GROUP", Path = Path, ParentMenu = self.ParentMenu, MenuText = self.MenuText, Group = self.Group } ) return nil end - + return self end - - + --- @type MENU_GROUP_COMMAND -- @extends Core.Menu#MENU_COMMAND_BASE - - --- The @{Core.Menu#MENU_GROUP_COMMAND} class manages the command menus for coalitions, which allow players to execute functions during mission execution. + + --- The @{Core.Menu#MENU_GROUP_COMMAND} class manages the command menus for coalitions, which allow players to execute functions during mission execution. -- You can add menus with the @{#MENU_GROUP_COMMAND.New} method, which constructs a MENU_GROUP_COMMAND object and returns you the object reference. -- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_GROUP_COMMAND.Remove}. -- -- @field #MENU_GROUP_COMMAND MENU_GROUP_COMMAND = { - ClassName = "MENU_GROUP_COMMAND" + ClassName = "MENU_GROUP_COMMAND", } - + --- Creates a new radio command item for a group -- @param #MENU_GROUP_COMMAND self -- @param Wrapper.Group#GROUP Group The Group owning the menu. @@ -996,7 +963,7 @@ do MENU_INDEX:PrepareGroup( Group ) local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText ) - local GroupMenu = MENU_INDEX:HasGroupMenu( Group, Path ) + local GroupMenu = MENU_INDEX:HasGroupMenu( Group, Path ) if GroupMenu then GroupMenu:SetCommandMenuFunction( CommandMenuFunction ) @@ -1006,12 +973,12 @@ do self = BASE:Inherit( self, MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, arg ) ) MENU_INDEX:SetGroupMenu( Group, Path, self ) - + self.Group = Group self.GroupID = Group:GetID() - + self.MenuPath = missionCommands.addCommandForGroup( self.GroupID, MenuText, self.MenuParentPath, self.MenuCallHandler ) - + self:SetParentMenu( self.MenuText, self ) return self end @@ -1029,7 +996,7 @@ do end end - + --- Removes a menu structure for a group. -- @param #MENU_GROUP_COMMAND self -- @param MenuStamp @@ -1039,13 +1006,13 @@ do MENU_INDEX:PrepareGroup( self.Group ) local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText ) - local GroupMenu = MENU_INDEX:HasGroupMenu( self.Group, Path ) + local GroupMenu = MENU_INDEX:HasGroupMenu( self.Group, Path ) if GroupMenu == self then if not MenuStamp or self.MenuStamp ~= MenuStamp then - if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then + if (not MenuTag) or (MenuTag and self.MenuTag and MenuTag == self.MenuTag) then if self.MenuPath ~= nil then - self:F( { Group = self.GroupID, Text = self.MenuText, Path = self.MenuPath } ) + self:F( { Group = self.GroupID, Text = self.MenuText, Path = self.MenuPath } ) missionCommands.removeItemForGroup( self.GroupID, self.MenuPath ) end MENU_INDEX:ClearGroupMenu( self.Group, Path ) @@ -1056,7 +1023,7 @@ do else BASE:E( { "Cannot Remove MENU_GROUP_COMMAND", Path = Path, ParentMenu = self.ParentMenu, MenuText = self.MenuText, Group = self.Group } ) end - + return self end @@ -1068,9 +1035,8 @@ do --- @type MENU_GROUP_DELAYED -- @extends Core.Menu#MENU_BASE - - - --- The MENU_GROUP_DELAYED class manages the main menus for groups. + + --- The MENU_GROUP_DELAYED class manages the main menus for groups. -- 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}. -- The creation of the menu item is delayed however, and must be created using the @{#MENU_GROUP.Set} method. @@ -1079,9 +1045,9 @@ do -- -- @field #MENU_GROUP_DELAYED MENU_GROUP_DELAYED = { - ClassName = "MENU_GROUP_DELAYED" + ClassName = "MENU_GROUP_DELAYED", } - + --- MENU_GROUP_DELAYED constructor. Creates a new radio menu item for a group. -- @param #MENU_GROUP_DELAYED self -- @param Wrapper.Group#GROUP Group The Group owning the menu. @@ -1089,7 +1055,7 @@ do -- @param #table ParentMenu The parent menu. -- @return #MENU_GROUP_DELAYED self function MENU_GROUP_DELAYED:New( Group, MenuText, ParentMenu ) - + MENU_INDEX:PrepareGroup( Group ) local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText ) local GroupMenu = MENU_INDEX:HasGroupMenu( Group, Path ) @@ -1109,13 +1075,12 @@ do self.MenuPath = {} end table.insert( self.MenuPath, self.MenuText ) - + self:SetParentMenu( self.MenuText, self ) return self end - - end + end --- Refreshes a new radio item for a group and submenus -- @param #MENU_GROUP_DELAYED self @@ -1127,7 +1092,7 @@ do missionCommands.addSubMenuForGroup( self.GroupID, self.MenuText, self.MenuParentPath ) self.MenuSet = true end - + for MenuText, Menu in pairs( self.Menus or {} ) do Menu:Set() end @@ -1135,7 +1100,6 @@ do end - --- Refreshes a new radio item for a group and submenus -- @param #MENU_GROUP_DELAYED self -- @return #MENU_GROUP_DELAYED @@ -1144,14 +1108,14 @@ do do missionCommands.removeItemForGroup( self.GroupID, self.MenuPath ) missionCommands.addSubMenuForGroup( self.GroupID, self.MenuText, self.MenuParentPath ) - + for MenuText, Menu in pairs( self.Menus or {} ) do Menu:Refresh() end end end - + --- Removes the sub menus recursively of this MENU_GROUP_DELAYED. -- @param #MENU_GROUP_DELAYED self -- @param MenuStamp @@ -1162,11 +1126,10 @@ do for MenuText, Menu in pairs( self.Menus or {} ) do Menu:Remove( MenuStamp, MenuTag ) end - - self.Menus = nil - - end + self.Menus = nil + + end --- Removes the main menu and sub menus recursively of this MENU_GROUP. -- @param #MENU_GROUP_DELAYED self @@ -1177,12 +1140,12 @@ do MENU_INDEX:PrepareGroup( self.Group ) local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText ) - local GroupMenu = MENU_INDEX:HasGroupMenu( self.Group, Path ) + local GroupMenu = MENU_INDEX:HasGroupMenu( self.Group, Path ) if GroupMenu == self then self:RemoveSubMenus( MenuStamp, MenuTag ) if not MenuStamp or self.MenuStamp ~= MenuStamp then - if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then + if (not MenuTag) or (MenuTag and self.MenuTag and MenuTag == self.MenuTag) then if self.MenuPath ~= nil then self:F( { Group = self.GroupID, Text = self.MenuText, Path = self.MenuPath } ) missionCommands.removeItemForGroup( self.GroupID, self.MenuPath ) @@ -1196,24 +1159,23 @@ do BASE:E( { "Cannot Remove MENU_GROUP_DELAYED", Path = Path, ParentMenu = self.ParentMenu, MenuText = self.MenuText, Group = self.Group } ) return nil end - + return self end - - + --- @type MENU_GROUP_COMMAND_DELAYED -- @extends Core.Menu#MENU_COMMAND_BASE - - --- Manages the command menus for coalitions, which allow players to execute functions during mission execution. - -- + + --- Manages the command menus for coalitions, which allow players to execute functions during mission execution. + -- -- You can add menus with the @{#MENU_GROUP_COMMAND_DELAYED.New} method, which constructs a MENU_GROUP_COMMAND_DELAYED 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_DELAYED.Remove}. -- -- @field #MENU_GROUP_COMMAND_DELAYED MENU_GROUP_COMMAND_DELAYED = { - ClassName = "MENU_GROUP_COMMAND_DELAYED" + ClassName = "MENU_GROUP_COMMAND_DELAYED", } - + --- Creates a new radio command item for a group -- @param #MENU_GROUP_COMMAND_DELAYED self -- @param Wrapper.Group#GROUP Group The Group owning the menu. @@ -1226,7 +1188,7 @@ do MENU_INDEX:PrepareGroup( Group ) local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText ) - local GroupMenu = MENU_INDEX:HasGroupMenu( Group, Path ) + local GroupMenu = MENU_INDEX:HasGroupMenu( Group, Path ) if GroupMenu then GroupMenu:SetCommandMenuFunction( CommandMenuFunction ) @@ -1236,17 +1198,17 @@ do self = BASE:Inherit( self, MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, arg ) ) MENU_INDEX:SetGroupMenu( Group, Path, self ) - + self.Group = Group self.GroupID = Group:GetID() - + if self.MenuParentPath then self.MenuPath = UTILS.DeepCopy( self.MenuParentPath ) else self.MenuPath = {} end table.insert( self.MenuPath, self.MenuText ) - + self:SetParentMenu( self.MenuText, self ) return self end @@ -1266,7 +1228,7 @@ do end end - + --- Refreshes a radio item for a group -- @param #MENU_GROUP_COMMAND_DELAYED self -- @return #MENU_GROUP_COMMAND_DELAYED @@ -1278,7 +1240,7 @@ do end end - + --- Removes a menu structure for a group. -- @param #MENU_GROUP_COMMAND_DELAYED self -- @param MenuStamp @@ -1288,11 +1250,11 @@ do MENU_INDEX:PrepareGroup( self.Group ) local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText ) - local GroupMenu = MENU_INDEX:HasGroupMenu( self.Group, Path ) + local GroupMenu = MENU_INDEX:HasGroupMenu( self.Group, Path ) if GroupMenu == self then if not MenuStamp or self.MenuStamp ~= MenuStamp then - if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then + if (not MenuTag) or (MenuTag and self.MenuTag and MenuTag == self.MenuTag) then if self.MenuPath ~= nil then self:F( { Group = self.GroupID, Text = self.MenuText, Path = self.MenuPath } ) missionCommands.removeItemForGroup( self.GroupID, self.MenuPath ) @@ -1305,7 +1267,7 @@ do else BASE:E( { "Cannot Remove MENU_GROUP_COMMAND_DELAYED", Path = Path, ParentMenu = self.ParentMenu, MenuText = self.MenuText, Group = self.Group } ) end - + return self end diff --git a/Moose Development/Moose/Core/Message.lua b/Moose Development/Moose/Core/Message.lua index 8e1f8324f..7daf9c671 100644 --- a/Moose Development/Moose/Core/Message.lua +++ b/Moose Development/Moose/Core/Message.lua @@ -1,18 +1,18 @@ --- **Core** - Informs the players using messages during a simulation. --- +-- -- === --- +-- -- ## Features: --- +-- -- * A more advanced messaging system using the DCS message system. -- * Time messages. -- * Send messages based on a message type, which has a pre-defined duration that can be tweaked in SETTINGS. -- * Send message to all players. -- * Send messages to a coalition. -- * Send messages to a specific group. --- +-- -- === --- +-- -- @module Core.Message -- @image Core_Message.JPG @@ -23,14 +23,14 @@ --- 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. --- +-- -- ## MESSAGE construction --- +-- -- Messages are created with @{#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. --- +-- -- ## Send messages to an audience --- +-- -- Messages are sent: -- -- * To a @{Client} using @{#MESSAGE.ToClient}(). @@ -39,26 +39,26 @@ -- * To the red coalition using @{#MESSAGE.ToRed}(). -- * To the blue coalition using @{#MESSAGE.ToBlue}(). -- * To all Players using @{#MESSAGE.ToAll}(). --- +-- -- ## Send conditionally to an audience --- +-- -- Messages can be sent conditionally to an audience (when a condition is true): --- +-- -- * To all players using @{#MESSAGE.ToAllIf}(). -- * To a coalition using @{#MESSAGE.ToCoalitionIf}(). --- +-- -- === --- +-- -- ### Author: **FlightControl** --- ### Contributions: --- +-- ### Contributions: +-- -- === --- +-- -- @field #MESSAGE MESSAGE = { - ClassName = "MESSAGE", - MessageCategory = 0, - MessageID = 0, + ClassName = "MESSAGE", + MessageCategory = 0, + MessageID = 0, } --- Message Types @@ -68,10 +68,9 @@ MESSAGE.Type = { Information = "Information", Briefing = "Briefing Report", Overview = "Overview Report", - Detailed = "Detailed Report" + Detailed = "Detailed Report", } - --- 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. @@ -80,52 +79,52 @@ MESSAGE.Type = { -- @param #boolean ClearScreen (optional) Clear all previous messages if true. -- @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") +-- +-- -- 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, ClearScreen ) - local self = BASE:Inherit( self, BASE:New() ) - self:F( { MessageText, MessageDuration, MessageCategory } ) - + local self = BASE:Inherit( self, BASE:New() ) + self:F( { MessageText, MessageDuration, MessageCategory } ) self.MessageType = nil - + -- When no MessageCategory is given, we don't show it as a title... - if MessageCategory and MessageCategory ~= "" then - if MessageCategory:sub(-1) ~= "\n" then + if MessageCategory and MessageCategory ~= "" then + if MessageCategory:sub( -1 ) ~= "\n" then self.MessageCategory = MessageCategory .. ": " else - self.MessageCategory = MessageCategory:sub( 1, -2 ) .. ":\n" + self.MessageCategory = MessageCategory:sub( 1, -2 ) .. ":\n" end else self.MessageCategory = "" end - - self.ClearScreen=false - if ClearScreen~=nil then - self.ClearScreen=ClearScreen + + self.ClearScreen = false + if ClearScreen ~= nil then + self.ClearScreen = ClearScreen end - self.MessageDuration = MessageDuration or 5 - self.MessageTime = timer.getTime() - self.MessageText = MessageText:gsub("^\n","",1):gsub("\n$","",1) - - self.MessageSent = false - self.MessageGroup = false - self.MessageCoalition = false + self.MessageDuration = MessageDuration or 5 + self.MessageTime = timer.getTime() + self.MessageText = MessageText:gsub( "^\n", "", 1 ):gsub( "\n$", "", 1 ) - return self + self.MessageSent = false + self.MessageGroup = false + self.MessageCoalition = false + + return self end - ---- Creates a new MESSAGE object of a certain type. --- Note that these MESSAGE objects are not yet displayed on the display panel. +--- Creates a new MESSAGE object of a certain type. +-- 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. -- The message display times are automatically defined based on the timing settings in the @{Settings} menu. -- @param self @@ -134,83 +133,83 @@ end -- @param #boolean ClearScreen (optional) Clear all previous messages. -- @return #MESSAGE -- @usage +-- -- MessageAll = MESSAGE:NewType( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", MESSAGE.Type.Information ) -- MessageRED = MESSAGE:NewType( "To the RED Players: You receive a penalty because you've killed one of your own units", MESSAGE.Type.Information ) -- MessageClient1 = MESSAGE:NewType( "Congratulations, you've just hit a target", MESSAGE.Type.Update ) -- MessageClient2 = MESSAGE:NewType( "Congratulations, you've just killed a target", MESSAGE.Type.Update ) +-- function MESSAGE:NewType( MessageText, MessageType, ClearScreen ) local self = BASE:Inherit( self, BASE:New() ) self:F( { MessageText } ) - + self.MessageType = MessageType - - self.ClearScreen=false - if ClearScreen~=nil then - self.ClearScreen=ClearScreen + + self.ClearScreen = false + if ClearScreen ~= nil then + self.ClearScreen = ClearScreen end self.MessageTime = timer.getTime() - self.MessageText = MessageText:gsub("^\n","",1):gsub("\n$","",1) - + self.MessageText = MessageText:gsub( "^\n", "", 1 ):gsub( "\n$", "", 1 ) + return self end - - ---- Clears all previous messages from the screen before the new message is displayed. Not that this must come before all functions starting with ToX(), e.g. ToAll(), ToGroup() etc. +--- Clears all previous messages from the screen before the new message is displayed. Not that this must come before all functions starting with ToX(), e.g. ToAll(), ToGroup() etc. -- @param #MESSAGE self -- @return #MESSAGE function MESSAGE:Clear() self:F() - self.ClearScreen=true + self.ClearScreen = true return self end - - --- Sends a MESSAGE to a Client Group. Note that the Group needs to be defined within the ME with the skillset "Client" or "Player". -- @param #MESSAGE self -- @param Wrapper.Client#CLIENT Client is the Group of the Client. -- @param Core.Settings#SETTINGS Settings Settings used to display the message. -- @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 ) +-- -- 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, Settings ) - self:F( Client ) + self:F( Client ) - if Client and Client:GetClientGroupID() then + if Client and Client:GetClientGroupID() then if self.MessageType then - local Settings = Settings or ( Client and _DATABASE:GetPlayerSettings( Client:GetPlayerName() ) ) or _SETTINGS -- Core.Settings#SETTINGS + local Settings = Settings or (Client and _DATABASE:GetPlayerSettings( Client:GetPlayerName() )) or _SETTINGS -- Core.Settings#SETTINGS self.MessageDuration = Settings:GetMessageTime( self.MessageType ) self.MessageCategory = "" -- self.MessageType .. ": " end if self.MessageDuration ~= 0 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 , self.ClearScreen) - end - end - - return self + 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, self.ClearScreen ) + end + end + + return self end ---- Sends a MESSAGE to a Group. +--- Sends a MESSAGE to a Group. -- @param #MESSAGE self -- @param Wrapper.Group#GROUP Group to which the message is displayed. -- @return #MESSAGE Message object. @@ -218,74 +217,80 @@ function MESSAGE:ToGroup( Group, Settings ) self:F( Group.GroupName ) if Group then - + if self.MessageType then - local Settings = Settings or ( Group and _DATABASE:GetPlayerSettings( Group:GetPlayerName() ) ) or _SETTINGS -- Core.Settings#SETTINGS + local Settings = Settings or (Group and _DATABASE:GetPlayerSettings( Group:GetPlayerName() )) or _SETTINGS -- Core.Settings#SETTINGS self.MessageDuration = Settings:GetMessageTime( self.MessageType ) self.MessageCategory = "" -- self.MessageType .. ": " end if self.MessageDuration ~= 0 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, self.ClearScreen ) + 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, self.ClearScreen ) end 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() +-- +-- -- 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.RED ) - - return self + self:ToCoalition( coalition.side.BLUE ) + + return self end ---- Sends a MESSAGE to a Coalition. +--- 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 #DCS.coalition.side CoalitionSide @{#DCS.coalition.side} to which the message is displayed. -- @param Core.Settings#SETTINGS Settings (Optional) Settings for message display. -- @return #MESSAGE Message object. -- @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 ) +-- +-- -- 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, Settings ) - self:F( CoalitionSide ) + self:F( CoalitionSide ) if self.MessageType then local Settings = Settings or _SETTINGS -- Core.Settings#SETTINGS @@ -293,20 +298,20 @@ function MESSAGE:ToCoalition( CoalitionSide, Settings ) self.MessageCategory = "" -- self.MessageType .. ": " end - if CoalitionSide then + if CoalitionSide then if self.MessageDuration ~= 0 then - self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration ) - trigger.action.outTextForCoalition( CoalitionSide, self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration, self.ClearScreen ) + self:T( self.MessageCategory .. self.MessageText:gsub( "\n$", "" ):gsub( "\n$", "" ) .. " / " .. self.MessageDuration ) + trigger.action.outTextForCoalition( CoalitionSide, self.MessageText:gsub( "\n$", "" ):gsub( "\n$", "" ), self.MessageDuration, self.ClearScreen ) end - end - - return self + end + + return self end ---- Sends a MESSAGE to a Coalition if the given Condition is true. +--- Sends a MESSAGE to a Coalition if the given Condition is true. -- @param #MESSAGE self -- @param CoalitionSide needs to be filled out by the defined structure of the standard scripting engine @{coalition.side}. --- @param #boolean Condition Sends the message only if the condition is true. +-- @param #boolean Condition Sends the message only if the condition is true. -- @return #MESSAGE self function MESSAGE:ToCoalitionIf( CoalitionSide, Condition ) self:F( CoalitionSide ) @@ -314,7 +319,7 @@ function MESSAGE:ToCoalitionIf( CoalitionSide, Condition ) if Condition and Condition == true then self:ToCoalition( CoalitionSide ) end - + return self end @@ -323,14 +328,16 @@ end -- @param Core.Settings#Settings Settings (Optional) Settings for message display. -- @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(Settings) +-- +-- -- 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( Settings ) self:F() if self.MessageType then @@ -340,14 +347,13 @@ function MESSAGE:ToAll(Settings) end if self.MessageDuration ~= 0 then - self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration ) - trigger.action.outText( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration, self.ClearScreen ) + self:T( self.MessageCategory .. self.MessageText:gsub( "\n$", "" ):gsub( "\n$", "" ) .. " / " .. self.MessageDuration ) + trigger.action.outText( self.MessageCategory .. self.MessageText:gsub( "\n$", "" ):gsub( "\n$", "" ), self.MessageDuration, self.ClearScreen ) end return self end - --- Sends a MESSAGE to all players if the given Condition is true. -- @param #MESSAGE self -- @return #MESSAGE @@ -357,5 +363,5 @@ function MESSAGE:ToAllIf( Condition ) self:ToAll() end - return self + return self end diff --git a/Moose Development/Moose/Core/Report.lua b/Moose Development/Moose/Core/Report.lua index bd860996b..d8225adcc 100644 --- a/Moose Development/Moose/Core/Report.lua +++ b/Moose Development/Moose/Core/Report.lua @@ -1,13 +1,13 @@ --- **Core** - Provides a handy means to create messages and reports. -- -- === --- +-- -- ## Features: --- +-- -- * Create text blocks that are formatted. -- * Create automatic indents. -- * Variate the delimiters between reporting lines. --- +-- -- === -- -- ### Authors: FlightControl : Design & Programming @@ -15,7 +15,6 @@ -- @module Core.Report -- @image Core_Report.JPG - --- @type REPORT -- @extends Core.Base#BASE @@ -36,7 +35,7 @@ function REPORT:New( Title ) self.Report = {} - self:SetTitle( Title or "" ) + self:SetTitle( Title or "" ) self:SetIndent( 3 ) return self @@ -45,28 +44,26 @@ end --- Has the REPORT Text? -- @param #REPORT self -- @return #boolean -function REPORT:HasText() --R2.1 - +function REPORT:HasText() -- R2.1 + return #self.Report > 0 end - --- Set indent of a REPORT. -- @param #REPORT self -- @param #number Indent -- @return #REPORT -function REPORT:SetIndent( Indent ) --R2.1 +function REPORT:SetIndent( Indent ) -- R2.1 self.Indent = Indent return self end - --- Add a new line to a REPORT. -- @param #REPORT self -- @param #string Text -- @return #REPORT function REPORT:Add( Text ) - self.Report[#self.Report+1] = Text + self.Report[#self.Report + 1] = Text return self end @@ -76,17 +73,17 @@ end -- @param #string Separator (optional) The start of each report line can begin with an optional separator character. This can be a "-", or "#", or "*". You're free to choose what you find the best. -- @return #REPORT function REPORT:AddIndent( Text, Separator ) - self.Report[#self.Report+1] = ( ( Separator and Separator .. string.rep( " ", self.Indent - 1 ) ) or string.rep(" ", self.Indent ) ) .. Text:gsub("\n","\n"..string.rep( " ", self.Indent ) ) + self.Report[#self.Report + 1] = ((Separator and Separator .. string.rep( " ", self.Indent - 1 )) or string.rep( " ", self.Indent )) .. Text:gsub( "\n", "\n" .. string.rep( " ", self.Indent ) ) return self end ---- Produces the text of the report, taking into account an optional delimeter, which is \n by default. +--- Produces the text of the report, taking into account an optional delimiter, which is \n by default. -- @param #REPORT self -- @param #string Delimiter (optional) A delimiter text. -- @return #string The report text. function REPORT:Text( Delimiter ) Delimiter = Delimiter or "\n" - local ReportText = ( self.Title ~= "" and self.Title .. Delimiter or self.Title ) .. table.concat( self.Report, Delimiter ) or "" + local ReportText = (self.Title ~= "" and self.Title .. Delimiter or self.Title) .. table.concat( self.Report, Delimiter ) or "" return ReportText end @@ -95,7 +92,7 @@ end -- @param #string Title The title of the report. -- @return #REPORT function REPORT:SetTitle( Title ) - self.Title = Title + self.Title = Title return self end diff --git a/Moose Development/Moose/Core/ScheduleDispatcher.lua b/Moose Development/Moose/Core/ScheduleDispatcher.lua index bfd1dabb4..145759bca 100644 --- a/Moose Development/Moose/Core/ScheduleDispatcher.lua +++ b/Moose Development/Moose/Core/ScheduleDispatcher.lua @@ -1,36 +1,36 @@ --- **Core** -- SCHEDULEDISPATCHER dispatches the different schedules. --- +-- -- === --- +-- -- Takes care of the creation and dispatching of scheduled functions for SCHEDULER objects. --- +-- -- This class is tricky and needs some thorough explanation. -- SCHEDULE classes are used to schedule functions for objects, or as persistent objects. -- The SCHEDULEDISPATCHER class ensures that: --- +-- -- - Scheduled functions are planned according the SCHEDULER object parameters. -- - Scheduled functions are repeated when requested, according the SCHEDULER object parameters. -- - Scheduled functions are automatically removed when the schedule is finished, according the SCHEDULER object parameters. --- +-- -- The SCHEDULEDISPATCHER class will manage SCHEDULER object in memory during garbage collection: --- +-- -- - When a SCHEDULER object is not attached to another object (that is, it's first :Schedule() parameter is nil), then the SCHEDULER object is _persistent_ within memory. -- - When a SCHEDULER object *is* attached to another object, then the SCHEDULER object is _not persistent_ within memory after a garbage collection! --- --- The none persistency of SCHEDULERS attached to objects is required to allow SCHEDULER objects to be garbage collectged, when the parent object is also desroyed or nillified and garbage collected. --- Even when there are pending timer scheduled functions to be executed for the SCHEDULER object, +-- +-- The non-persistency of SCHEDULERS attached to objects is required to allow SCHEDULER objects to be garbage collected when the parent object is destroyed, or set to nil and garbage collected. +-- Even when there are pending timer scheduled functions to be executed for the SCHEDULER object, -- these will not be executed anymore when the SCHEDULER object has been destroyed. --- +-- -- The SCHEDULEDISPATCHER allows multiple scheduled functions to be planned and executed for one SCHEDULER object. -- The SCHEDULER object therefore keeps a table of "CallID's", which are returned after each planning of a new scheduled function by the SCHEDULEDISPATCHER. --- The SCHEDULER object plans new scheduled functions through the @{Core.Scheduler#SCHEDULER.Schedule}() method. +-- The SCHEDULER object plans new scheduled functions through the @{Core.Scheduler#SCHEDULER.Schedule}() method. -- The Schedule() method returns the CallID that is the reference ID for each planned schedule. --- +-- -- === --- +-- -- ### Contributions: - -- ### Authors: FlightControl : Design & Programming --- +-- -- @module Core.ScheduleDispatcher -- @image Core_Schedule_Dispatcher.JPG @@ -38,7 +38,7 @@ -- @type SCHEDULEDISPATCHER -- @field #string ClassName Name of the class. -- @field #number CallID Call ID counter. --- @field #table PersistentSchedulers Persistant schedulers. +-- @field #table PersistentSchedulers Persistent schedulers. -- @field #table ObjectSchedulers Schedulers that only exist as long as the master object exists. -- @field #table Schedule Meta table setmetatable( {}, { __mode = "k" } ). -- @extends Core.Base#BASE @@ -46,11 +46,11 @@ --- The SCHEDULEDISPATCHER structure -- @type SCHEDULEDISPATCHER SCHEDULEDISPATCHER = { - ClassName = "SCHEDULEDISPATCHER", - CallID = 0, - PersistentSchedulers = {}, - ObjectSchedulers = {}, - Schedule = nil, + ClassName = "SCHEDULEDISPATCHER", + CallID = 0, + PersistentSchedulers = {}, + ObjectSchedulers = {}, + Schedule = nil, } --- Player data table holding all important parameters of each player. @@ -58,7 +58,7 @@ SCHEDULEDISPATCHER = { -- @field #function Function The schedule function to be called. -- @field #table Arguments Schedule function arguments. -- @field #number Start Start time in seconds. --- @field #number Repeat Repeat time intervall in seconds. +-- @field #number Repeat Repeat time interval in seconds. -- @field #number Randomize Randomization factor [0,1]. -- @field #number Stop Stop time in seconds. -- @field #number StartTime Time in seconds when the scheduler is created. @@ -77,7 +77,7 @@ end --- Add a Schedule to the ScheduleDispatcher. -- The development of this method was really tidy. --- It is constructed as such that a garbage collection is executed on the weak tables, when the Scheduler is nillified. +-- It is constructed as such that a garbage collection is executed on the weak tables, when the Scheduler is set to nil. -- Nothing of this code should be modified without testing it thoroughly. -- @param #SCHEDULEDISPATCHER self -- @param Core.Scheduler#SCHEDULER Scheduler Scheduler object. @@ -85,7 +85,7 @@ end -- @param #table ScheduleArguments Table of arguments passed to the ScheduleFunction. -- @param #number Start Start time in seconds. -- @param #number Repeat Repeat interval in seconds. --- @param #number Randomize Radomization factor [0,1]. +-- @param #number Randomize Randomization factor [0,1]. -- @param #number Stop Stop time in seconds. -- @param #number TraceLevel Trace level [0,3]. -- @param Core.Fsm#FSM Fsm Finite state model. @@ -95,39 +95,38 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr -- Increase counter. self.CallID = self.CallID + 1 - + -- Create ID. - local CallID = self.CallID .. "#" .. ( Scheduler.MasterObject and Scheduler.MasterObject.GetClassNameAndID and Scheduler.MasterObject:GetClassNameAndID() or "" ) or "" - - self:T2(string.format("Adding schedule #%d CallID=%s", self.CallID, CallID)) + local CallID = self.CallID .. "#" .. (Scheduler.MasterObject and Scheduler.MasterObject.GetClassNameAndID and Scheduler.MasterObject:GetClassNameAndID() or "") or "" + + self:T2( string.format( "Adding schedule #%d CallID=%s", self.CallID, CallID ) ) -- Initialize PersistentSchedulers self.PersistentSchedulers = self.PersistentSchedulers or {} -- Initialize the ObjectSchedulers array, which is a weakly coupled table. -- If the object used as the key is nil, then the garbage collector will remove the item from the Functions array. - self.ObjectSchedulers = self.ObjectSchedulers or setmetatable( {}, { __mode = "v" } ) - + self.ObjectSchedulers = self.ObjectSchedulers or setmetatable( {}, { __mode = "v" } ) + if Scheduler.MasterObject then self.ObjectSchedulers[CallID] = Scheduler - self:F3( { CallID = CallID, ObjectScheduler = tostring(self.ObjectSchedulers[CallID]), MasterObject = tostring(Scheduler.MasterObject) } ) + self:F3( { CallID = CallID, ObjectScheduler = tostring( self.ObjectSchedulers[CallID] ), MasterObject = tostring( Scheduler.MasterObject ) } ) else self.PersistentSchedulers[CallID] = Scheduler self:F3( { CallID = CallID, PersistentScheduler = self.PersistentSchedulers[CallID] } ) end - + self.Schedule = self.Schedule or setmetatable( {}, { __mode = "k" } ) self.Schedule[Scheduler] = self.Schedule[Scheduler] or {} - self.Schedule[Scheduler][CallID] = {} --#SCHEDULEDISPATCHER.ScheduleData + self.Schedule[Scheduler][CallID] = {} -- #SCHEDULEDISPATCHER.ScheduleData self.Schedule[Scheduler][CallID].Function = ScheduleFunction self.Schedule[Scheduler][CallID].Arguments = ScheduleArguments - self.Schedule[Scheduler][CallID].StartTime = timer.getTime() + ( Start or 0 ) + self.Schedule[Scheduler][CallID].StartTime = timer.getTime() + (Start or 0) self.Schedule[Scheduler][CallID].Start = Start + 0.1 self.Schedule[Scheduler][CallID].Repeat = Repeat or 0 self.Schedule[Scheduler][CallID].Randomize = Randomize or 0 self.Schedule[Scheduler][CallID].Stop = Stop - - + -- This section handles the tracing of the scheduled calls. -- Because these calls will be executed with a delay, we inspect the place where these scheduled calls are initiated. -- The Info structure contains the output of the debug.getinfo() calls, which inspects the call stack for the function name, line number and source name. @@ -149,10 +148,10 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr -- Therefore, in the call stack, at the TraceLevel these functions are mentioned as "tail calls", and the Info.name field will be nil as a result. -- To obtain the correct function name for FSM object calls, the function is mentioned in the call stack at a higher stack level. -- So when function name stored in Info.name is nil, then I inspect the function name within the call stack one level higher. - -- So this little piece of code does its magic wonderfully, preformance overhead is neglectible, as scheduled calls don't happen that often. + -- So this little piece of code does its magic wonderfully, performance overhead is negligible, as scheduled calls don't happen that often. local Info = {} - + if debug then TraceLevel = TraceLevel or 2 Info = debug.getinfo( TraceLevel, "nlS" ) @@ -166,7 +165,7 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr --- Function passed to the DCS timer.scheduleFunction() self.Schedule[Scheduler][CallID].CallHandler = function( Params ) - + local CallID = Params.CallID local Info = Params.Info or {} local Source = Info.source or "?" @@ -180,27 +179,27 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr end return errmsg end - - -- Get object or persistant scheduler object. - local Scheduler = self.ObjectSchedulers[CallID] --Core.Scheduler#SCHEDULER + + -- Get object or persistent scheduler object. + local Scheduler = self.ObjectSchedulers[CallID] -- Core.Scheduler#SCHEDULER if not Scheduler then Scheduler = self.PersistentSchedulers[CallID] end - - --self:T3( { Scheduler = Scheduler } ) - + + -- self:T3( { Scheduler = Scheduler } ) + if Scheduler then - local MasterObject = tostring(Scheduler.MasterObject) - - -- Schedule object. - local Schedule = self.Schedule[Scheduler][CallID] --#SCHEDULEDISPATCHER.ScheduleData - - --self:T3( { Schedule = Schedule } ) + local MasterObject = tostring( Scheduler.MasterObject ) - local SchedulerObject = Scheduler.MasterObject --Scheduler.SchedulerObject Now is this the Maste or Scheduler object? + -- Schedule object. + local Schedule = self.Schedule[Scheduler][CallID] -- #SCHEDULEDISPATCHER.ScheduleData + + -- self:T3( { Schedule = Schedule } ) + + local SchedulerObject = Scheduler.MasterObject -- Scheduler.SchedulerObject Now is this the Master or Scheduler object? local ShowTrace = Scheduler.ShowTrace - + local ScheduleFunction = Schedule.Function local ScheduleArguments = Schedule.Arguments or {} local Start = Schedule.Start @@ -208,18 +207,17 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr local Randomize = Schedule.Randomize or 0 local Stop = Schedule.Stop or 0 local ScheduleID = Schedule.ScheduleID - - - local Prefix = ( Repeat == 0 ) and "--->" or "+++>" - + + local Prefix = (Repeat == 0) and "--->" or "+++>" + local Status, Result - --self:E( { SchedulerObject = SchedulerObject } ) + -- self:E( { SchedulerObject = SchedulerObject } ) if SchedulerObject then local function Timer() if ShowTrace then SchedulerObject:T( Prefix .. Name .. ":" .. Line .. " (" .. Source .. ")" ) end - return ScheduleFunction( SchedulerObject, unpack( ScheduleArguments ) ) + return ScheduleFunction( SchedulerObject, unpack( ScheduleArguments ) ) end Status, Result = xpcall( Timer, ErrorHandler ) else @@ -227,40 +225,39 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr if ShowTrace then self:T( Prefix .. Name .. ":" .. Line .. " (" .. Source .. ")" ) end - return ScheduleFunction( unpack( ScheduleArguments ) ) + return ScheduleFunction( unpack( ScheduleArguments ) ) end Status, Result = xpcall( Timer, ErrorHandler ) end - + local CurrentTime = timer.getTime() local StartTime = Schedule.StartTime -- Debug info. - self:F3( { CallID=CallID, ScheduleID=ScheduleID, Master = MasterObject, CurrentTime = CurrentTime, StartTime = StartTime, Start = Start, Repeat = Repeat, Randomize = Randomize, Stop = Stop } ) - - - if Status and (( Result == nil ) or ( Result and Result ~= false ) ) then - - if Repeat ~= 0 and ( ( Stop == 0 ) or ( Stop ~= 0 and CurrentTime <= StartTime + Stop ) ) then - local ScheduleTime = CurrentTime + Repeat + math.random(- ( Randomize * Repeat / 2 ), ( Randomize * Repeat / 2 )) + 0.0001 -- Accuracy - --self:T3( { Repeat = CallID, CurrentTime, ScheduleTime, ScheduleArguments } ) + self:F3( { CallID = CallID, ScheduleID = ScheduleID, Master = MasterObject, CurrentTime = CurrentTime, StartTime = StartTime, Start = Start, Repeat = Repeat, Randomize = Randomize, Stop = Stop } ) + + if Status and ((Result == nil) or (Result and Result ~= false)) then + + if Repeat ~= 0 and ((Stop == 0) or (Stop ~= 0 and CurrentTime <= StartTime + Stop)) then + local ScheduleTime = CurrentTime + Repeat + math.random( -(Randomize * Repeat / 2), (Randomize * Repeat / 2) ) + 0.0001 -- Accuracy + -- self:T3( { Repeat = CallID, CurrentTime, ScheduleTime, ScheduleArguments } ) return ScheduleTime -- returns the next time the function needs to be called. else self:Stop( Scheduler, CallID ) end - + else self:Stop( Scheduler, CallID ) end else self:I( "<<<>" .. Name .. ":" .. Line .. " (" .. Source .. ")" ) end - + return nil end - + self:Start( Scheduler, CallID, Info ) - + return CallID end @@ -284,33 +281,33 @@ end -- @param #string Info (Optional) Debug info. function SCHEDULEDISPATCHER:Start( Scheduler, CallID, Info ) self:F2( { Start = CallID, Scheduler = Scheduler } ) - + if CallID then - - local Schedule = self.Schedule[Scheduler][CallID] --#SCHEDULEDISPATCHER.ScheduleData - + + local Schedule = self.Schedule[Scheduler][CallID] -- #SCHEDULEDISPATCHER.ScheduleData + -- Only start when there is no ScheduleID defined! -- This prevents to "Start" the scheduler twice with the same CallID... if not Schedule.ScheduleID then - + -- Current time in seconds. - local Tnow=timer.getTime() - - Schedule.StartTime = Tnow -- Set the StartTime field to indicate when the scheduler started. - + local Tnow = timer.getTime() + + Schedule.StartTime = Tnow -- Set the StartTime field to indicate when the scheduler started. + -- Start DCS schedule function https://wiki.hoggitworld.com/view/DCS_func_scheduleFunction - Schedule.ScheduleID = timer.scheduleFunction(Schedule.CallHandler, { CallID = CallID, Info = Info }, Tnow + Schedule.Start) - - self:T(string.format("Starting scheduledispatcher Call ID=%s ==> Schedule ID=%s", tostring(CallID), tostring(Schedule.ScheduleID))) + Schedule.ScheduleID = timer.scheduleFunction( Schedule.CallHandler, { CallID = CallID, Info = Info }, Tnow + Schedule.Start ) + + self:T( string.format( "Starting SCHEDULEDISPATCHER Call ID=%s ==> Schedule ID=%s", tostring( CallID ), tostring( Schedule.ScheduleID ) ) ) end - + else - + -- Recursive. for CallID, Schedule in pairs( self.Schedule[Scheduler] or {} ) do self:Start( Scheduler, CallID, Info ) -- Recursive end - + end end @@ -322,29 +319,29 @@ function SCHEDULEDISPATCHER:Stop( Scheduler, CallID ) self:F2( { Stop = CallID, Scheduler = Scheduler } ) if CallID then - - local Schedule = self.Schedule[Scheduler][CallID] --#SCHEDULEDISPATCHER.ScheduleData - + + local Schedule = self.Schedule[Scheduler][CallID] -- #SCHEDULEDISPATCHER.ScheduleData + -- Only stop when there is a ScheduleID defined for the CallID. So, when the scheduler was stopped before, do nothing. if Schedule.ScheduleID then - - self:T(string.format("scheduledispatcher stopping scheduler CallID=%s, ScheduleID=%s", tostring(CallID), tostring(Schedule.ScheduleID))) - + + self:T( string.format( "SCHEDULEDISPATCHER stopping scheduler CallID=%s, ScheduleID=%s", tostring( CallID ), tostring( Schedule.ScheduleID ) ) ) + -- Remove schedule function https://wiki.hoggitworld.com/view/DCS_func_removeFunction - timer.removeFunction(Schedule.ScheduleID) - + timer.removeFunction( Schedule.ScheduleID ) + Schedule.ScheduleID = nil - + else - self:T(string.format("Error no ScheduleID for CallID=%s", tostring(CallID))) + self:T( string.format( "Error no ScheduleID for CallID=%s", tostring( CallID ) ) ) end - + else - + for CallID, Schedule in pairs( self.Schedule[Scheduler] or {} ) do self:Stop( Scheduler, CallID ) -- Recursive end - + end end @@ -359,7 +356,7 @@ function SCHEDULEDISPATCHER:Clear( Scheduler ) end end ---- Shopw tracing info. +--- Show tracing info. -- @param #SCHEDULEDISPATCHER self -- @param Core.Scheduler#SCHEDULER Scheduler Scheduler object. function SCHEDULEDISPATCHER:ShowTrace( Scheduler ) diff --git a/Moose Development/Moose/Core/Scheduler.lua b/Moose Development/Moose/Core/Scheduler.lua index 781b90ebe..d0c200a1e 100644 --- a/Moose Development/Moose/Core/Scheduler.lua +++ b/Moose Development/Moose/Core/Scheduler.lua @@ -1,41 +1,41 @@ --- **Core** - Prepares and handles the execution of functions over scheduled time (intervals). -- -- === --- +-- -- ## Features: --- +-- -- * Schedule functions over time, --- * optionally in an optional specified time interval, --- * optionally **repeating** with a specified time repeat interval, --- * optionally **randomizing** with a specified time interval randomization factor, --- * optionally **stop** the repeating after a specified time interval. +-- * optionally in an optional specified time interval, +-- * optionally **repeating** with a specified time repeat interval, +-- * optionally **randomizing** with a specified time interval randomization factor, +-- * optionally **stop** the repeating after a specified time interval. -- -- === --- +-- -- # Demo Missions --- +-- -- ### [SCHEDULER Demo Missions source code](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/SCH%20-%20Scheduler) --- +-- -- ### [SCHEDULER Demo Missions, only for beta testers](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SCH%20-%20Scheduler) -- -- ### [ALL Demo Missions pack of the last release](https://github.com/FlightControl-Master/MOOSE_MISSIONS/releases) --- --- === --- --- # YouTube Channel --- --- ### [SCHEDULER YouTube Channel (none)]() --- +-- -- === -- --- ### Contributions: --- +-- # YouTube Channel +-- +-- ### [SCHEDULER YouTube Channel (none)]() +-- +-- === +-- +-- ### Contributions: +-- -- * FlightControl : Concept & Testing --- --- ### Authors: --- +-- +-- ### Authors: +-- -- * FlightControl : Design & Programming --- +-- -- === -- -- @module Core.Scheduler @@ -48,62 +48,61 @@ -- @field #boolean ShowTrace Trace info if true. -- @extends Core.Base#BASE - --- Creates and handles schedules over time, which allow to execute code at specific time intervals with randomization. --- +-- -- A SCHEDULER can manage **multiple** (repeating) schedules. Each planned or executing schedule has a unique **ScheduleID**. -- The ScheduleID is returned when the method @{#SCHEDULER.Schedule}() is called. -- It is recommended to store the ScheduleID in a variable, as it is used in the methods @{SCHEDULER.Start}() and @{SCHEDULER.Stop}(), -- which can start and stop specific repeating schedules respectively within a SCHEDULER object. -- -- ## SCHEDULER constructor --- +-- -- The SCHEDULER class is quite easy to use, but note that the New constructor has variable parameters: --- +-- -- The @{#SCHEDULER.New}() method returns 2 variables: --- +-- -- 1. The SCHEDULER object reference. -- 2. The first schedule planned in the SCHEDULER object. --- +-- -- To clarify the different appliances, lets have a look at the following examples: --- +-- -- ### Construct a SCHEDULER object without a persistent schedule. --- +-- -- * @{#SCHEDULER.New}( nil ): Setup a new SCHEDULER object, which is persistently executed after garbage collection. --- +-- -- MasterObject = SCHEDULER:New() -- SchedulerID = MasterObject:Schedule( nil, ScheduleFunction, {} ) --- +-- -- The above example creates a new MasterObject, but does not schedule anything. -- A separate schedule is created by using the MasterObject using the method :Schedule..., which returns a ScheduleID --- +-- -- ### Construct a SCHEDULER object without a volatile schedule, but volatile to the Object existence... --- --- * @{#SCHEDULER.New}( Object ): Setup a new SCHEDULER object, which is linked to the Object. When the Object is nillified or destroyed, the SCHEDULER object will also be destroyed and stopped after garbage collection. --- +-- +-- * @{#SCHEDULER.New}( Object ): Setup a new SCHEDULER object, which is linked to the Object. When the Object is set to nil or destroyed, the SCHEDULER object will also be destroyed and stopped after garbage collection. +-- -- ZoneObject = ZONE:New( "ZoneName" ) -- MasterObject = SCHEDULER:New( ZoneObject ) -- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {} ) -- ... -- ZoneObject = nil -- garbagecollect() --- +-- -- The above example creates a new MasterObject, but does not schedule anything, and is bound to the existence of ZoneObject, which is a ZONE. -- A separate schedule is created by using the MasterObject using the method :Schedule()..., which returns a ScheduleID -- Later in the logic, the ZoneObject is put to nil, and garbage is collected. -- As a result, the MasterObject will cancel any planned schedule. --- +-- -- ### Construct a SCHEDULER object with a persistent schedule. --- +-- -- * @{#SCHEDULER.New}( nil, Function, FunctionArguments, Start, ... ): Setup a new persistent SCHEDULER object, and start a new schedule for the Function with the defined FunctionArguments according the Start and sequent parameters. --- +-- -- MasterObject, SchedulerID = SCHEDULER:New( nil, ScheduleFunction, {} ) --- +-- -- The above example creates a new MasterObject, and does schedule the first schedule as part of the call. -- Note that 2 variables are returned here: MasterObject, ScheduleID... --- +-- -- ### Construct a SCHEDULER object without a schedule, but volatile to the Object existence... --- +-- -- * @{#SCHEDULER.New}( Object, Function, FunctionArguments, Start, ... ): Setup a new SCHEDULER object, linked to Object, and start a new schedule for the Function with the defined FunctionArguments according the Start and sequent parameters. -- -- ZoneObject = ZONE:New( "ZoneName" ) @@ -112,13 +111,13 @@ -- ... -- ZoneObject = nil -- garbagecollect() --- +-- -- The above example creates a new MasterObject, and schedules a method call (ScheduleFunction), -- and is bound to the existence of ZoneObject, which is a ZONE object (ZoneObject). -- Both a MasterObject and a SchedulerID variable are returned. -- Later in the logic, the ZoneObject is put to nil, and garbage is collected. -- As a result, the MasterObject will cancel the planned schedule. --- +-- -- ## SCHEDULER timer stopping and (re-)starting. -- -- The SCHEDULER can be stopped and restarted with the following methods: @@ -133,70 +132,70 @@ -- MasterObject:Stop( SchedulerID ) -- ... -- MasterObject:Start( SchedulerID ) --- +-- -- The above example creates a new MasterObject, and does schedule the first schedule as part of the call. --- Note that 2 variables are returned here: MasterObject, ScheduleID... --- Later in the logic, the repeating schedule with SchedulerID is stopped. --- A bit later, the repeating schedule with SchedulerId is (re)-started. --- +-- Note that 2 variables are returned here: MasterObject, ScheduleID... +-- Later in the logic, the repeating schedule with SchedulerID is stopped. +-- A bit later, the repeating schedule with SchedulerId is (re)-started. +-- -- ## Create a new schedule --- --- With the method @{#SCHEDULER.Schedule}() a new time event can be scheduled. +-- +-- With the method @{#SCHEDULER.Schedule}() a new time event can be scheduled. -- This method is used by the :New() constructor when a new schedule is planned. --- +-- -- Consider the following code fragment of the SCHEDULER object creation. --- +-- -- ZoneObject = ZONE:New( "ZoneName" ) -- MasterObject = SCHEDULER:New( ZoneObject ) --- --- Several parameters can be specified that influence the behaviour of a Schedule. --- +-- +-- Several parameters can be specified that influence the behavior of a Schedule. +-- -- ### A single schedule, immediately executed --- +-- -- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {} ) --- --- The above example schedules a new ScheduleFunction call to be executed asynchronously, within milleseconds ... --- +-- +-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within milliseconds ... +-- -- ### A single schedule, planned over time --- +-- -- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {}, 10 ) --- +-- -- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds ... --- +-- -- ### A schedule with a repeating time interval, planned over time --- +-- -- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {}, 10, 60 ) --- --- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds, +-- +-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds, -- and repeating 60 every seconds ... --- +-- -- ### A schedule with a repeating time interval, planned over time, with time interval randomization --- +-- -- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {}, 10, 60, 0.5 ) --- --- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds, +-- +-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds, -- and repeating 60 seconds, with a 50% time interval randomization ... --- So the repeating time interval will be randomized using the **0.5**, --- and will calculate between **60 - ( 60 * 0.5 )** and **60 + ( 60 * 0.5 )** for each repeat, +-- So the repeating time interval will be randomized using the **0.5**, +-- and will calculate between **60 - ( 60 * 0.5 )** and **60 + ( 60 * 0.5 )** for each repeat, -- which is in this example between **30** and **90** seconds. --- +-- -- ### A schedule with a repeating time interval, planned over time, with time interval randomization, and stop after a time interval --- +-- -- SchedulerID = MasterObject:Schedule( ZoneObject, ScheduleFunction, {}, 10, 60, 0.5, 300 ) --- --- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds, +-- +-- The above example schedules a new ScheduleFunction call to be executed asynchronously, within 10 seconds, -- The schedule will repeat every 60 seconds. --- So the repeating time interval will be randomized using the **0.5**, --- and will calculate between **60 - ( 60 * 0.5 )** and **60 + ( 60 * 0.5 )** for each repeat, +-- So the repeating time interval will be randomized using the **0.5**, +-- and will calculate between **60 - ( 60 * 0.5 )** and **60 + ( 60 * 0.5 )** for each repeat, -- which is in this example between **30** and **90** seconds. -- The schedule will stop after **300** seconds. --- +-- -- @field #SCHEDULER SCHEDULER = { - ClassName = "SCHEDULER", - Schedules = {}, - MasterObject = nil, - ShowTrace = nil, + ClassName = "SCHEDULER", + Schedules = {}, + MasterObject = nil, + ShowTrace = nil, } --- SCHEDULER constructor. @@ -211,15 +210,15 @@ SCHEDULER = { -- @return #SCHEDULER self. -- @return #table The ScheduleID of the planned schedule. function SCHEDULER:New( MasterObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop ) - + local self = BASE:Inherit( self, BASE:New() ) -- #SCHEDULER self:F2( { Start, Repeat, RandomizeFactor, Stop } ) local ScheduleID = nil - + self.MasterObject = MasterObject self.ShowTrace = false - + if SchedulerFunction then ScheduleID = self:Schedule( MasterObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop, 3 ) end @@ -235,7 +234,7 @@ end -- @param #number Start Specifies the amount of seconds that will be waited before the scheduling is started, and the event function is called. -- @param #number Repeat Specifies the time interval in seconds when the scheduler will call the event function. -- @param #number RandomizeFactor Specifies a randomization factor between 0 and 1 to randomize the Repeat. --- @param #number Stop Time interval in seconds after which the scheduler will be stoppe. +-- @param #number Stop Time interval in seconds after which the scheduler will be stopped. -- @param #number TraceLevel Trace level [0,3]. Default 3. -- @param Core.Fsm#FSM Fsm Finite state model. -- @return #table The ScheduleID of the planned schedule. @@ -245,28 +244,27 @@ function SCHEDULER:Schedule( MasterObject, SchedulerFunction, SchedulerArguments -- Debug info. local ObjectName = "-" - if MasterObject and MasterObject.ClassName and MasterObject.ClassID then + if MasterObject and MasterObject.ClassName and MasterObject.ClassID then ObjectName = MasterObject.ClassName .. MasterObject.ClassID end - self:F3( { "Schedule :", ObjectName, tostring( MasterObject ), Start, Repeat, RandomizeFactor, Stop } ) - + self:F3( { "Schedule :", ObjectName, tostring( MasterObject ), Start, Repeat, RandomizeFactor, Stop } ) + -- Set master object. self.MasterObject = MasterObject - + -- Add schedule. - local ScheduleID = _SCHEDULEDISPATCHER:AddSchedule( - self, - SchedulerFunction, - SchedulerArguments, - Start, - Repeat, - RandomizeFactor, - Stop, - TraceLevel or 3, - Fsm - ) - - self.Schedules[#self.Schedules+1] = ScheduleID + local ScheduleID = _SCHEDULEDISPATCHER:AddSchedule( self, + SchedulerFunction, + SchedulerArguments, + Start, + Repeat, + RandomizeFactor, + Stop, + TraceLevel or 3, + Fsm + ) + + self.Schedules[#self.Schedules + 1] = ScheduleID return ScheduleID end @@ -276,7 +274,7 @@ end -- @param #string ScheduleID (Optional) The ScheduleID of the planned (repeating) schedule. function SCHEDULER:Start( ScheduleID ) self:F3( { ScheduleID } ) - self:T(string.format("Starting scheduler ID=%s", tostring(ScheduleID))) + self:T( string.format( "Starting scheduler ID=%s", tostring( ScheduleID ) ) ) _SCHEDULEDISPATCHER:Start( self, ScheduleID ) end @@ -285,7 +283,7 @@ end -- @param #string ScheduleID (Optional) The ScheduleID of the planned (repeating) schedule. function SCHEDULER:Stop( ScheduleID ) self:F3( { ScheduleID } ) - self:T(string.format("Stopping scheduler ID=%s", tostring(ScheduleID))) + self:T( string.format( "Stopping scheduler ID=%s", tostring( ScheduleID ) ) ) _SCHEDULEDISPATCHER:Stop( self, ScheduleID ) end @@ -294,15 +292,15 @@ end -- @param #string ScheduleID (optional) The ScheduleID of the planned (repeating) schedule. function SCHEDULER:Remove( ScheduleID ) self:F3( { ScheduleID } ) - self:T(string.format("Removing scheduler ID=%s", tostring(ScheduleID))) + self:T( string.format( "Removing scheduler ID=%s", tostring( ScheduleID ) ) ) _SCHEDULEDISPATCHER:RemoveSchedule( self, ScheduleID ) end --- Clears all pending schedules. -- @param #SCHEDULER self function SCHEDULER:Clear() - self:F3( ) - self:T(string.format("Clearing scheduler")) + self:F3() + self:T( string.format( "Clearing scheduler" ) ) _SCHEDULEDISPATCHER:Clear( self ) end diff --git a/Moose Development/Moose/Core/Settings.lua b/Moose Development/Moose/Core/Settings.lua index 74b5fa54b..ea3ecffdd 100644 --- a/Moose Development/Moose/Core/Settings.lua +++ b/Moose Development/Moose/Core/Settings.lua @@ -29,15 +29,14 @@ -- @module Core.Settings -- @image Core_Settings.JPG - --- @type SETTINGS -- @extends Core.Base#BASE ---- Takes care of various settings that influence the behaviour of certain functionalities and classes within the MOOSE framework. +--- Takes care of various settings that influence the behavior of certain functionalities and classes within the MOOSE framework. -- -- === -- --- The SETTINGS class takes care of various settings that influence the behaviour of certain functionalities and classes within the MOOSE framework. +-- The SETTINGS class takes care of various settings that influence the behavior of certain functionalities and classes within the MOOSE framework. -- SETTINGS can work on 2 levels: -- -- - **Default settings**: A running mission has **Default settings**. @@ -59,7 +58,7 @@ -- -- A menu is created automatically per Command Center that allows to modify the **Default** settings. -- So, when joining a CC unit, a menu will be available that allows to change the settings parameters **FOR ALL THE PLAYERS**! --- Note that the **Default settings** will only be used when a player has not choosen its own settings. +-- Note that the **Default settings** will only be used when a player has not chosen its own settings. -- -- ## 2.2) Player settings menu -- @@ -69,7 +68,7 @@ -- -- ## 2.3) Show or Hide the Player Setting menus -- --- Of course, it may be requried not to show any setting menus. In this case, a method is available on the **\_SETTINGS object**. +-- Of course, it may be required not to show any setting menus. In this case, a method is available on the **\_SETTINGS object**. -- Use @{#SETTINGS.SetPlayerMenuOff}() to hide the player menus, and use @{#SETTINGS.SetPlayerMenuOn}() show the player menus. -- Note that when this method is used, any player already in a slot will not have its menus visibility changed. -- The option will only have effect when a player enters a new slot or changes a slot. @@ -94,8 +93,8 @@ -- -- - A2G BR: [Bearing Range](https://en.wikipedia.org/wiki/Bearing_(navigation)). -- - A2G MGRS: The [Military Grid Reference System](https://en.wikipedia.org/wiki/Military_Grid_Reference_System). The accuracy can also be adapted. --- - A2G LL DMS: Lattitude Longitude [Degrees Minutes Seconds](https://en.wikipedia.org/wiki/Geographic_coordinate_conversion). The accuracy can also be adapted. --- - A2G LL DDM: Lattitude Longitude [Decimal Degrees Minutes](https://en.wikipedia.org/wiki/Decimal_degrees). The accuracy can also be adapted. +-- - A2G LL DMS: Latitude Longitude [Degrees Minutes Seconds](https://en.wikipedia.org/wiki/Geographic_coordinate_conversion). The accuracy can also be adapted. +-- - A2G LL DDM: Latitude Longitude [Decimal Degrees Minutes](https://en.wikipedia.org/wiki/Decimal_degrees). The accuracy can also be adapted. -- -- ### 3.1.2) A2G coordinates setting **menu** -- @@ -183,7 +182,7 @@ -- The settings can be changed by using the **Default settings menu** on the Command Center or the **Player settings menu** on the Player Slot. -- -- Each Message Type has specific timings that will be applied when the message is displayed. --- The Settings Menu will provide for each Message Type a selection of proposed durations from which can be choosen. +-- The Settings Menu will provide for each Message Type a selection of proposed durations from which can be chosen. -- So the player can choose its own amount of seconds how long a message should be displayed of a certain type. -- Note that **Update** messages can be chosen not to be displayed at all! -- @@ -196,7 +195,7 @@ -- -- ## 3.5) **Era** of the battle -- --- The threat level metric is scaled according the era of the battle. A target that is AAA, will pose a much greather threat in WWII than on modern warfare. +-- The threat level metric is scaled according the era of the battle. A target that is AAA, will pose a much greater threat in WWII than on modern warfare. -- Therefore, there are 4 era that are defined within the settings: -- -- - **WWII** era: Use for warfare with equipment during the world war II time. @@ -213,8 +212,8 @@ SETTINGS = { ClassName = "SETTINGS", ShowPlayerMenu = true, - MenuShort = false, - MenuStatic = false, + MenuShort = false, + MenuStatic = false, } SETTINGS.__Enum = {} @@ -231,7 +230,6 @@ SETTINGS.__Enum.Era = { Modern = 4, } - do -- SETTINGS --- SETTINGS constructor. @@ -268,14 +266,14 @@ do -- SETTINGS -- Short text are better suited for, e.g., VR. -- @param #SETTINGS self -- @param #boolean onoff If *true* use short menu texts. If *false* long ones (default). - function SETTINGS:SetMenutextShort(onoff) + function SETTINGS:SetMenutextShort( onoff ) _SETTINGS.MenuShort = onoff end --- Set menu to be static. -- @param #SETTINGS self -- @param #boolean onoff If *true* menu is static. If *false* menu will be updated after changes (default). - function SETTINGS:SetMenuStatic(onoff) + function SETTINGS:SetMenuStatic( onoff ) _SETTINGS.MenuStatic = onoff end @@ -289,7 +287,7 @@ do -- SETTINGS -- @param #SETTINGS self -- @return #boolean true if metric. function SETTINGS:IsMetric() - return ( self.Metric ~= nil and self.Metric == true ) or ( self.Metric == nil and _SETTINGS:IsMetric() ) + return (self.Metric ~= nil and self.Metric == true) or (self.Metric == nil and _SETTINGS:IsMetric()) end --- Sets the SETTINGS imperial. @@ -302,7 +300,7 @@ do -- SETTINGS -- @param #SETTINGS self -- @return #boolean true if imperial. function SETTINGS:IsImperial() - return ( self.Metric ~= nil and self.Metric == false ) or ( self.Metric == nil and _SETTINGS:IsMetric() ) + return (self.Metric ~= nil and self.Metric == false) or (self.Metric == nil and _SETTINGS:IsMetric()) end --- Sets the SETTINGS LL accuracy. @@ -344,13 +342,12 @@ do -- SETTINGS self.MessageTypeTimings[MessageType] = MessageTime end - --- Gets the SETTINGS Message Display Timing of a MessageType -- @param #SETTINGS self -- @param Core.Message#MESSAGE MessageType The type of the message. -- @return #number function SETTINGS:GetMessageTime( MessageType ) - return ( self.MessageTypeTimings and self.MessageTypeTimings[MessageType] ) or _SETTINGS:GetMessageTime( MessageType ) + return (self.MessageTypeTimings and self.MessageTypeTimings[MessageType]) or _SETTINGS:GetMessageTime( MessageType ) end --- Sets A2G LL DMS @@ -371,14 +368,14 @@ do -- SETTINGS -- @param #SETTINGS self -- @return #boolean true if LL DMS function SETTINGS:IsA2G_LL_DMS() - return ( self.A2GSystem and self.A2GSystem == "LL DMS" ) or ( not self.A2GSystem and _SETTINGS:IsA2G_LL_DMS() ) + return (self.A2GSystem and self.A2GSystem == "LL DMS") or (not self.A2GSystem and _SETTINGS:IsA2G_LL_DMS()) end --- Is LL DDM -- @param #SETTINGS self -- @return #boolean true if LL DDM function SETTINGS:IsA2G_LL_DDM() - return ( self.A2GSystem and self.A2GSystem == "LL DDM" ) or ( not self.A2GSystem and _SETTINGS:IsA2G_LL_DDM() ) + return (self.A2GSystem and self.A2GSystem == "LL DDM") or (not self.A2GSystem and _SETTINGS:IsA2G_LL_DDM()) end --- Sets A2G MGRS @@ -392,7 +389,7 @@ do -- SETTINGS -- @param #SETTINGS self -- @return #boolean true if MGRS function SETTINGS:IsA2G_MGRS() - return ( self.A2GSystem and self.A2GSystem == "MGRS" ) or ( not self.A2GSystem and _SETTINGS:IsA2G_MGRS() ) + return (self.A2GSystem and self.A2GSystem == "MGRS") or (not self.A2GSystem and _SETTINGS:IsA2G_MGRS()) end --- Sets A2G BRA @@ -406,7 +403,7 @@ do -- SETTINGS -- @param #SETTINGS self -- @return #boolean true if BRA function SETTINGS:IsA2G_BR() - return ( self.A2GSystem and self.A2GSystem == "BR" ) or ( not self.A2GSystem and _SETTINGS:IsA2G_BR() ) + return (self.A2GSystem and self.A2GSystem == "BR") or (not self.A2GSystem and _SETTINGS:IsA2G_BR()) end --- Sets A2A BRA @@ -420,7 +417,7 @@ do -- SETTINGS -- @param #SETTINGS self -- @return #boolean true if BRA function SETTINGS:IsA2A_BRAA() - return ( self.A2ASystem and self.A2ASystem == "BRAA" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_BRAA() ) + return (self.A2ASystem and self.A2ASystem == "BRAA") or (not self.A2ASystem and _SETTINGS:IsA2A_BRAA()) end --- Sets A2A BULLS @@ -434,7 +431,7 @@ do -- SETTINGS -- @param #SETTINGS self -- @return #boolean true if BULLS function SETTINGS:IsA2A_BULLS() - return ( self.A2ASystem and self.A2ASystem == "BULLS" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_BULLS() ) + return (self.A2ASystem and self.A2ASystem == "BULLS") or (not self.A2ASystem and _SETTINGS:IsA2A_BULLS()) end --- Sets A2A LL DMS @@ -455,14 +452,14 @@ do -- SETTINGS -- @param #SETTINGS self -- @return #boolean true if LL DMS function SETTINGS:IsA2A_LL_DMS() - return ( self.A2ASystem and self.A2ASystem == "LL DMS" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_LL_DMS() ) + return (self.A2ASystem and self.A2ASystem == "LL DMS") or (not self.A2ASystem and _SETTINGS:IsA2A_LL_DMS()) end --- Is LL DDM -- @param #SETTINGS self -- @return #boolean true if LL DDM function SETTINGS:IsA2A_LL_DDM() - return ( self.A2ASystem and self.A2ASystem == "LL DDM" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_LL_DDM() ) + return (self.A2ASystem and self.A2ASystem == "LL DDM") or (not self.A2ASystem and _SETTINGS:IsA2A_LL_DDM()) end --- Sets A2A MGRS @@ -476,7 +473,7 @@ do -- SETTINGS -- @param #SETTINGS self -- @return #boolean true if MGRS function SETTINGS:IsA2A_MGRS() - return ( self.A2ASystem and self.A2ASystem == "MGRS" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_MGRS() ) + return (self.A2ASystem and self.A2ASystem == "MGRS") or (not self.A2ASystem and _SETTINGS:IsA2A_MGRS()) end --- @param #SETTINGS self @@ -495,37 +492,37 @@ do -- SETTINGS -- A2G Coordinate System ------- - local text="A2G Coordinate System" + local text = "A2G Coordinate System" if _SETTINGS.MenuShort then - text="A2G Coordinates" + text = "A2G Coordinates" end local A2GCoordinateMenu = MENU_GROUP:New( MenuGroup, text, SettingsMenu ):SetTime( MenuTime ) -- Set LL DMS if not self:IsA2G_LL_DMS() then - local text="Lat/Lon Degree Min Sec (LL DMS)" + local text = "Lat/Lon Degree Min Sec (LL DMS)" if _SETTINGS.MenuShort then - text="LL DMS" + text = "LL DMS" end MENU_GROUP_COMMAND:New( MenuGroup, text, A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "LL DMS" ):SetTime( MenuTime ) end -- Set LL DDM if not self:IsA2G_LL_DDM() then - local text="Lat/Lon Degree Dec Min (LL DDM)" + local text = "Lat/Lon Degree Dec Min (LL DDM)" if _SETTINGS.MenuShort then - text="LL DDM" + text = "LL DDM" end MENU_GROUP_COMMAND:New( MenuGroup, "Lat/Lon Degree Dec Min (LL DDM)", A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "LL DDM" ):SetTime( MenuTime ) end -- Set LL DMS accuracy. if self:IsA2G_LL_DDM() then - local text1="LL DDM Accuracy 1" - local text2="LL DDM Accuracy 2" - local text3="LL DDM Accuracy 3" + local text1 = "LL DDM Accuracy 1" + local text2 = "LL DDM Accuracy 2" + local text3 = "LL DDM Accuracy 3" if _SETTINGS.MenuShort then - text1="LL DDM" + text1 = "LL DDM" end MENU_GROUP_COMMAND:New( MenuGroup, "LL DDM Accuracy 1", A2GCoordinateMenu, self.MenuLL_DDM_Accuracy, self, MenuGroup, RootMenu, 1 ):SetTime( MenuTime ) MENU_GROUP_COMMAND:New( MenuGroup, "LL DDM Accuracy 2", A2GCoordinateMenu, self.MenuLL_DDM_Accuracy, self, MenuGroup, RootMenu, 2 ):SetTime( MenuTime ) @@ -534,18 +531,18 @@ do -- SETTINGS -- Set BR. if not self:IsA2G_BR() then - local text="Bearing, Range (BR)" + local text = "Bearing, Range (BR)" if _SETTINGS.MenuShort then - text="BR" + text = "BR" end - MENU_GROUP_COMMAND:New( MenuGroup, text , A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "BR" ):SetTime( MenuTime ) + MENU_GROUP_COMMAND:New( MenuGroup, text, A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "BR" ):SetTime( MenuTime ) end -- Set MGRS. if not self:IsA2G_MGRS() then - local text="Military Grid (MGRS)" + local text = "Military Grid (MGRS)" if _SETTINGS.MenuShort then - text="MGRS" + text = "MGRS" end MENU_GROUP_COMMAND:New( MenuGroup, text, A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "MGRS" ):SetTime( MenuTime ) end @@ -563,24 +560,24 @@ do -- SETTINGS -- A2A Coordinate System ------- - local text="A2A Coordinate System" + local text = "A2A Coordinate System" if _SETTINGS.MenuShort then - text="A2A Coordinates" + text = "A2A Coordinates" end local A2ACoordinateMenu = MENU_GROUP:New( MenuGroup, text, SettingsMenu ):SetTime( MenuTime ) if not self:IsA2A_LL_DMS() then - local text="Lat/Lon Degree Min Sec (LL DMS)" + local text = "Lat/Lon Degree Min Sec (LL DMS)" if _SETTINGS.MenuShort then - text="LL DMS" + text = "LL DMS" end MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "LL DMS" ):SetTime( MenuTime ) end if not self:IsA2A_LL_DDM() then - local text="Lat/Lon Degree Dec Min (LL DDM)" + local text = "Lat/Lon Degree Dec Min (LL DDM)" if _SETTINGS.MenuShort then - text="LL DDM" + text = "LL DDM" end MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "LL DDM" ):SetTime( MenuTime ) end @@ -593,25 +590,25 @@ do -- SETTINGS end if not self:IsA2A_BULLS() then - local text="Bullseye (BULLS)" + local text = "Bullseye (BULLS)" if _SETTINGS.MenuShort then - text="Bulls" + text = "Bulls" end MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "BULLS" ):SetTime( MenuTime ) end if not self:IsA2A_BRAA() then - local text="Bearing Range Altitude Aspect (BRAA)" + local text = "Bearing Range Altitude Aspect (BRAA)" if _SETTINGS.MenuShort then - text="BRAA" + text = "BRAA" end MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "BRAA" ):SetTime( MenuTime ) end if not self:IsA2A_MGRS() then - local text="Military Grid (MGRS)" + local text = "Military Grid (MGRS)" if _SETTINGS.MenuShort then - text="MGRS" + text = "MGRS" end MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "MGRS" ):SetTime( MenuTime ) end @@ -624,31 +621,31 @@ do -- SETTINGS MENU_GROUP_COMMAND:New( MenuGroup, "MGRS Accuracy 5", A2ACoordinateMenu, self.MenuMGRS_Accuracy, self, MenuGroup, RootMenu, 5 ):SetTime( MenuTime ) end - local text="Measures and Weights System" + local text = "Measures and Weights System" if _SETTINGS.MenuShort then - text="Unit System" + text = "Unit System" end local MetricsMenu = MENU_GROUP:New( MenuGroup, text, SettingsMenu ):SetTime( MenuTime ) if self:IsMetric() then - local text="Imperial (Miles,Feet)" + local text = "Imperial (Miles,Feet)" if _SETTINGS.MenuShort then - text="Imperial" + text = "Imperial" end MENU_GROUP_COMMAND:New( MenuGroup, text, MetricsMenu, self.MenuMWSystem, self, MenuGroup, RootMenu, false ):SetTime( MenuTime ) end if self:IsImperial() then - local text="Metric (Kilometers,Meters)" + local text = "Metric (Kilometers,Meters)" if _SETTINGS.MenuShort then - text="Metric" + text = "Metric" end MENU_GROUP_COMMAND:New( MenuGroup, text, MetricsMenu, self.MenuMWSystem, self, MenuGroup, RootMenu, true ):SetTime( MenuTime ) end - local text="Messages and Reports" + local text = "Messages and Reports" if _SETTINGS.MenuShort then - text="Messages & Reports" + text = "Messages & Reports" end local MessagesMenu = MENU_GROUP:New( MenuGroup, text, SettingsMenu ):SetTime( MenuTime ) @@ -689,7 +686,6 @@ do -- SETTINGS MENU_GROUP_COMMAND:New( MenuGroup, "2 minutes", DetailedReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.DetailedReportsMenu, 120 ):SetTime( MenuTime ) MENU_GROUP_COMMAND:New( MenuGroup, "3 minutes", DetailedReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.DetailedReportsMenu, 180 ):SetTime( MenuTime ) - SettingsMenu:Remove( MenuTime ) return self @@ -733,11 +729,11 @@ do -- SETTINGS self.PlayerMenu = PlayerMenu - self:I(string.format("Setting menu for player %s", tostring(PlayerName))) + self:I( string.format( "Setting menu for player %s", tostring( PlayerName ) ) ) local submenu = MENU_GROUP:New( PlayerGroup, "LL Accuracy", PlayerMenu ) MENU_GROUP_COMMAND:New( PlayerGroup, "LL 0 Decimals", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 0 ) - MENU_GROUP_COMMAND:New( PlayerGroup, "LL 1 Decimal", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 1 ) + MENU_GROUP_COMMAND:New( PlayerGroup, "LL 1 Decimal", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 1 ) MENU_GROUP_COMMAND:New( PlayerGroup, "LL 2 Decimals", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 2 ) MENU_GROUP_COMMAND:New( PlayerGroup, "LL 3 Decimals", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 3 ) MENU_GROUP_COMMAND:New( PlayerGroup, "LL 4 Decimals", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 4 ) @@ -754,40 +750,40 @@ do -- SETTINGS -- A2G Coordinate System ------ - local text="A2G Coordinate System" + local text = "A2G Coordinate System" if _SETTINGS.MenuShort then - text="A2G Coordinates" + text = "A2G Coordinates" end local A2GCoordinateMenu = MENU_GROUP:New( PlayerGroup, text, PlayerMenu ) if not self:IsA2G_LL_DMS() or _SETTINGS.MenuStatic then - local text="Lat/Lon Degree Min Sec (LL DMS)" + local text = "Lat/Lon Degree Min Sec (LL DMS)" if _SETTINGS.MenuShort then - text="A2G LL DMS" + text = "A2G LL DMS" end MENU_GROUP_COMMAND:New( PlayerGroup, text, A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL DMS" ) end if not self:IsA2G_LL_DDM() or _SETTINGS.MenuStatic then - local text="Lat/Lon Degree Dec Min (LL DDM)" + local text = "Lat/Lon Degree Dec Min (LL DDM)" if _SETTINGS.MenuShort then - text="A2G LL DDM" + text = "A2G LL DDM" end MENU_GROUP_COMMAND:New( PlayerGroup, text, A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL DDM" ) end if not self:IsA2G_BR() or _SETTINGS.MenuStatic then - local text="Bearing, Range (BR)" + local text = "Bearing, Range (BR)" if _SETTINGS.MenuShort then - text="A2G BR" + text = "A2G BR" end MENU_GROUP_COMMAND:New( PlayerGroup, text, A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "BR" ) end if not self:IsA2G_MGRS() or _SETTINGS.MenuStatic then - local text="Military Grid (MGRS)" + local text = "Military Grid (MGRS)" if _SETTINGS.MenuShort then - text="A2G MGRS" + text = "A2G MGRS" end MENU_GROUP_COMMAND:New( PlayerGroup, text, A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "MGRS" ) end @@ -796,49 +792,48 @@ do -- SETTINGS -- A2A Coordinates Menu ------ - local text="A2A Coordinate System" + local text = "A2A Coordinate System" if _SETTINGS.MenuShort then - text="A2A Coordinates" + text = "A2A Coordinates" end local A2ACoordinateMenu = MENU_GROUP:New( PlayerGroup, text, PlayerMenu ) - if not self:IsA2A_LL_DMS() or _SETTINGS.MenuStatic then - local text="Lat/Lon Degree Min Sec (LL DMS)" + local text = "Lat/Lon Degree Min Sec (LL DMS)" if _SETTINGS.MenuShort then - text="A2A LL DMS" + text = "A2A LL DMS" end MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL DMS" ) end if not self:IsA2A_LL_DDM() or _SETTINGS.MenuStatic then - local text="Lat/Lon Degree Dec Min (LL DDM)" + local text = "Lat/Lon Degree Dec Min (LL DDM)" if _SETTINGS.MenuShort then - text="A2A LL DDM" + text = "A2A LL DDM" end MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL DDM" ) end if not self:IsA2A_BULLS() or _SETTINGS.MenuStatic then - local text="Bullseye (BULLS)" + local text = "Bullseye (BULLS)" if _SETTINGS.MenuShort then - text="A2A BULLS" + text = "A2A BULLS" end MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "BULLS" ) end if not self:IsA2A_BRAA() or _SETTINGS.MenuStatic then - local text="Bearing Range Altitude Aspect (BRAA)" + local text = "Bearing Range Altitude Aspect (BRAA)" if _SETTINGS.MenuShort then - text="A2A BRAA" + text = "A2A BRAA" end MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "BRAA" ) end if not self:IsA2A_MGRS() or _SETTINGS.MenuStatic then - local text="Military Grid (MGRS)" + local text = "Military Grid (MGRS)" if _SETTINGS.MenuShort then - text="A2A MGRS" + text = "A2A MGRS" end MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "MGRS" ) end @@ -847,24 +842,24 @@ do -- SETTINGS -- Unit system --- - local text="Measures and Weights System" + local text = "Measures and Weights System" if _SETTINGS.MenuShort then - text="Unit System" + text = "Unit System" end local MetricsMenu = MENU_GROUP:New( PlayerGroup, text, PlayerMenu ) if self:IsMetric() or _SETTINGS.MenuStatic then - local text="Imperial (Miles,Feet)" + local text = "Imperial (Miles,Feet)" if _SETTINGS.MenuShort then - text="Imperial" + text = "Imperial" end MENU_GROUP_COMMAND:New( PlayerGroup, text, MetricsMenu, self.MenuGroupMWSystem, self, PlayerUnit, PlayerGroup, PlayerName, false ) end if self:IsImperial() or _SETTINGS.MenuStatic then - local text="Metric (Kilometers,Meters)" + local text = "Metric (Kilometers,Meters)" if _SETTINGS.MenuShort then - text="Metric" + text = "Metric" end MENU_GROUP_COMMAND:New( PlayerGroup, text, MetricsMenu, self.MenuGroupMWSystem, self, PlayerUnit, PlayerGroup, PlayerName, true ) end @@ -873,9 +868,9 @@ do -- SETTINGS -- Messages and Reports --- - local text="Messages and Reports" + local text = "Messages and Reports" if _SETTINGS.MenuShort then - text="Messages & Reports" + text = "Messages & Reports" end local MessagesMenu = MENU_GROUP:New( PlayerGroup, text, PlayerMenu ) @@ -935,39 +930,38 @@ do -- SETTINGS return self end - --- @param #SETTINGS self function SETTINGS:A2GMenuSystem( MenuGroup, RootMenu, A2GSystem ) self.A2GSystem = A2GSystem - MESSAGE:New( string.format("Settings: Default A2G coordinate system set to %s for all players!", A2GSystem ), 5 ):ToAll() + MESSAGE:New( string.format( "Settings: Default A2G coordinate system set to %s for all players!", A2GSystem ), 5 ):ToAll() self:SetSystemMenu( MenuGroup, RootMenu ) end --- @param #SETTINGS self function SETTINGS:A2AMenuSystem( MenuGroup, RootMenu, A2ASystem ) self.A2ASystem = A2ASystem - MESSAGE:New( string.format("Settings: Default A2A coordinate system set to %s for all players!", A2ASystem ), 5 ):ToAll() + MESSAGE:New( string.format( "Settings: Default A2A coordinate system set to %s for all players!", A2ASystem ), 5 ):ToAll() self:SetSystemMenu( MenuGroup, RootMenu ) end --- @param #SETTINGS self function SETTINGS:MenuLL_DDM_Accuracy( MenuGroup, RootMenu, LL_Accuracy ) self.LL_Accuracy = LL_Accuracy - MESSAGE:New( string.format("Settings: Default LL accuracy set to %s for all players!", LL_Accuracy ), 5 ):ToAll() + MESSAGE:New( string.format( "Settings: Default LL accuracy set to %s for all players!", LL_Accuracy ), 5 ):ToAll() self:SetSystemMenu( MenuGroup, RootMenu ) end --- @param #SETTINGS self function SETTINGS:MenuMGRS_Accuracy( MenuGroup, RootMenu, MGRS_Accuracy ) self.MGRS_Accuracy = MGRS_Accuracy - MESSAGE:New( string.format("Settings: Default MGRS accuracy set to %s for all players!", MGRS_Accuracy ), 5 ):ToAll() + MESSAGE:New( string.format( "Settings: Default MGRS accuracy set to %s for all players!", MGRS_Accuracy ), 5 ):ToAll() self:SetSystemMenu( MenuGroup, RootMenu ) end --- @param #SETTINGS self function SETTINGS:MenuMWSystem( MenuGroup, RootMenu, MW ) self.Metric = MW - MESSAGE:New( string.format("Settings: Default measurement format set to %s for all players!", MW and "Metric" or "Imperial" ), 5 ):ToAll() + MESSAGE:New( string.format( "Settings: Default measurement format set to %s for all players!", MW and "Metric" or "Imperial" ), 5 ):ToAll() self:SetSystemMenu( MenuGroup, RootMenu ) end @@ -980,12 +974,12 @@ do -- SETTINGS do --- @param #SETTINGS self function SETTINGS:MenuGroupA2GSystem( PlayerUnit, PlayerGroup, PlayerName, A2GSystem ) - BASE:E( {self, PlayerUnit:GetName(), A2GSystem} ) + BASE:E( { self, PlayerUnit:GetName(), A2GSystem } ) self.A2GSystem = A2GSystem MESSAGE:New( string.format( "Settings: A2G format set to %s for player %s.", A2GSystem, PlayerName ), 5 ):ToGroup( PlayerGroup ) - if _SETTINGS.MenuStatic==false then - self:RemovePlayerMenu(PlayerUnit) - self:SetPlayerMenu(PlayerUnit) + if _SETTINGS.MenuStatic == false then + self:RemovePlayerMenu( PlayerUnit ) + self:SetPlayerMenu( PlayerUnit ) end end @@ -993,9 +987,9 @@ do -- SETTINGS function SETTINGS:MenuGroupA2ASystem( PlayerUnit, PlayerGroup, PlayerName, A2ASystem ) self.A2ASystem = A2ASystem MESSAGE:New( string.format( "Settings: A2A format set to %s for player %s.", A2ASystem, PlayerName ), 5 ):ToGroup( PlayerGroup ) - if _SETTINGS.MenuStatic==false then - self:RemovePlayerMenu(PlayerUnit) - self:SetPlayerMenu(PlayerUnit) + if _SETTINGS.MenuStatic == false then + self:RemovePlayerMenu( PlayerUnit ) + self:SetPlayerMenu( PlayerUnit ) end end @@ -1003,9 +997,9 @@ do -- SETTINGS function SETTINGS:MenuGroupLL_DDM_AccuracySystem( PlayerUnit, PlayerGroup, PlayerName, LL_Accuracy ) self.LL_Accuracy = LL_Accuracy MESSAGE:New( string.format( "Settings: LL format accuracy set to %d decimal places for player %s.", LL_Accuracy, PlayerName ), 5 ):ToGroup( PlayerGroup ) - if _SETTINGS.MenuStatic==false then - self:RemovePlayerMenu(PlayerUnit) - self:SetPlayerMenu(PlayerUnit) + if _SETTINGS.MenuStatic == false then + self:RemovePlayerMenu( PlayerUnit ) + self:SetPlayerMenu( PlayerUnit ) end end @@ -1013,9 +1007,9 @@ do -- SETTINGS function SETTINGS:MenuGroupMGRS_AccuracySystem( PlayerUnit, PlayerGroup, PlayerName, MGRS_Accuracy ) self.MGRS_Accuracy = MGRS_Accuracy MESSAGE:New( string.format( "Settings: MGRS format accuracy set to %d for player %s.", MGRS_Accuracy, PlayerName ), 5 ):ToGroup( PlayerGroup ) - if _SETTINGS.MenuStatic==false then - self:RemovePlayerMenu(PlayerUnit) - self:SetPlayerMenu(PlayerUnit) + if _SETTINGS.MenuStatic == false then + self:RemovePlayerMenu( PlayerUnit ) + self:SetPlayerMenu( PlayerUnit ) end end @@ -1023,9 +1017,9 @@ do -- SETTINGS function SETTINGS:MenuGroupMWSystem( PlayerUnit, PlayerGroup, PlayerName, MW ) self.Metric = MW MESSAGE:New( string.format( "Settings: Measurement format set to %s for player %s.", MW and "Metric" or "Imperial", PlayerName ), 5 ):ToGroup( PlayerGroup ) - if _SETTINGS.MenuStatic==false then - self:RemovePlayerMenu(PlayerUnit) - self:SetPlayerMenu(PlayerUnit) + if _SETTINGS.MenuStatic == false then + self:RemovePlayerMenu( PlayerUnit ) + self:SetPlayerMenu( PlayerUnit ) end end @@ -1055,7 +1049,6 @@ do -- SETTINGS end - --- Configures the era of the mission to be Cold war. -- @param #SETTINGS self -- @return #SETTINGS self @@ -1065,7 +1058,6 @@ do -- SETTINGS end - --- Configures the era of the mission to be Modern war. -- @param #SETTINGS self -- @return #SETTINGS self @@ -1075,7 +1067,4 @@ do -- SETTINGS end - - - end diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index d25186b66..a067cc680 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -1,16 +1,16 @@ --- **Core** - Spawn dynamically new groups of units in running missions. --- +-- -- === --- +-- -- ## Features: --- +-- -- * Spawn new groups in running missions. -- * Schedule spawning of new groups. -- * Put limits on the amount of groups that can be spawned, and the amount of units that can be alive at the same time. -- * Randomize the spawning location between different zones. -- * Randomize the initial positions within the zones. -- * Spawn in array formation. --- * Spawn uncontrolled (for planes or helos only). +-- * Spawn uncontrolled (for planes or helicopters only). -- * Clean up inactive helicopters that "crashed". -- * Place a hook to capture a spawn event, and tailor with customer code. -- * Spawn late activated. @@ -27,26 +27,25 @@ -- * Spawn and keep the unit names. -- * Spawn with a different coalition and country. -- * Enquiry methods to check on spawn status. --- +-- -- === --- +-- -- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/SPA%20-%20Spawning) --- +-- -- === --- +-- -- ### [YouTube Playlist](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl1jirWIo4t4YxqN-HxjqRkL) --- +-- -- === --- +-- -- ### Author: **FlightControl** -- ### Contributions: A lot of people within this community! --- +-- -- === --- +-- -- @module Core.Spawn -- @image Core_Spawn.JPG - --- SPAWN Class -- @type SPAWN -- @field ClassName @@ -59,81 +58,80 @@ -- @field #SPAWN.SpawnZoneTable SpawnZoneTable -- @extends Core.Base#BASE - ---- Allows to spawn dynamically new @{Core.Group}s. --- +--- Allows to spawn dynamically new @{Core.Group}s. +-- -- Each SPAWN object needs to be have related **template groups** setup in the Mission Editor (ME), --- which is a normal group with the **Late Activation** flag set. --- This template group will never be activated in your mission. --- SPAWN uses that **template group** to reference to all the characteristics --- (air, ground, livery, unit composition, formation, skill level etc) of each new group to be spawned. --- +-- which is a normal group with the **Late Activation** flag set. +-- This template group will never be activated in your mission. +-- SPAWN uses that **template group** to reference to all the characteristics +-- (air, ground, livery, unit composition, formation, skill level etc) of each new group to be spawned. +-- -- Therefore, when creating a SPAWN object, the @{#SPAWN.New} and @{#SPAWN.NewWithAlias} require --- **the name of the template group** to be given as a string to those constructor methods. --- --- Initialization settings can be applied on the SPAWN object, --- which modify the behaviour or the way groups are spawned. +-- **the name of the template group** to be given as a string to those constructor methods. +-- +-- Initialization settings can be applied on the SPAWN object, +-- which modify the behavior or the way groups are spawned. -- These initialization methods have the prefix **Init**. -- There are also spawn methods with the prefix **Spawn** and will spawn new groups in various ways. --- --- ### IMPORTANT! The methods with prefix **Init** must be used before any methods with prefix **Spawn** method are used, or unexpected results may appear!!! --- --- Because SPAWN can spawn multiple groups of a template group, --- SPAWN has an **internal index** that keeps track --- which was the latest group that was spawned. --- --- **Limits** can be set on how many groups can be spawn in each SPAWN object, +-- +-- ### IMPORTANT! The methods with prefix **Init** must be used before any methods with prefix **Spawn** method are used, or unexpected results may appear!!! +-- +-- Because SPAWN can spawn multiple groups of a template group, +-- SPAWN has an **internal index** that keeps track +-- which was the latest group that was spawned. +-- +-- **Limits** can be set on how many groups can be spawn in each SPAWN object, -- using the method @{#SPAWN.InitLimit}. SPAWN has 2 kind of limits: --- --- * The maximum amount of @{Wrapper.Unit}s that can be **alive** at the same time... +-- +-- * The maximum amount of @{Wrapper.Unit}s that can be **alive** at the same time... -- * The maximum amount of @{Wrapper.Group}s that can be **spawned**... This is more of a **resource**-type of limit. --- --- When new groups get spawned using the **Spawn** methods, +-- +-- When new groups get spawned using the **Spawn** methods, -- it will be evaluated whether any limits have been reached. --- When no spawn limit is reached, a new group will be created by the spawning methods, --- and the internal index will be increased with 1. --- --- These limits ensure that your mission does not accidentally get flooded with spawned groups. --- Additionally, it also guarantees that independent of the group composition, +-- When no spawn limit is reached, a new group will be created by the spawning methods, +-- and the internal index will be increased with 1. +-- +-- These limits ensure that your mission does not accidentally get flooded with spawned groups. +-- Additionally, it also guarantees that independent of the group composition, -- at any time, the most optimal amount of groups are alive in your mission. -- For example, if your template group has a group composition of 10 units, and you specify a limit of 100 units alive at the same time, -- with unlimited resources = :InitLimit( 100, 0 ) and 10 groups are alive, but two groups have only one unit alive in the group, -- then a sequent Spawn(Scheduled) will allow a new group to be spawned!!! --- --- ### IMPORTANT!! If a limit has been reached, it is possible that a **Spawn** method returns **nil**, meaning, no @{Wrapper.Group} had been spawned!!! --- --- Spawned groups get **the same name** as the name of the template group. --- Spawned units in those groups keep _by default_ **the same name** as the name of the template group. --- However, because multiple groups and units are created from the template group, +-- +-- ### IMPORTANT!! If a limit has been reached, it is possible that a **Spawn** method returns **nil**, meaning, no @{Wrapper.Group} had been spawned!!! +-- +-- Spawned groups get **the same name** as the name of the template group. +-- Spawned units in those groups keep _by default_ **the same name** as the name of the template group. +-- However, because multiple groups and units are created from the template group, -- a suffix is added to each spawned group and unit. --- +-- -- Newly spawned groups will get the following naming structure at run-time: --- --- 1. Spawned groups will have the name _GroupName_#_nnn_, where _GroupName_ is the name of the **template group**, +-- +-- 1. Spawned groups will have the name _GroupName_#_nnn_, where _GroupName_ is the name of the **template group**, -- and _nnn_ is a **counter from 0 to 999**. --- 2. Spawned units will have the name _GroupName_#_nnn_-_uu_, +-- 2. Spawned units will have the name _GroupName_#_nnn_-_uu_, -- where _uu_ is a **counter from 0 to 99** for each new spawned unit belonging to the group. --- --- That being said, there is a way to keep the same unit names! +-- +-- That being said, there is a way to keep the same unit names! -- The method @{#SPAWN.InitKeepUnitNames}() will keep the same unit names as defined within the template group, thus: --- --- 3. Spawned units will have the name _UnitName_#_nnn_-_uu_, --- where _UnitName_ is the **unit name as defined in the template group*, +-- +-- 3. Spawned units will have the name _UnitName_#_nnn_-_uu_, +-- where _UnitName_ is the **unit name as defined in the template group*, -- and _uu_ is a **counter from 0 to 99** for each new spawned unit belonging to the group. --- +-- -- Some **additional notes that need to be considered!!**: --- --- * templates are actually groups defined within the mission editor, with the flag "Late Activation" set. +-- +-- * 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), +-- * 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. --- +-- -- ## 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). -- * @{#SPAWN.NewWithAlias}(): Creates a new SPAWN object taking the name of the group that represents the GROUP template (definition), and gives each spawned @{Wrapper.Group} an different name. -- @@ -142,63 +140,62 @@ -- 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. -- -- ## SPAWN **Init**ialization methods --- --- A spawn object will behave differently based on the usage of **initialization** methods, which all start with the **Init** prefix: --- +-- +-- A spawn object will behave differently based on the usage of **initialization** methods, which all start with the **Init** prefix: +-- -- ### Unit Names --- +-- -- * @{#SPAWN.InitKeepUnitNames}(): Keeps the unit names as defined within the mission editor, but note that anything after a # mark is ignored, and any spaces before and after the resulting name are removed. IMPORTANT! This method MUST be the first used after :New !!! --- +-- -- ### Route randomization --- +-- -- * @{#SPAWN.InitRandomizeRoute}(): Randomize the routes of spawned groups, and for air groups also optionally the height. --- --- ### Group composition randomization --- +-- +-- ### Group composition randomization +-- -- * @{#SPAWN.InitRandomizeTemplate}(): Randomize the group templates so that when a new group is spawned, a random group template is selected from one of the templates defined. --- +-- -- ### Uncontrolled --- +-- -- * @{#SPAWN.InitUnControlled}(): Spawn plane groups uncontrolled. --- +-- -- ### Array formation --- --- * @{#SPAWN.InitArray}(): Make groups visible before they are actually activated, and order these groups like a batallion in an array. --- +-- +-- * @{#SPAWN.InitArray}(): Make groups visible before they are actually activated, and order these groups like a battalion in an array. +-- -- ### Position randomization --- +-- -- * @{#SPAWN.InitRandomizePosition}(): Randomizes the position of @{Wrapper.Group}s that are spawned within a **radius band**, given an Outer and Inner radius, from the point that the spawn happens. -- * @{#SPAWN.InitRandomizeUnits}(): Randomizes the @{Wrapper.Unit}s in the @{Wrapper.Group} that is spawned within a **radius band**, given an Outer and Inner radius. -- * @{#SPAWN.InitRandomizeZones}(): Randomizes the spawning between a predefined list of @{Zone}s that are declared using this function. Each zone can be given a probability factor. --- +-- -- ### Enable / Disable AI when spawning a new @{Wrapper.Group} --- +-- -- * @{#SPAWN.InitAIOn}(): Turns the AI On when spawning the new @{Wrapper.Group} object. -- * @{#SPAWN.InitAIOff}(): Turns the AI Off when spawning the new @{Wrapper.Group} object. -- * @{#SPAWN.InitAIOnOff}(): Turns the AI On or Off when spawning the new @{Wrapper.Group} object. --- --- ### Limit scheduled spawning --- +-- +-- ### Limit scheduled spawning +-- -- * @{#SPAWN.InitLimit}(): Limits the amount of groups that can be alive at the same time and that can be dynamically spawned. --- +-- -- ### Delay initial scheduled spawn --- --- * @{#SPAWN.InitDelayOnOff}(): Turns the inital delay On/Off when scheduled spawning the first @{Wrapper.Group} object. --- * @{#SPAWN.InitDelayOn}(): Turns the inital delay On when scheduled spawning the first @{Wrapper.Group} object. --- * @{#SPAWN.InitDelayOff}(): Turns the inital delay Off when scheduled spawning the first @{Wrapper.Group} object. --- +-- +-- * @{#SPAWN.InitDelayOnOff}(): Turns the initial delay On/Off when scheduled spawning the first @{Wrapper.Group} object. +-- * @{#SPAWN.InitDelayOn}(): Turns the initial delay On when scheduled spawning the first @{Wrapper.Group} object. +-- * @{#SPAWN.InitDelayOff}(): Turns the initial delay Off when scheduled spawning the first @{Wrapper.Group} object. +-- -- ### Repeat spawned @{Wrapper.Group}s upon landing --- +-- -- * @{#SPAWN.InitRepeat}() or @{#SPAWN.InitRepeatOnLanding}(): This method is used to re-spawn automatically the same group after it has landed. -- * @{#SPAWN.InitRepeatOnEngineShutDown}(): This method is used to re-spawn automatically the same group after it has landed and it shuts down the engines at the ramp. --- --- +-- -- ## SPAWN **Spawn** methods --- +-- -- Groups can be spawned at different times and methods: --- +-- -- ### **Single** spawning 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.SpawnFromVec3}(): Spawn a new group from a Vec3 coordinate. (The group will can be spawned at a point in the air). @@ -207,70 +204,66 @@ -- * @{#SPAWN.SpawnFromUnit}(): Spawn a new group taking the position of a @{Wrapper.Unit}. -- * @{#SPAWN.SpawnInZone}(): Spawn a new group in a @{Zone}. -- * @{#SPAWN.SpawnAtAirbase}(): Spawn a new group at an @{Wrapper.Airbase}, which can be an airdrome, ship or helipad. --- --- Note that @{#SPAWN.Spawn} and @{#SPAWN.ReSpawn} return a @{Wrapper.Group#GROUP.New} object, that contains a reference to the DCSGroup object. +-- +-- Note that @{#SPAWN.Spawn} and @{#SPAWN.ReSpawn} return a @{Wrapper.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. --- +-- -- ### **Scheduled** spawning methods --- --- * @{#SPAWN.SpawnScheduled}(): Spawn groups at scheduled but randomized intervals. ---- * @{#SPAWN.SpawnScheduleStart}(): Start or continue to spawn groups at scheduled time intervals. --- * @{#SPAWN.SpawnScheduleStop}(): Stop the spawning of groups at scheduled time intervals. --- --- +-- +-- * @{#SPAWN.SpawnScheduled}(): Spawn groups at scheduled but randomized intervals. +--- * @{#SPAWN.SpawnScheduleStart}(): Start or continue to spawn groups at scheduled time intervals. +-- * @{#SPAWN.SpawnScheduleStop}(): Stop the spawning of groups at scheduled time intervals. +-- -- ## 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... --- +-- -- ## Spawned cleaning of inactive groups --- --- 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, +-- +-- Sometimes, it will occur during a mission run-time, that ground or especially air objects get damaged, and will while being damaged 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.InitCleanUp}() initialization method has been defined that will silently monitor the status of each spawned group. --- Once a group has a velocity = 0, and has been waiting for a defined interval, that group will be cleaned or removed from run-time. --- There is a catch however :-) If a damaged group has returned to an airbase within the coalition, that group will not be considered as "lost"... --- In such a case, when the inactive group is cleaned, a new group will Re-spawned automatically. --- This models AI that has succesfully returned to their airbase, to restart their combat activities. +-- 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 successfully returned to their airbase, to restart their combat activities. -- Check the @{#SPAWN.InitCleanUp}() for further info. --- +-- -- ## Catch the @{Wrapper.Group} Spawn Event in a callback function! --- +-- -- When using the @{#SPAWN.SpawnScheduled)() method, new @{Wrapper.Group}s are created following the spawn time interval parameters. -- When a new @{Wrapper.Group} is spawned, you maybe want to execute actions with that group spawned at the spawn event. --- The SPAWN class supports this functionality through the method @{#SPAWN.OnSpawnGroup}( **function( SpawnedGroup ) end ** ), --- which takes a function as a parameter that you can define locally. +-- The SPAWN class supports this functionality through the method @{#SPAWN.OnSpawnGroup}( **function( SpawnedGroup ) end ** ), +-- which takes a function as a parameter that you can define locally. -- Whenever a new @{Wrapper.Group} is spawned, the given function is called, and the @{Wrapper.Group} that was just spawned, is given as a parameter. --- As a result, your spawn event handling function requires one parameter to be declared, which will contain the spawned @{Wrapper.Group} object. +-- As a result, your spawn event handling function requires one parameter to be declared, which will contain the spawned @{Wrapper.Group} object. -- A coding example is provided at the description of the @{#SPAWN.OnSpawnGroup}( **function( SpawnedGroup ) end ** ) method. --- +-- -- ## Delay the initial spawning --- --- When using the @{#SPAWN.SpawnScheduled)() method, the default behaviour of this method will be that it will spawn the initial (first) @{Wrapper.Group} +-- +-- When using the @{#SPAWN.SpawnScheduled)() method, the default behavior of this method will be that it will spawn the initial (first) @{Wrapper.Group} -- immediately when :SpawnScheduled() is initiated. The methods @{#SPAWN.InitDelayOnOff}() and @{#SPAWN.InitDelayOn}() can be used to -- activate a delay before the first @{Wrapper.Group} is spawned. For completeness, a method @{#SPAWN.InitDelayOff}() is also available, that --- can be used to switch off the initial delay. Because there is no delay by default, this method would only be used when a +-- can be used to switch off the initial delay. Because there is no delay by default, this method would only be used when a -- @{#SPAWN.SpawnScheduleStop}() ; @{#SPAWN.SpawnScheduleStart}() sequence would have been used. --- --- +-- -- @field #SPAWN SPAWN --- SPAWN = { ClassName = "SPAWN", SpawnTemplatePrefix = nil, SpawnAliasPrefix = nil, } - --- Enumerator for spawns at airbases -- @type SPAWN.Takeoff -- @extends Wrapper.Group#GROUP.Takeoff @@ -286,7 +279,6 @@ SPAWN.Takeoff = { --- @type SPAWN.SpawnZoneTable -- @list SpawnZone - --- Creates the main object to spawn a @{Wrapper.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. @@ -296,47 +288,47 @@ SPAWN.Takeoff = { -- 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:FindByName( 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.SpawnInitLimit = false -- By default, no InitLimit - 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.AIOnOff = true -- The AI is on by default when spawning a group. + local self = BASE:Inherit( self, BASE:New() ) -- #SPAWN + self:F( { SpawnTemplatePrefix } ) + + local TemplateGroup = GROUP:FindByName( 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.SpawnInitLimit = false -- By default, no InitLimit + 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.AIOnOff = true -- The AI is on by default when spawning a group. self.SpawnUnControlled = false - self.SpawnInitKeepUnitNames = false -- Overwrite unit names by default with group name. - self.DelayOnOff = false -- No intial delay when spawning the first group. - self.SpawnGrouping = nil -- No grouping. - self.SpawnInitLivery = nil -- No special livery. - self.SpawnInitSkill = nil -- No special skill. - self.SpawnInitFreq = nil -- No special frequency. - self.SpawnInitModu = nil -- No special modulation. - self.SpawnInitRadio = nil -- No radio comms setting. + self.SpawnInitKeepUnitNames = false -- Overwrite unit names by default with group name. + self.DelayOnOff = false -- No intial delay when spawning the first group. + self.SpawnGrouping = nil -- No grouping. + self.SpawnInitLivery = nil -- No special livery. + self.SpawnInitSkill = nil -- No special skill. + self.SpawnInitFreq = nil -- No special frequency. + self.SpawnInitModu = nil -- No special modulation. + self.SpawnInitRadio = nil -- No radio comms setting. self.SpawnInitModex = nil self.SpawnInitAirbase = nil - self.TweakedTemplate = false -- Check if the user is using self made template. + self.TweakedTemplate = false -- Check if the user is using self made template. - 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 + 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 self:SetEventPriority( 5 ) self.SpawnHookScheduler = SCHEDULER:New( nil ) - return self + 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. @@ -349,50 +341,49 @@ end -- 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:FindByName( 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.SpawnInitLimit = false -- By default, no InitLimit - 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.AIOnOff = true -- The AI is on by default when spawning a group. + local self = BASE:Inherit( self, BASE:New() ) + self:F( { SpawnTemplatePrefix, SpawnAliasPrefix } ) + + local TemplateGroup = GROUP:FindByName( 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.SpawnInitLimit = false -- By default, no InitLimit + 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.AIOnOff = true -- The AI is on by default when spawning a group. self.SpawnUnControlled = false - self.SpawnInitKeepUnitNames = false -- Overwrite unit names by default with group name. - self.DelayOnOff = false -- No intial delay when spawning the first group. - self.SpawnGrouping = nil -- No grouping. - self.SpawnInitLivery = nil -- No special livery. - self.SpawnInitSkill = nil -- No special skill. - self.SpawnInitFreq = nil -- No special frequency. - self.SpawnInitModu = nil -- No special modulation. - self.SpawnInitRadio = nil -- No radio comms setting. + self.SpawnInitKeepUnitNames = false -- Overwrite unit names by default with group name. + self.DelayOnOff = false -- No initial delay when spawning the first group. + self.SpawnGrouping = nil -- No grouping. + self.SpawnInitLivery = nil -- No special livery. + self.SpawnInitSkill = nil -- No special skill. + self.SpawnInitFreq = nil -- No special frequency. + self.SpawnInitModu = nil -- No special modulation. + self.SpawnInitRadio = nil -- No radio communication setting. self.SpawnInitModex = nil self.SpawnInitAirbase = nil - self.TweakedTemplate = false -- Check if the user is using self made template. + self.TweakedTemplate = false -- Check if the user is using self made template. + + 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 - 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 - self:SetEventPriority( 5 ) self.SpawnHookScheduler = SCHEDULER:New( nil ) - - return self -end + return self +end --- Creates a new SPAWN instance to create new groups based on the provided template. -- @param #SPAWN self @@ -403,86 +394,89 @@ end -- @usage -- -- Create a new SPAWN object based on a Group Template defined from scratch. -- Spawn_BE_KA50 = SPAWN:NewWithAlias( 'BE KA-50@RAMP-Ground Defense', 'Helicopter Attacking a City' ) --- @usage --- -- Create a new CSAR_Spawn object based on a normal Group Template to spawn a soldier. --- local CSAR_Spawn = SPAWN:NewWithFromTemplate( Template, "CSAR", "Pilot" ) +-- @usage +-- +-- -- Create a new CSAR_Spawn object based on a normal Group Template to spawn a soldier. +-- local CSAR_Spawn = SPAWN:NewWithFromTemplate( Template, "CSAR", "Pilot" ) +-- function SPAWN:NewFromTemplate( SpawnTemplate, SpawnTemplatePrefix, SpawnAliasPrefix ) local self = BASE:Inherit( self, BASE:New() ) self:F( { SpawnTemplate, SpawnTemplatePrefix, SpawnAliasPrefix } ) if SpawnAliasPrefix == nil or SpawnAliasPrefix == "" then - BASE:I("ERROR: in function NewFromTemplate, required paramter SpawnAliasPrefix is not set") + BASE:I( "ERROR: in function NewFromTemplate, required paramter SpawnAliasPrefix is not set" ) return nil end if SpawnTemplate then - self.SpawnTemplate = SpawnTemplate -- Contains the template structure for a Group Spawn from the Mission Editor. Note that this group must have lateActivation always on!!! + self.SpawnTemplate = SpawnTemplate -- Contains the template structure for a Group Spawn from the Mission Editor. Note that this group must have lateActivation always on!!! 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.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.SpawnInitLimit = false -- By default, no InitLimit. - 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.AIOnOff = true -- The AI is on by default when spawning a group. + 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.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.SpawnInitLimit = false -- By default, no InitLimit. + 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.AIOnOff = true -- The AI is on by default when spawning a group. self.SpawnUnControlled = false - self.SpawnInitKeepUnitNames = false -- Overwrite unit names by default with group name. - self.DelayOnOff = false -- No intial delay when spawning the first group. - self.Grouping = nil -- No grouping. - self.SpawnInitLivery = nil -- No special livery. - self.SpawnInitSkill = nil -- No special skill. - self.SpawnInitFreq = nil -- No special frequency. - self.SpawnInitModu = nil -- No special modulation. - self.SpawnInitRadio = nil -- No radio comms setting. + self.SpawnInitKeepUnitNames = false -- Overwrite unit names by default with group name. + self.DelayOnOff = false -- No initial delay when spawning the first group. + self.Grouping = nil -- No grouping. + self.SpawnInitLivery = nil -- No special livery. + self.SpawnInitSkill = nil -- No special skill. + self.SpawnInitFreq = nil -- No special frequency. + self.SpawnInitModu = nil -- No special modulation. + self.SpawnInitRadio = nil -- No radio communication setting. self.SpawnInitModex = nil self.SpawnInitAirbase = nil - self.TweakedTemplate = true -- Check if the user is using self made template. - - self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned. + self.TweakedTemplate = true -- Check if the user is using self made template. + + self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned. else error( "There is no template provided for SpawnTemplatePrefix = '" .. SpawnTemplatePrefix .. "'" ) end - + self:SetEventPriority( 5 ) self.SpawnHookScheduler = SCHEDULER:New( nil ) - + return self end - --- Stops any more repeat spawns from happening once the UNIT count of Alive units, spawned by the same SPAWN object, exceeds the first parameter. Also can stop spawns from happening once a total GROUP still alive is met. -- Exceptionally powerful when combined with SpawnSchedule for Respawning. -- 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 method should be used... -- When a @{#SPAWN.New} is executed and the limit of the amount of units alive is reached, then no new spawn will happen of the group, until some of these units of the spawn object will be destroyed. -- @param #SPAWN self --- @param #number SpawnMaxUnitsAlive The maximum amount of units that can be alive at runtime. --- @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. +-- @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' ):InitLimit( 2, 24 ) +-- +-- -- 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' ):InitLimit( 2, 24 ) +-- function SPAWN:InitLimit( SpawnMaxUnitsAlive, SpawnMaxGroups ) - self:F( { self.SpawnTemplatePrefix, SpawnMaxUnitsAlive, SpawnMaxGroups } ) + self:F( { self.SpawnTemplatePrefix, SpawnMaxUnitsAlive, SpawnMaxGroups } ) self.SpawnInitLimit = true - 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 + 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. - return self + for SpawnGroupID = 1, self.SpawnMaxGroups do + self:_InitializeSpawnGroups( SpawnGroupID ) + end + + return self end --- Keeps the unit names as defined within the mission editor, @@ -493,23 +487,22 @@ end -- @param #boolean KeepUnitNames (optional) If true, the unit names are kept, false or not provided to make new unit names. -- @return #SPAWN self function SPAWN:InitKeepUnitNames( KeepUnitNames ) - self:F( ) + self:F() self.SpawnInitKeepUnitNames = KeepUnitNames or true - + return self end - --- Flags that the spawned groups must be spawned late activated. -- @param #SPAWN self -- @param #boolean LateActivated (optional) If true, the spawned groups are late activated. -- @return #SPAWN self function SPAWN:InitLateActivated( LateActivated ) - self:F( ) + self:F() self.LateActivated = LateActivated or true - + return self end @@ -517,47 +510,45 @@ end -- @param #SPAWN self -- @param #string AirbaseName Name of the airbase. -- @param #number Takeoff (Optional) Takeoff type. Can be SPAWN.Takeoff.Hot (default), SPAWN.Takeoff.Cold or SPAWN.Takeoff.Runway. --- @param #number TerminalTyple (Optional) The terminal type. +-- @param #number TerminalType (Optional) The terminal type. -- @return #SPAWN self function SPAWN:InitAirbase( AirbaseName, Takeoff, TerminalType ) - self:F( ) + self:F() + + self.SpawnInitAirbase = AIRBASE:FindByName( AirbaseName ) + + self.SpawnInitTakeoff = Takeoff or SPAWN.Takeoff.Hot + + self.SpawnInitTerminalType = TerminalType - self.SpawnInitAirbase=AIRBASE:FindByName(AirbaseName) - - self.SpawnInitTakeoff=Takeoff or SPAWN.Takeoff.Hot - - self.SpawnInitTerminalType=TerminalType - return self end - ---- Defines the Heading for the new spawned units. +--- Defines the Heading for the new spawned units. -- The heading can be given as one fixed degree, or can be randomized between minimum and maximum degrees. -- @param #SPAWN self -- @param #number HeadingMin The minimum or fixed heading in degrees. -- @param #number HeadingMax (optional) The maximum heading in degrees. This there is no maximum heading, then the heading will be fixed for all units using minimum heading. -- @return #SPAWN self -- @usage --- --- Spawn = SPAWN:New( ... ) --- --- -- Spawn the units pointing to 100 degrees. --- Spawn:InitHeading( 100 ) --- --- -- Spawn the units pointing between 100 and 150 degrees. --- Spawn:InitHeading( 100, 150 ) --- +-- +-- Spawn = SPAWN:New( ... ) +-- +-- -- Spawn the units pointing to 100 degrees. +-- Spawn:InitHeading( 100 ) +-- +-- -- Spawn the units pointing between 100 and 150 degrees. +-- Spawn:InitHeading( 100, 150 ) +-- function SPAWN:InitHeading( HeadingMin, HeadingMax ) - self:F( ) + self:F() self.SpawnInitHeadingMin = HeadingMin self.SpawnInitHeadingMax = HeadingMax - + return self end - --- Defines the heading of the overall formation of the new spawned group. -- The heading can be given as one fixed degree, or can be randomized between minimum and maximum degrees. -- The Group's formation as laid out in its template will be rotated around the first unit in the group @@ -569,116 +560,114 @@ end -- @param #number unitVar (optional) Individual units within the group will have their heading randomized by +/- unitVar degrees. Default is zero. -- @return #SPAWN self -- @usage --- +-- -- mySpawner = SPAWN:New( ... ) --- --- -- Spawn the Group with the formation rotated +100 degrees around unit #1, compared to the mission template. --- mySpawner:InitGroupHeading( 100 ) --- --- Spawn the Group with the formation rotated units between +100 and +150 degrees around unit #1, compared to the mission template, and with individual units varying by +/- 10 degrees from their templated facing. --- mySpawner:InitGroupHeading( 100, 150, 10 ) --- --- Spawn the Group with the formation rotated -60 degrees around unit #1, compared to the mission template, but with all units facing due north regardless of how they were laid out in the template. --- mySpawner:InitGroupHeading(-60):InitHeading(0) --- or --- mySpawner:InitHeading(0):InitGroupHeading(-60) --- +-- +-- -- Spawn the Group with the formation rotated +100 degrees around unit #1, compared to the mission template. +-- mySpawner:InitGroupHeading( 100 ) +-- +-- -- Spawn the Group with the formation rotated units between +100 and +150 degrees around unit #1, compared to the mission template, and with individual units varying by +/- 10 degrees from their templated facing. +-- mySpawner:InitGroupHeading( 100, 150, 10 ) +-- +-- -- Spawn the Group with the formation rotated -60 degrees around unit #1, compared to the mission template, but with all units facing due north regardless of how they were laid out in the template. +-- mySpawner:InitGroupHeading(-60):InitHeading(0) +-- -- or +-- mySpawner:InitHeading(0):InitGroupHeading(-60) +-- function SPAWN:InitGroupHeading( HeadingMin, HeadingMax, unitVar ) - self:F({HeadingMin=HeadingMin, HeadingMax=HeadingMax, unitVar=unitVar}) + self:F( { HeadingMin = HeadingMin, HeadingMax = HeadingMax, unitVar = unitVar } ) self.SpawnInitGroupHeadingMin = HeadingMin self.SpawnInitGroupHeadingMax = HeadingMax - self.SpawnInitGroupUnitVar = unitVar + self.SpawnInitGroupUnitVar = unitVar return self end - --- Sets the coalition of the spawned group. Note that it might be necessary to also set the country explicitly! --- @param #SPAWN self +-- @param #SPAWN self -- @param DCS#coalition.side Coalition Coalition of the group as number of enumerator: --- --- * @{DCS#coaliton.side.NEUTRAL} --- * @{DCS#coaliton.side.RED} +-- +-- * @{DCS#coalition.side.NEUTRAL} +-- * @{DCS#coalition.side.RED} -- * @{DCS#coalition.side.BLUE} --- +-- -- @return #SPAWN self function SPAWN:InitCoalition( Coalition ) - self:F({coalition=Coalition}) + self:F( { coalition = Coalition } ) self.SpawnInitCoalition = Coalition - + return self end ---- Sets the country of the spawn group. Note that the country determins the coalition of the group depending on which country is defined to be on which side for each specific mission! --- @param #SPAWN self +--- Sets the country of the spawn group. Note that the country determines the coalition of the group depending on which country is defined to be on which side for each specific mission! +-- @param #SPAWN self -- @param #number Country Country id as number or enumerator: --- +-- -- * @{DCS#country.id.RUSSIA} -- * @{DCS#county.id.USA} --- +-- -- @return #SPAWN self function SPAWN:InitCountry( Country ) - self:F( ) + self:F() self.SpawnInitCountry = Country - + return self end - --- Sets category ID of the group. --- @param #SPAWN self +-- @param #SPAWN self -- @param #number Category Category id. -- @return #SPAWN self function SPAWN:InitCategory( Category ) - self:F( ) + self:F() self.SpawnInitCategory = Category - + return self end --- Sets livery of the group. --- @param #SPAWN self --- @param #string Livery Livery name. Note that this is not necessarily the same name as displayed in the mission edior. +-- @param #SPAWN self +-- @param #string Livery Livery name. Note that this is not necessarily the same name as displayed in the mission editor. -- @return #SPAWN self function SPAWN:InitLivery( Livery ) - self:F({livery=Livery} ) + self:F( { livery = Livery } ) self.SpawnInitLivery = Livery - + return self end --- Sets skill of the group. --- @param #SPAWN self +-- @param #SPAWN self -- @param #string Skill Skill, possible values "Average", "Good", "High", "Excellent" or "Random". -- @return #SPAWN self function SPAWN:InitSkill( Skill ) - self:F({skill=Skill}) - if Skill:lower()=="average" then - self.SpawnInitSkill="Average" - elseif Skill:lower()=="good" then - self.SpawnInitSkill="Good" - elseif Skill:lower()=="excellent" then - self.SpawnInitSkill="Excellent" - elseif Skill:lower()=="random" then - self.SpawnInitSkill="Random" + self:F( { skill = Skill } ) + if Skill:lower() == "average" then + self.SpawnInitSkill = "Average" + elseif Skill:lower() == "good" then + self.SpawnInitSkill = "Good" + elseif Skill:lower() == "excellent" then + self.SpawnInitSkill = "Excellent" + elseif Skill:lower() == "random" then + self.SpawnInitSkill = "Random" else - self.SpawnInitSkill="High" + self.SpawnInitSkill = "High" end - + return self end ---- Sets the radio comms on or off. Same as checking/unchecking the COMM box in the mission editor. --- @param #SPAWN self --- @param #number switch If true (or nil), enables the radio comms. If false, disables the radio for the spawned group. +--- Sets the radio communication on or off. Same as checking/unchecking the COMM box in the mission editor. +-- @param #SPAWN self +-- @param #number switch If true (or nil), enables the radio communication. If false, disables the radio for the spawned group. -- @return #SPAWN self -function SPAWN:InitRadioCommsOnOff(switch) - self:F({switch=switch} ) - self.SpawnInitRadio=switch or true +function SPAWN:InitRadioCommsOnOff( switch ) + self:F( { switch = switch } ) + self.SpawnInitRadio = switch or true return self end @@ -686,11 +675,11 @@ end -- @param #SPAWN self -- @param #number frequency The frequency in MHz. -- @return #SPAWN self -function SPAWN:InitRadioFrequency(frequency) - self:F({frequency=frequency} ) +function SPAWN:InitRadioFrequency( frequency ) + self:F( { frequency = frequency } ) + + self.SpawnInitFreq = frequency - self.SpawnInitFreq=frequency - return self end @@ -698,64 +687,65 @@ end -- @param #SPAWN self -- @param #string modulation Either "FM" or "AM". If no value is given, modulation is set to AM. -- @return #SPAWN self -function SPAWN:InitRadioModulation(modulation) - self:F({modulation=modulation}) - if modulation and modulation:lower()=="fm" then - self.SpawnInitModu=radio.modulation.FM +function SPAWN:InitRadioModulation( modulation ) + self:F( { modulation = modulation } ) + if modulation and modulation:lower() == "fm" then + self.SpawnInitModu = radio.modulation.FM else - self.SpawnInitModu=radio.modulation.AM + self.SpawnInitModu = radio.modulation.AM end return self end --- Sets the modex of the first unit of the group. If more units are in the group, the number is increased by one with every unit. --- @param #SPAWN self +-- @param #SPAWN self -- @param #number modex Modex of the first unit. -- @return #SPAWN self -function SPAWN:InitModex(modex) +function SPAWN:InitModex( modex ) if modex then - self.SpawnInitModex=tonumber(modex) + self.SpawnInitModex = tonumber( modex ) 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. +--- Randomizes the defined route of the SpawnTemplatePrefix group in the ME. This is very useful to define extra variation of the behavior of groups. -- @param #SPAWN self --- @param #number SpawnStartPoint is the waypoint where the randomization begins. +-- @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. +-- @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' ):InitRandomizeRoute( 2, 2, 2000 ) +-- +-- -- NATO helicopters engaging in the battle field. +-- -- The KA-50 has waypoints Start point ( =0 or SP ), 1, 2, 3, 4, End point (= 5 or DP). +-- -- Waypoints 2 and 3 will only be randomized. The others will remain on their original position with each new spawn of the helicopter. +-- -- The randomization of waypoint 2 and 3 will take place within a radius of 2000 meters. +-- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitRandomizeRoute( 2, 2, 2000 ) +-- function SPAWN:InitRandomizeRoute( SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight ) - self:F( { self.SpawnTemplatePrefix, 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 + 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 + for GroupID = 1, self.SpawnMaxGroups do + self:_RandomizeRoute( GroupID ) + end + + return self end --- Randomizes the position of @{Wrapper.Group}s that are spawned within a **radius band**, given an Outer and Inner radius, from the point that the spawn happens. -- @param #SPAWN self --- @param #boolean RandomizePosition If true, SPAWN will perform the randomization of the @{Wrapper.Group}s position between a given outer and inner radius. +-- @param #boolean RandomizePosition If true, SPAWN will perform the randomization of the @{Wrapper.Group}s position between a given outer and inner radius. -- @param DCS#Distance OuterRadius (optional) The outer radius in meters where the new group will be spawned. -- @param DCS#Distance InnerRadius (optional) The inner radius in meters where the new group will NOT be spawned. -- @return #SPAWN @@ -769,23 +759,24 @@ function SPAWN:InitRandomizePosition( RandomizePosition, OuterRadius, InnerRadiu for GroupID = 1, self.SpawnMaxGroups do self:_RandomizeRoute( GroupID ) end - + return self end - --- Randomizes the UNITs that are spawned within a radius band given an Outer and Inner radius. -- @param #SPAWN self --- @param #boolean RandomizeUnits If true, SPAWN will perform the randomization of the @{UNIT}s position within the group between a given outer and inner radius. +-- @param #boolean RandomizeUnits If true, SPAWN will perform the randomization of the @{UNIT}s position within the group between a given outer and inner radius. -- @param DCS#Distance OuterRadius (optional) The outer radius in meters where the new group will be spawned. -- @param DCS#Distance InnerRadius (optional) The inner radius in meters where the new group will NOT be spawned. -- @return #SPAWN -- @usage --- -- NATO helicopters engaging in the battle field. --- -- The KA-50 has waypoints Start point ( =0 or SP ), 1, 2, 3, 4, End point (= 5 or DP). --- -- Waypoints 2 and 3 will only be randomized. The others will remain on their original position with each new spawn of the helicopter. --- -- The randomization of waypoint 2 and 3 will take place within a radius of 2000 meters. --- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitRandomizeRoute( 2, 2, 2000 ) +-- +-- -- NATO helicopters engaging in the battle field. +-- -- The KA-50 has waypoints Start point ( =0 or SP ), 1, 2, 3, 4, End point (= 5 or DP). +-- -- Waypoints 2 and 3 will only be randomized. The others will remain on their original position with each new spawn of the helicopter. +-- -- The randomization of waypoint 2 and 3 will take place within a radius of 2000 meters. +-- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitRandomizeRoute( 2, 2, 2000 ) +-- function SPAWN:InitRandomizeUnits( RandomizeUnits, OuterRadius, InnerRadius ) self:F( { self.SpawnTemplatePrefix, RandomizeUnits, OuterRadius, InnerRadius } ) @@ -796,65 +787,67 @@ function SPAWN:InitRandomizeUnits( RandomizeUnits, OuterRadius, InnerRadius ) for GroupID = 1, self.SpawnMaxGroups do self:_RandomizeRoute( GroupID ) end - + return self end --- This method is rather complicated to understand. But I'll try to explain. --- This method becomes useful when you need to spawn groups with random templates of groups defined within the mission editor, +-- This method becomes useful when you need to spawn groups with random templates of groups defined within the mission editor, -- but they will all follow the same Template route and have the same prefix name. -- In other words, this method randomizes between a defined set of groups the template to be used for each new spawn of a group. -- @param #SPAWN self --- @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. +-- @param #string SpawnTemplatePrefixTable A table with the names of the groups defined within the mission editor, from which one will be chosen 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' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplate( Spawn_US_Platoon ):InitRandomizeRoute( 3, 3, 2000 ) --- Spawn_US_Platoon_Middle = SPAWN:New( 'US Tank Platoon Middle' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplate( Spawn_US_Platoon ):InitRandomizeRoute( 3, 3, 2000 ) --- Spawn_US_Platoon_Right = SPAWN:New( 'US Tank Platoon Right' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplate( Spawn_US_Platoon ):InitRandomizeRoute( 3, 3, 2000 ) +-- +-- -- 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 simultaneously 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' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplate( Spawn_US_Platoon ):InitRandomizeRoute( 3, 3, 2000 ) +-- Spawn_US_Platoon_Middle = SPAWN:New( 'US Tank Platoon Middle' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplate( Spawn_US_Platoon ):InitRandomizeRoute( 3, 3, 2000 ) +-- Spawn_US_Platoon_Right = SPAWN:New( 'US Tank Platoon Right' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplate( Spawn_US_Platoon ):InitRandomizeRoute( 3, 3, 2000 ) function SPAWN:InitRandomizeTemplate( SpawnTemplatePrefixTable ) - self:F( { self.SpawnTemplatePrefix, SpawnTemplatePrefixTable } ) + self:F( { self.SpawnTemplatePrefix, SpawnTemplatePrefixTable } ) - self.SpawnTemplatePrefixTable = SpawnTemplatePrefixTable - self.SpawnRandomizeTemplate = true + self.SpawnTemplatePrefixTable = SpawnTemplatePrefixTable + self.SpawnRandomizeTemplate = true - for SpawnGroupID = 1, self.SpawnMaxGroups do - self:_RandomizeTemplate( SpawnGroupID ) - end - - return self + for SpawnGroupID = 1, self.SpawnMaxGroups do + self:_RandomizeTemplate( SpawnGroupID ) + end + + return self end - --- Randomize templates to be used as the unit representatives for the Spawned group, defined using a SET_GROUP object. --- This method becomes useful when you need to spawn groups with random templates of groups defined within the mission editor, +-- This method becomes useful when you need to spawn groups with random templates of groups defined within the mission editor, -- but they will all follow the same Template route and have the same prefix name. -- In other words, this method randomizes between a defined set of groups the template to be used for each new spawn of a group. -- @param #SPAWN self --- @param Core.Set#SET_GROUP SpawnTemplateSet A SET_GROUP object set, that contains the groups that are possible unit representatives of the group to be spawned. +-- @param Core.Set#SET_GROUP SpawnTemplateSet A SET_GROUP object set, that contains the groups that are possible unit representatives of the group to be spawned. -- @return #SPAWN -- @usage --- -- NATO Tank Platoons invading Gori. --- --- -- Choose between different 'US Tank Platoon Template' configurations to be spawned for the --- -- 'US Tank Platoon Left', 'US Tank Platoon Middle' and 'US Tank Platoon Right' SPAWN objects. --- --- -- 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_PlatoonSet = SET_GROUP:New():FilterPrefixes( "US Tank Platoon Templates" ):FilterOnce() --- --- --- Now use the Spawn_US_PlatoonSet to define the templates using InitRandomizeTemplateSet. --- Spawn_US_Platoon_Left = SPAWN:New( 'US Tank Platoon Left' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplateSet( Spawn_US_PlatoonSet ):InitRandomizeRoute( 3, 3, 2000 ) --- Spawn_US_Platoon_Middle = SPAWN:New( 'US Tank Platoon Middle' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplateSet( Spawn_US_PlatoonSet ):InitRandomizeRoute( 3, 3, 2000 ) --- Spawn_US_Platoon_Right = SPAWN:New( 'US Tank Platoon Right' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplateSet( Spawn_US_PlatoonSet ):InitRandomizeRoute( 3, 3, 2000 ) +-- +-- -- NATO Tank Platoons invading Gori. +-- +-- -- Choose between different 'US Tank Platoon Template' configurations to be spawned for the +-- -- 'US Tank Platoon Left', 'US Tank Platoon Middle' and 'US Tank Platoon Right' SPAWN objects. +-- +-- -- 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 simultaneously and 150 Groups to be spawned during the whole mission. +-- +-- Spawn_US_PlatoonSet = SET_GROUP:New():FilterPrefixes( "US Tank Platoon Templates" ):FilterOnce() +-- +-- -- Now use the Spawn_US_PlatoonSet to define the templates using InitRandomizeTemplateSet. +-- Spawn_US_Platoon_Left = SPAWN:New( 'US Tank Platoon Left' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplateSet( Spawn_US_PlatoonSet ):InitRandomizeRoute( 3, 3, 2000 ) +-- Spawn_US_Platoon_Middle = SPAWN:New( 'US Tank Platoon Middle' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplateSet( Spawn_US_PlatoonSet ):InitRandomizeRoute( 3, 3, 2000 ) +-- Spawn_US_Platoon_Right = SPAWN:New( 'US Tank Platoon Right' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplateSet( Spawn_US_PlatoonSet ):InitRandomizeRoute( 3, 3, 2000 ) +-- function SPAWN:InitRandomizeTemplateSet( SpawnTemplateSet ) -- R2.3 self:F( { self.SpawnTemplatePrefix } ) @@ -864,44 +857,44 @@ function SPAWN:InitRandomizeTemplateSet( SpawnTemplateSet ) -- R2.3 for SpawnGroupID = 1, self.SpawnMaxGroups do self:_RandomizeTemplate( SpawnGroupID ) end - + return self end - --- Randomize templates to be used as the unit representatives for the Spawned group, defined by specifying the prefix names. --- This method becomes useful when you need to spawn groups with random templates of groups defined within the mission editor, +-- This method becomes useful when you need to spawn groups with random templates of groups defined within the mission editor, -- but they will all follow the same Template route and have the same prefix name. -- In other words, this method randomizes between a defined set of groups the template to be used for each new spawn of a group. -- @param #SPAWN self -- @param #string SpawnTemplatePrefixes A string or a list of string that contains the prefixes of the groups that are possible unit representatives of the group to be spawned. -- @return #SPAWN -- @usage --- -- NATO Tank Platoons invading Gori. --- --- -- Choose between different 'US Tank Platoon Templates' configurations to be spawned for the --- -- 'US Tank Platoon Left', 'US Tank Platoon Middle' and 'US Tank Platoon Right' SPAWN objects. --- --- -- 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_Left = SPAWN:New( 'US Tank Platoon Left' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplatePrefixes( "US Tank Platoon Templates" ):InitRandomizeRoute( 3, 3, 2000 ) --- Spawn_US_Platoon_Middle = SPAWN:New( 'US Tank Platoon Middle' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplatePrefixes( "US Tank Platoon Templates" ):InitRandomizeRoute( 3, 3, 2000 ) --- Spawn_US_Platoon_Right = SPAWN:New( 'US Tank Platoon Right' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplatePrefixes( "US Tank Platoon Templates" ):InitRandomizeRoute( 3, 3, 2000 ) -function SPAWN:InitRandomizeTemplatePrefixes( SpawnTemplatePrefixes ) --R2.3 +-- +-- -- NATO Tank Platoons invading Gori. +-- +-- -- Choose between different 'US Tank Platoon Templates' configurations to be spawned for the +-- -- 'US Tank Platoon Left', 'US Tank Platoon Middle' and 'US Tank Platoon Right' SPAWN objects. +-- +-- -- 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 simultaneously and 150 Groups to be spawned during the whole mission. +-- +-- Spawn_US_Platoon_Left = SPAWN:New( 'US Tank Platoon Left' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplatePrefixes( "US Tank Platoon Templates" ):InitRandomizeRoute( 3, 3, 2000 ) +-- Spawn_US_Platoon_Middle = SPAWN:New( 'US Tank Platoon Middle' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplatePrefixes( "US Tank Platoon Templates" ):InitRandomizeRoute( 3, 3, 2000 ) +-- Spawn_US_Platoon_Right = SPAWN:New( 'US Tank Platoon Right' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplatePrefixes( "US Tank Platoon Templates" ):InitRandomizeRoute( 3, 3, 2000 ) +-- +function SPAWN:InitRandomizeTemplatePrefixes( SpawnTemplatePrefixes ) -- R2.3 self:F( { self.SpawnTemplatePrefix } ) local SpawnTemplateSet = SET_GROUP:New():FilterPrefixes( SpawnTemplatePrefixes ):FilterOnce() self:InitRandomizeTemplateSet( SpawnTemplateSet ) - + return self end - --- When spawning a new group, make the grouping of the units according the InitGrouping setting. -- @param #SPAWN self --- @param #number Grouping Indicates the maximum amount of units in the group. +-- @param #number Grouping Indicates the maximum amount of units in the group. -- @return #SPAWN function SPAWN:InitGrouping( Grouping ) -- R2.2 self:F( { self.SpawnTemplatePrefix, Grouping } ) @@ -911,22 +904,21 @@ function SPAWN:InitGrouping( Grouping ) -- R2.2 return self end - - --- This method provides the functionality to randomize the spawning of the Groups at a given list of zones of different types. -- @param #SPAWN self --- @param #table SpawnZoneTable A table with @{Zone} objects. If this table is given, then each spawn will be executed within the given list of @{Zone}s objects. +-- @param #table SpawnZoneTable A table with @{Zone} objects. If this table is given, then each spawn will be executed within the given list of @{Zone}s objects. -- @return #SPAWN -- @usage --- -- Create a zone table of the 2 zones. --- ZoneTable = { ZONE:New( "Zone1" ), ZONE:New( "Zone2" ) } --- --- Spawn_Vehicle_1 = SPAWN:New( "Spawn Vehicle 1" ) --- :InitLimit( 10, 10 ) --- :InitRandomizeRoute( 1, 1, 200 ) --- :InitRandomizeZones( ZoneTable ) --- :SpawnScheduled( 5, .5 ) --- +-- +-- -- Create a zone table of the 2 zones. +-- ZoneTable = { ZONE:New( "Zone1" ), ZONE:New( "Zone2" ) } +-- +-- Spawn_Vehicle_1 = SPAWN:New( "Spawn Vehicle 1" ) +-- :InitLimit( 10, 10 ) +-- :InitRandomizeRoute( 1, 1, 200 ) +-- :InitRandomizeZones( ZoneTable ) +-- :SpawnScheduled( 5, .5 ) +-- function SPAWN:InitRandomizeZones( SpawnZoneTable ) self:F( { self.SpawnTemplatePrefix, SpawnZoneTable } ) @@ -936,111 +928,106 @@ function SPAWN:InitRandomizeZones( SpawnZoneTable ) for SpawnGroupID = 1, self.SpawnMaxGroups do self:_RandomizeZones( SpawnGroupID ) end - + return self end - - - - ---- For planes and helicopters, when these groups go home and land on their home airbases and farps, they normally would taxi to the parking spot, shut-down their engines and wait forever until the Group is removed by the runtime environment. --- This method is used to re-spawn automatically (so no extra call is needed anymore) the same group after it has landed. +--- 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 method is used to re-spawn automatically (so no extra call is needed anymore) the same group after it has landed. -- This will enable a spawned group to be re-spawned after it lands, until it is destroyed... --- Note: When the group is respawned, it will re-spawn from the original airbase where it took off. +-- 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( 'Su-34' ) --- :Schedule( 2, 3, 1800, 0.4 ) --- :SpawnUncontrolled() --- :InitRandomizeRoute( 1, 1, 3000 ) --- :InitRepeatOnEngineShutDown() --- +-- +-- -- RU Su-34 - AI Ship Attack +-- -- Re-SPAWN the Group(s) after each landing and Engine Shut-Down automatically. +-- SpawnRU_SU34 = SPAWN:New( 'Su-34' ) +-- :Schedule( 2, 3, 1800, 0.4 ) +-- :SpawnUncontrolled() +-- :InitRandomizeRoute( 1, 1, 3000 ) +-- :InitRepeatOnEngineShutDown() +-- function SPAWN:InitRepeat() - self:F( { self.SpawnTemplatePrefix, self.SpawnIndex } ) + self:F( { self.SpawnTemplatePrefix, self.SpawnIndex } ) - self.Repeat = true - self.RepeatOnEngineShutDown = false - self.RepeatOnLanding = true + self.Repeat = true + self.RepeatOnEngineShutDown = false + self.RepeatOnLanding = true - return self + return self end --- Respawn group after landing. -- @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( 'Su-34' ) --- :InitRandomizeRoute( 1, 1, 3000 ) --- :InitRepeatOnLanding() --- :Spawn() +-- +-- -- RU Su-34 - AI Ship Attack +-- -- Re-SPAWN the Group(s) after each landing and Engine Shut-Down automatically. +-- SpawnRU_SU34 = SPAWN:New( 'Su-34' ) +-- :InitRandomizeRoute( 1, 1, 3000 ) +-- :InitRepeatOnLanding() +-- :Spawn() +-- function SPAWN:InitRepeatOnLanding() - self:F( { self.SpawnTemplatePrefix } ) + self:F( { self.SpawnTemplatePrefix } ) - self:InitRepeat() - self.RepeatOnEngineShutDown = false - self.RepeatOnLanding = true - - return self + 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 -- @usage --- -- RU Su-34 - AI Ship Attack --- -- Re-SPAWN the Group(s) after each landing and Engine Shut-Down automatically. --- SpawnRU_SU34 = SPAWN --- :New( 'Su-34' ) --- :SpawnUncontrolled() --- :InitRandomizeRoute( 1, 1, 3000 ) --- :InitRepeatOnEngineShutDown() --- :Spawn() +-- +-- -- RU Su-34 - AI Ship Attack +-- -- Re-SPAWN the Group(s) after each landing and Engine Shut-Down automatically. +-- SpawnRU_SU34 = SPAWN:New( 'Su-34' ) +-- :SpawnUncontrolled() +-- :InitRandomizeRoute( 1, 1, 3000 ) +-- :InitRepeatOnEngineShutDown() +-- :Spawn() function SPAWN:InitRepeatOnEngineShutDown() - self:F( { self.SpawnTemplatePrefix } ) + self:F( { self.SpawnTemplatePrefix } ) - self:InitRepeat() - self.RepeatOnEngineShutDown = true - self.RepeatOnLanding = false - - return self + self:InitRepeat() + self.RepeatOnEngineShutDown = true + self.RepeatOnLanding = false + + return self end - --- Delete groups that have not moved for X seconds - AIR ONLY!!! -- DO NOT USE ON GROUPS THAT DO NOT MOVE OR YOUR SERVER WILL BURN IN HELL (Pikes - April 2020) -- 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:InitCleanUp( 20 ) -- CleanUp the spawning of the helicopters every 20 seconds when they become inactive. +-- @usage +-- +-- Spawn_Helicopter:InitCleanUp( 20 ) -- CleanUp the spawning of the helicopters every 20 seconds when they become inactive. +-- function SPAWN:InitCleanUp( SpawnCleanUpInterval ) - self:F( { self.SpawnTemplatePrefix, SpawnCleanUpInterval } ) + self:F( { self.SpawnTemplatePrefix, SpawnCleanUpInterval } ) - self.SpawnCleanUpInterval = SpawnCleanUpInterval - self.SpawnCleanUpTimeStamps = {} + self.SpawnCleanUpInterval = SpawnCleanUpInterval + self.SpawnCleanUpTimeStamps = {} local SpawnGroup, SpawnCursor = self:GetFirstAliveGroup() self:T( { "CleanUp Scheduler:", SpawnGroup } ) - - --self.CleanUpFunction = routines.scheduleFunction( self._SpawnCleanUpScheduler, { self }, timer.getTime() + 1, SpawnCleanUpInterval ) - self.CleanUpScheduler = SCHEDULER:New( self, self._SpawnCleanUpScheduler, {}, 1, SpawnCleanUpInterval, 0.2 ) - return self + + -- 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). +--- Makes the groups visible before start (like a battalion). -- The method will take the position of the group as the first position in the array. -- CAUTION: this directive will NOT work with OnSpawnGroup function. -- @param #SPAWN self @@ -1050,45 +1037,45 @@ end -- @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' ) --- :InitLimit( 2, 24 ) --- :InitArray( 90, 10, 100, 50 ) --- +-- +-- -- Define an array of Groups. +-- Spawn_BE_Ground = SPAWN:New( 'BE Ground' ) +-- :InitLimit( 2, 24 ) +-- :InitArray( 90, 10, 100, 50 ) +-- function SPAWN:InitArray( SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY ) - self:F( { self.SpawnTemplatePrefix, 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.SpawnVisible = true -- When the first Spawn executes, all the Groups need to be made visible before start. - 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 SpawnX = 0 + local SpawnY = 0 + local SpawnXIndex = 0 + local SpawnYIndex = 0 - 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 + 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 self:HandleEvent( EVENTS.Birth, self._OnBirth ) self:HandleEvent( EVENTS.Dead, self._OnDeadOrCrash ) @@ -1101,40 +1088,41 @@ function SPAWN:InitArray( SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY ) if self.RepeatOnEngineShutDown then self:HandleEvent( EVENTS.EngineShutdown, self._OnEngineShutDown ) end - - self.SpawnGroups[SpawnGroupID].Group = _DATABASE:Spawn( self.SpawnGroups[SpawnGroupID].SpawnTemplate ) - SpawnX = SpawnXIndex * SpawnDeltaX - SpawnY = SpawnYIndex * SpawnDeltaY - end - - return self + self.SpawnGroups[SpawnGroupID].Group = _DATABASE:Spawn( self.SpawnGroups[SpawnGroupID].SpawnTemplate ) + + SpawnX = SpawnXIndex * SpawnDeltaX + SpawnY = SpawnYIndex * SpawnDeltaY + end + + return self end do -- AI methods + --- Turns the AI On or Off for the @{Wrapper.Group} when spawning. -- @param #SPAWN self -- @param #boolean AIOnOff A value of true sets the AI On, a value of false sets the AI Off. -- @return #SPAWN The SPAWN object function SPAWN:InitAIOnOff( AIOnOff ) - + self.AIOnOff = AIOnOff return self end - + --- Turns the AI On for the @{Wrapper.Group} when spawning. -- @param #SPAWN self -- @return #SPAWN The SPAWN object function SPAWN:InitAIOn() - + return self:InitAIOnOff( true ) end - + --- Turns the AI Off for the @{Wrapper.Group} when spawning. -- @param #SPAWN self -- @return #SPAWN The SPAWN object function SPAWN:InitAIOff() - + return self:InitAIOnOff( false ) end @@ -1147,24 +1135,24 @@ do -- Delay methods -- @param #boolean DelayOnOff A value of true sets the Delay On, a value of false sets the Delay Off. -- @return #SPAWN The SPAWN object function SPAWN:InitDelayOnOff( DelayOnOff ) - + self.DelayOnOff = DelayOnOff return self end - + --- Turns the Delay On for the @{Wrapper.Group} when spawning. -- @param #SPAWN self -- @return #SPAWN The SPAWN object function SPAWN:InitDelayOn() - + return self:InitDelayOnOff( true ) end - + --- Turns the Delay Off for the @{Wrapper.Group} when spawning. -- @param #SPAWN self -- @return #SPAWN The SPAWN object function SPAWN:InitDelayOff() - + return self:InitDelayOnOff( false ) end @@ -1175,14 +1163,14 @@ end -- Delay methods -- @param #SPAWN self -- @return Wrapper.Group#GROUP The group that was spawned. You can use this group for further actions. function SPAWN:Spawn() - self:F( { self.SpawnTemplatePrefix, self.SpawnIndex, self.AliveUnits } ) + self:F( { self.SpawnTemplatePrefix, self.SpawnIndex, self.AliveUnits } ) if self.SpawnInitAirbase then - return self:SpawnAtAirbase(self.SpawnInitAirbase, self.SpawnInitTakeoff, nil, self.SpawnInitTerminalType) + return self:SpawnAtAirbase( self.SpawnInitAirbase, self.SpawnInitTakeoff, nil, self.SpawnInitTerminalType ) else - return self:SpawnWithIndex( self.SpawnIndex + 1 ) - end - + return self:SpawnWithIndex( self.SpawnIndex + 1 ) + end + end --- Will re-spawn a group based on a given index. @@ -1191,38 +1179,37 @@ end -- @param #string SpawnIndex The index of the group to be spawned. -- @return Wrapper.Group#GROUP The group that was spawned. You can use this group for further actions. function SPAWN:ReSpawn( SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, SpawnIndex } ) - - if not SpawnIndex then - SpawnIndex = 1 - end + self:F( { self.SpawnTemplatePrefix, SpawnIndex } ) --- TODO: This logic makes DCS crash and i don't know why (yet). -- ED (Pikes -- not in the least bit scary to see this, right?) - local SpawnGroup = self:GetGroupFromIndex( SpawnIndex ) - local WayPoints = SpawnGroup and SpawnGroup.WayPoints or nil - if SpawnGroup then - local SpawnDCSGroup = SpawnGroup:GetDCSObject() - if SpawnDCSGroup then - SpawnGroup:Destroy() - end + if not SpawnIndex then + SpawnIndex = 1 end - local SpawnGroup = self:SpawnWithIndex( SpawnIndex ) - if SpawnGroup and WayPoints then - -- If there were WayPoints set, then Re-Execute those WayPoints! - SpawnGroup:WayPointInitialize( WayPoints ) - SpawnGroup:WayPointExecute( 1, 5 ) - end - - if SpawnGroup.ReSpawnFunction then - SpawnGroup:ReSpawnFunction() - end - - SpawnGroup:ResetEvents() - - return SpawnGroup -end + -- TODO: This logic makes DCS crash and i don't know why (yet). -- ED (Pikes -- not in the least bit scary to see this, right?) + local SpawnGroup = self:GetGroupFromIndex( SpawnIndex ) + local WayPoints = SpawnGroup and SpawnGroup.WayPoints or nil + if SpawnGroup then + local SpawnDCSGroup = SpawnGroup:GetDCSObject() + if SpawnDCSGroup then + SpawnGroup:Destroy() + end + end + local SpawnGroup = self:SpawnWithIndex( SpawnIndex ) + if SpawnGroup and WayPoints then + -- If there were WayPoints set, then Re-Execute those WayPoints! + SpawnGroup:WayPointInitialize( WayPoints ) + SpawnGroup:WayPointExecute( 1, 5 ) + end + + if SpawnGroup.ReSpawnFunction then + SpawnGroup:ReSpawnFunction() + end + + SpawnGroup:ResetEvents() + + return SpawnGroup +end --- Set the spawn index to a specified index number. -- This method can be used to "reset" the spawn counter to a specific index number. @@ -1234,23 +1221,22 @@ function SPAWN:SetSpawnIndex( SpawnIndex ) self.SpawnIndex = SpawnIndex or 0 end - --- Will spawn a group with a specified index number. -- Uses @{DATABASE} global object defined in MOOSE. -- @param #SPAWN self -- @param #string SpawnIndex The index of the group to be spawned. -- @return Wrapper.Group#GROUP The group that was spawned. You can use this group for further actions. function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth ) - 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 + self:F2( { SpawnTemplatePrefix = self.SpawnTemplatePrefix, SpawnIndex = SpawnIndex, AliveUnits = self.AliveUnits, SpawnMaxGroups = self.SpawnMaxGroups } ) - local SpawnTemplate = self.SpawnGroups[self.SpawnIndex].SpawnTemplate - self:T( SpawnTemplate.name ) + if self:_GetSpawnIndex( SpawnIndex ) then + + if self.SpawnGroups[self.SpawnIndex].Visible then + self.SpawnGroups[self.SpawnIndex].Group:Activate() + else + + local SpawnTemplate = self.SpawnGroups[self.SpawnIndex].SpawnTemplate + self:T( SpawnTemplate.name ) if SpawnTemplate then @@ -1265,46 +1251,46 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth ) SpawnTemplate.x = RandomVec2.x SpawnTemplate.y = RandomVec2.y for UnitID = 1, #SpawnTemplate.units do - SpawnTemplate.units[UnitID].x = SpawnTemplate.units[UnitID].x + ( RandomVec2.x - CurrentX ) - SpawnTemplate.units[UnitID].y = SpawnTemplate.units[UnitID].y + ( RandomVec2.y - CurrentY ) - self:T( 'SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y ) + SpawnTemplate.units[UnitID].x = SpawnTemplate.units[UnitID].x + (RandomVec2.x - CurrentX) + SpawnTemplate.units[UnitID].y = SpawnTemplate.units[UnitID].y + (RandomVec2.y - CurrentY) + self:T( 'SpawnTemplate.units[' .. UnitID .. '].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. SpawnTemplate.units[UnitID].y ) end end - + -- If RandomizeUnits, then Randomize the formation at the start point. if self.SpawnRandomizeUnits then for UnitID = 1, #SpawnTemplate.units do local RandomVec2 = PointVec3:GetRandomVec2InRadius( self.SpawnOuterRadius, self.SpawnInnerRadius ) SpawnTemplate.units[UnitID].x = RandomVec2.x SpawnTemplate.units[UnitID].y = RandomVec2.y - self:T( 'SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y ) + self:T( 'SpawnTemplate.units[' .. UnitID .. '].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. SpawnTemplate.units[UnitID].y ) end end - - -- Get correct heading in Radians. - local function _Heading(courseDeg) - local h - if courseDeg<=180 then - h=math.rad(courseDeg) - else - h=-math.rad(360-courseDeg) - end - return h - end - local Rad180 = math.rad(180) - local function _HeadingRad(courseRad) - if courseRad<=Rad180 then + -- Get correct heading in Radians. + local function _Heading( courseDeg ) + local h + if courseDeg <= 180 then + h = math.rad( courseDeg ) + else + h = -math.rad( 360 - courseDeg ) + end + return h + end + + local Rad180 = math.rad( 180 ) + local function _HeadingRad( courseRad ) + if courseRad <= Rad180 then return courseRad else - return -((2*Rad180)-courseRad) + return -((2 * Rad180) - courseRad) end - end + end -- Generate a random value somewhere between two floating point values. - local function _RandomInRange ( min, max ) + local function _RandomInRange( min, max ) if min and max then - return min + ( math.random()*(max-min) ) + return min + (math.random() * (max - min)) else return min end @@ -1318,39 +1304,39 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth ) local pivotX = SpawnTemplate.units[1].x -- unit #1 is the pivot point local pivotY = SpawnTemplate.units[1].y - local headingRad = math.rad(_RandomInRange(self.SpawnInitGroupHeadingMin or 0,self.SpawnInitGroupHeadingMax)) - local cosHeading = math.cos(headingRad) - local sinHeading = math.sin(headingRad) - - local unitVarRad = math.rad(self.SpawnInitGroupUnitVar or 0) + local headingRad = math.rad( _RandomInRange( self.SpawnInitGroupHeadingMin or 0, self.SpawnInitGroupHeadingMax ) ) + local cosHeading = math.cos( headingRad ) + local sinHeading = math.sin( headingRad ) + + local unitVarRad = math.rad( self.SpawnInitGroupUnitVar or 0 ) for UnitID = 1, #SpawnTemplate.units do - + if UnitID > 1 then -- don't rotate position of unit #1 local unitXOff = SpawnTemplate.units[UnitID].x - pivotX -- rotate position offset around unit #1 local unitYOff = SpawnTemplate.units[UnitID].y - pivotY - SpawnTemplate.units[UnitID].x = pivotX + (unitXOff*cosHeading) - (unitYOff*sinHeading) - SpawnTemplate.units[UnitID].y = pivotY + (unitYOff*cosHeading) + (unitXOff*sinHeading) + SpawnTemplate.units[UnitID].x = pivotX + (unitXOff * cosHeading) - (unitYOff * sinHeading) + SpawnTemplate.units[UnitID].y = pivotY + (unitYOff * cosHeading) + (unitXOff * sinHeading) end - + -- adjust heading of all units, including unit #1 local unitHeading = SpawnTemplate.units[UnitID].heading + headingRad -- add group rotation to units default rotation - SpawnTemplate.units[UnitID].heading = _HeadingRad(_RandomInRange(unitHeading-unitVarRad, unitHeading+unitVarRad)) + SpawnTemplate.units[UnitID].heading = _HeadingRad( _RandomInRange( unitHeading - unitVarRad, unitHeading + unitVarRad ) ) SpawnTemplate.units[UnitID].psi = -SpawnTemplate.units[UnitID].heading - + end end - + -- If Heading is given, point all the units towards the given Heading. Overrides any heading set in InitGroupHeading above. if self.SpawnInitHeadingMin then for UnitID = 1, #SpawnTemplate.units do - SpawnTemplate.units[UnitID].heading = _Heading(_RandomInRange(self.SpawnInitHeadingMin, self.SpawnInitHeadingMax)) + SpawnTemplate.units[UnitID].heading = _Heading( _RandomInRange( self.SpawnInitHeadingMin, self.SpawnInitHeadingMax ) ) SpawnTemplate.units[UnitID].psi = -SpawnTemplate.units[UnitID].heading end end - + -- Set livery. if self.SpawnInitLivery then for UnitID = 1, #SpawnTemplate.units do @@ -1368,39 +1354,38 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth ) -- Set tail number. if self.SpawnInitModex then for UnitID = 1, #SpawnTemplate.units do - SpawnTemplate.units[UnitID].onboard_num = string.format("%03d", self.SpawnInitModex+(UnitID-1)) + SpawnTemplate.units[UnitID].onboard_num = string.format( "%03d", self.SpawnInitModex + (UnitID - 1) ) end end - + -- Set radio comms on/off. if self.SpawnInitRadio then - SpawnTemplate.communication=self.SpawnInitRadio - end - + SpawnTemplate.communication = self.SpawnInitRadio + end + -- Set radio frequency. if self.SpawnInitFreq then - SpawnTemplate.frequency=self.SpawnInitFreq + SpawnTemplate.frequency = self.SpawnInitFreq end - + -- Set radio modulation. if self.SpawnInitModu then - SpawnTemplate.modulation=self.SpawnInitModu - end - + SpawnTemplate.modulation = self.SpawnInitModu + end + -- Set country, coaliton and categroy. - SpawnTemplate.CategoryID = self.SpawnInitCategory or SpawnTemplate.CategoryID - SpawnTemplate.CountryID = self.SpawnInitCountry or SpawnTemplate.CountryID - SpawnTemplate.CoalitionID = self.SpawnInitCoalition or SpawnTemplate.CoalitionID - - --- if SpawnTemplate.CategoryID == Group.Category.HELICOPTER or SpawnTemplate.CategoryID == Group.Category.AIRPLANE then --- if SpawnTemplate.route.points[1].type == "TakeOffParking" then --- SpawnTemplate.uncontrolled = self.SpawnUnControlled --- end --- end + SpawnTemplate.CategoryID = self.SpawnInitCategory or SpawnTemplate.CategoryID + SpawnTemplate.CountryID = self.SpawnInitCountry or SpawnTemplate.CountryID + SpawnTemplate.CoalitionID = self.SpawnInitCoalition or SpawnTemplate.CoalitionID + + -- if SpawnTemplate.CategoryID == Group.Category.HELICOPTER or SpawnTemplate.CategoryID == Group.Category.AIRPLANE then + -- if SpawnTemplate.route.points[1].type == "TakeOffParking" then + -- SpawnTemplate.uncontrolled = self.SpawnUnControlled + -- end + -- end end - - if not NoBirth then + + if not NoBirth then self:HandleEvent( EVENTS.Birth, self._OnBirth ) end self:HandleEvent( EVENTS.Dead, self._OnDeadOrCrash ) @@ -1414,37 +1399,36 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth ) self:HandleEvent( EVENTS.EngineShutdown, self._OnEngineShutDown ) end - self.SpawnGroups[self.SpawnIndex].Group = _DATABASE:Spawn( SpawnTemplate ) - - local SpawnGroup = self.SpawnGroups[self.SpawnIndex].Group -- Wrapper.Group#GROUP - - --TODO: Need to check if this function doesn't need to be scheduled, as the group may not be immediately there! + self.SpawnGroups[self.SpawnIndex].Group = _DATABASE:Spawn( SpawnTemplate ) + + local SpawnGroup = self.SpawnGroups[self.SpawnIndex].Group -- Wrapper.Group#GROUP + + -- TODO: Need to check if this function doesn't need to be scheduled, as the group may not be immediately there! if SpawnGroup then - - SpawnGroup:SetAIOnOff( self.AIOnOff ) - end + + SpawnGroup:SetAIOnOff( self.AIOnOff ) + end self:T3( SpawnTemplate.name ) - - -- If there is a SpawnFunction hook defined, call it. - if self.SpawnFunctionHook then - -- delay calling this for .1 seconds so that it hopefully comes after the BIRTH event of the group. - self.SpawnHookScheduler:Schedule( nil, self.SpawnFunctionHook, { self.SpawnGroups[self.SpawnIndex].Group, unpack( self.SpawnFunctionArguments)}, 0.1 ) - 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 + -- If there is a SpawnFunction hook defined, call it. + if self.SpawnFunctionHook then + -- delay calling this for .1 seconds so that it hopefully comes after the BIRTH event of the group. + self.SpawnHookScheduler:Schedule( nil, self.SpawnFunctionHook, { self.SpawnGroups[self.SpawnIndex].Group, unpack( self.SpawnFunctionArguments ) }, 0.1 ) + 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. @@ -1452,29 +1436,29 @@ end -- @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. +-- The variation is a number between 0 and 1, representing the % 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 +-- -- 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. +-- -- Between these two values, a random amount of seconds will be chosen for each new spawn of the helicopters. -- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):SpawnScheduled( 600, 0.5 ) function SPAWN:SpawnScheduled( SpawnTime, SpawnTimeVariation ) - self:F( { SpawnTime, SpawnTimeVariation } ) + self:F( { SpawnTime, SpawnTimeVariation } ) - if SpawnTime ~= nil and SpawnTimeVariation ~= nil then - local InitialDelay = 0 - if self.DelayOnOff == true then - InitialDelay = math.random( SpawnTime - SpawnTime * SpawnTimeVariation, SpawnTime + SpawnTime * SpawnTimeVariation ) - end + if SpawnTime ~= nil and SpawnTimeVariation ~= nil then + local InitialDelay = 0 + if self.DelayOnOff == true then + InitialDelay = math.random( SpawnTime - SpawnTime * SpawnTimeVariation, SpawnTime + SpawnTime * SpawnTimeVariation ) + end self.SpawnScheduler = SCHEDULER:New( self, self._Scheduler, {}, InitialDelay, SpawnTime, SpawnTimeVariation ) - end + end - return self + return self end --- Will re-start the spawning scheduler. @@ -1493,12 +1477,11 @@ end -- @return #SPAWN function SPAWN:SpawnScheduleStop() self:F( { self.SpawnTemplatePrefix } ) - + self.SpawnScheduler:Stop() return self end - --- Allows to place a CallFunction hook when a new group spawns. -- The provided method will be called when a new group is spawned, including its given parameters. -- The first parameter of the SpawnFunction is the @{Wrapper.Group#GROUP} that was spawned. @@ -1507,17 +1490,16 @@ end -- @param SpawnFunctionArguments A random amount of arguments to be provided to the function when the group spawns. -- @return #SPAWN -- @usage --- -- Declare SpawnObject and call a function when a new Group is spawned. --- local SpawnObject = SPAWN --- :New( "SpawnObject" ) --- :InitLimit( 2, 10 ) --- :OnSpawnGroup( --- function( SpawnGroup ) --- SpawnGroup:E( "I am spawned" ) --- end --- ) --- :SpawnScheduled( 300, 0.3 ) --- +-- +-- -- Declare SpawnObject and call a function when a new Group is spawned. +-- local SpawnObject = SPAWN:New( "SpawnObject" ) +-- :InitLimit( 2, 10 ) +-- :OnSpawnGroup( function( SpawnGroup ) +-- SpawnGroup:E( "I am spawned" ) +-- end +-- ) +-- :SpawnScheduled( 300, 0.3 ) +-- function SPAWN:OnSpawnGroup( SpawnCallBackFunction, ... ) self:F( "OnSpawnGroup" ) @@ -1525,109 +1507,110 @@ function SPAWN:OnSpawnGroup( SpawnCallBackFunction, ... ) self.SpawnFunctionArguments = {} if arg then self.SpawnFunctionArguments = arg - end + end return self end ---- Will spawn a group at an @{Wrapper.Airbase}. +--- Will spawn a group at an @{Wrapper.Airbase}. -- This method is mostly advisable to be used if you want to simulate spawning units at an airbase. -- 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. --- +-- -- The @{Wrapper.Airbase#AIRBASE} object must refer to a valid airbase known in the sim. -- You can use the following enumerations to search for the pre-defined airbases on the current known maps of DCS: --- --- * @{Wrapper.Airbase#AIRBASE.Caucasus}: The airbases on the Caucasus map. --- * @{Wrapper.Airbase#AIRBASE.Nevada}: The airbases on the Nevada (NTTR) map. --- * @{Wrapper.Airbase#AIRBASE.Normandy}: The airbases on the Normandy map. --- --- Use the method @{Wrapper.Airbase#AIRBASE.FindByName}() to retrieve the airbase object. +-- +-- * @{Wrapper.Airbase#AIRBASE.Caucasus}: The airbases on the Caucasus map. +-- * @{Wrapper.Airbase#AIRBASE.Nevada}: The airbases on the Nevada (NTTR) map. +-- * @{Wrapper.Airbase#AIRBASE.Normandy}: The airbases on the Normandy map. +-- +-- Use the method @{Wrapper.Airbase#AIRBASE.FindByName}() to retrieve the airbase object. -- The known AIRBASE objects are automatically imported at mission start by MOOSE. -- Therefore, there isn't any New() constructor defined for AIRBASE objects. --- --- Ships and Farps are added within the mission, and are therefore not known. +-- +-- Ships and FARPs are added within the mission, and are therefore not known. -- For these AIRBASE objects, there isn't an @{Wrapper.Airbase#AIRBASE} enumeration defined. -- You need to provide the **exact name** of the airbase as the parameter to the @{Wrapper.Airbase#AIRBASE.FindByName}() method! --- +-- -- @param #SPAWN self -- @param Wrapper.Airbase#AIRBASE SpawnAirbase The @{Wrapper.Airbase} where to spawn the group. -- @param #SPAWN.Takeoff Takeoff (optional) The location and takeoff method. Default is Hot. -- @param #number TakeoffAltitude (optional) The altitude above the ground. -- @param Wrapper.Airbase#AIRBASE.TerminalType TerminalType (optional) The terminal type the aircraft should be spawned at. See @{Wrapper.Airbase#AIRBASE.TerminalType}. -- @param #boolean EmergencyAirSpawn (optional) If true (default), groups are spawned in air if there is no parking spot at the airbase. If false, nothing is spawned if no parking spot is available. --- @param #table Parkingdata (optional) Table holding the coordinates and terminal ids for all units of the group. Spawning will be forced to happen at exactily these spots! +-- @param #table Parkingdata (optional) Table holding the coordinates and terminal ids for all units of the group. Spawning will be forced to happen at exactly these spots! -- @return Wrapper.Group#GROUP The group that was spawned or nil when nothing was spawned. -- @usage +-- -- Spawn_Plane = SPAWN:New( "Plane" ) -- Spawn_Plane:SpawnAtAirbase( AIRBASE:FindByName( AIRBASE.Caucasus.Krymsk ), SPAWN.Takeoff.Cold ) -- Spawn_Plane:SpawnAtAirbase( AIRBASE:FindByName( AIRBASE.Caucasus.Krymsk ), SPAWN.Takeoff.Hot ) -- Spawn_Plane:SpawnAtAirbase( AIRBASE:FindByName( AIRBASE.Caucasus.Krymsk ), SPAWN.Takeoff.Runway ) --- +-- -- Spawn_Plane:SpawnAtAirbase( AIRBASE:FindByName( "Carrier" ), SPAWN.Takeoff.Cold ) --- +-- -- Spawn_Heli = SPAWN:New( "Heli") --- +-- -- Spawn_Heli:SpawnAtAirbase( AIRBASE:FindByName( "FARP Cold" ), SPAWN.Takeoff.Cold ) -- Spawn_Heli:SpawnAtAirbase( AIRBASE:FindByName( "FARP Hot" ), SPAWN.Takeoff.Hot ) -- Spawn_Heli:SpawnAtAirbase( AIRBASE:FindByName( "FARP Runway" ), SPAWN.Takeoff.Runway ) -- Spawn_Heli:SpawnAtAirbase( AIRBASE:FindByName( "FARP Air" ), SPAWN.Takeoff.Air ) --- +-- -- Spawn_Heli:SpawnAtAirbase( AIRBASE:FindByName( "Carrier" ), SPAWN.Takeoff.Cold ) --- +-- -- Spawn_Plane:SpawnAtAirbase( AIRBASE:FindByName( AIRBASE.Caucasus.Krymsk ), SPAWN.Takeoff.Cold, nil, AIRBASE.TerminalType.OpenBig ) --- +-- function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalType, EmergencyAirSpawn, Parkingdata ) -- R2.2, R2.4 self:F( { self.SpawnTemplatePrefix, SpawnAirbase, Takeoff, TakeoffAltitude, TerminalType } ) -- Get position of airbase. local PointVec3 = SpawnAirbase:GetCoordinate() - self:T2(PointVec3) + self:T2( PointVec3 ) -- Set take off type. Default is hot. Takeoff = Takeoff or SPAWN.Takeoff.Hot - + -- By default, groups are spawned in air if no parking spot is available. - if EmergencyAirSpawn==nil then - EmergencyAirSpawn=true + if EmergencyAirSpawn == nil then + EmergencyAirSpawn = true end - + self:F( { SpawnIndex = self.SpawnIndex } ) - + if self:_GetSpawnIndex( self.SpawnIndex + 1 ) then - + -- Get group template. local SpawnTemplate = self.SpawnGroups[self.SpawnIndex].SpawnTemplate - + self:F( { SpawnTemplate = SpawnTemplate } ) - + if SpawnTemplate then - + -- Check if the aircraft with the specified SpawnIndex is already spawned. -- If yes, ensure that the aircraft is spawned at the same aircraft spot. - + local GroupAlive = self:GetGroupFromIndex( self.SpawnIndex ) - + self:F( { GroupAlive = GroupAlive } ) -- Debug output self:T( { "Current point of ", self.SpawnTemplatePrefix, SpawnAirbase } ) - + -- Template group, unit and its attributes. - local TemplateGroup = GROUP:FindByName(self.SpawnTemplatePrefix) - local TemplateUnit=TemplateGroup:GetUnit(1) - + local TemplateGroup = GROUP:FindByName( self.SpawnTemplatePrefix ) + local TemplateUnit = TemplateGroup:GetUnit( 1 ) + -- General category of spawned group. - local group=TemplateGroup - local istransport=group:HasAttribute("Transports") and group:HasAttribute("Planes") - local isawacs=group:HasAttribute("AWACS") - local isfighter=group:HasAttribute("Fighters") or group:HasAttribute("Interceptors") or group:HasAttribute("Multirole fighters") or (group:HasAttribute("Bombers") and not group:HasAttribute("Strategic bombers")) - local isbomber=group:HasAttribute("Strategic bombers") - local istanker=group:HasAttribute("Tankers") - local ishelo=TemplateUnit:HasAttribute("Helicopters") - + local group = TemplateGroup + local istransport = group:HasAttribute( "Transports" ) and group:HasAttribute( "Planes" ) + local isawacs = group:HasAttribute( "AWACS" ) + local isfighter = group:HasAttribute( "Fighters" ) or group:HasAttribute( "Interceptors" ) or group:HasAttribute( "Multirole fighters" ) or (group:HasAttribute( "Bombers" ) and not group:HasAttribute( "Strategic bombers" )) + local isbomber = group:HasAttribute( "Strategic bombers" ) + local istanker = group:HasAttribute( "Tankers" ) + local ishelo = TemplateUnit:HasAttribute( "Helicopters" ) + -- Number of units in the group. With grouping this can actually differ from the template group size! - local nunits=#SpawnTemplate.units + local nunits = #SpawnTemplate.units -- First waypoint of the group. local SpawnPoint = SpawnTemplate.route.points[1] @@ -1641,7 +1624,7 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT local AirbaseID = SpawnAirbase:GetID() local AirbaseCategory = SpawnAirbase:GetAirbaseCategory() self:F( { AirbaseCategory = AirbaseCategory } ) - + -- Set airdromeId. if AirbaseCategory == Airbase.Category.SHIP then SpawnPoint.linkUnit = AirbaseID @@ -1654,71 +1637,70 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT end -- Set waypoint type/action. - SpawnPoint.alt = 0 - SpawnPoint.type = GROUPTEMPLATE.Takeoff[Takeoff][1] -- type + SpawnPoint.alt = 0 + SpawnPoint.type = GROUPTEMPLATE.Takeoff[Takeoff][1] -- type SpawnPoint.action = GROUPTEMPLATE.Takeoff[Takeoff][2] -- action - + -- Check if we spawn on ground. - local spawnonground=not (Takeoff==SPAWN.Takeoff.Air) - self:T({spawnonground=spawnonground, TOtype=Takeoff, TOair=Takeoff==SPAWN.Takeoff.Air}) - + local spawnonground = not (Takeoff == SPAWN.Takeoff.Air) + self:T( { spawnonground = spawnonground, TOtype = Takeoff, TOair = Takeoff == SPAWN.Takeoff.Air } ) + -- Check where we actually spawn if we spawn on ground. - local spawnonship=false - local spawnonfarp=false - local spawnonrunway=false - local spawnonairport=false - if spawnonground then + local spawnonship = false + local spawnonfarp = false + local spawnonrunway = false + local spawnonairport = false + if spawnonground then if AirbaseCategory == Airbase.Category.SHIP then - spawnonship=true + spawnonship = true elseif AirbaseCategory == Airbase.Category.HELIPAD then - spawnonfarp=true + spawnonfarp = true elseif AirbaseCategory == Airbase.Category.AIRDROME then - spawnonairport=true + spawnonairport = true end - spawnonrunway=Takeoff==SPAWN.Takeoff.Runway + spawnonrunway = Takeoff == SPAWN.Takeoff.Runway end - + -- Array with parking spots coordinates. - local parkingspots={} - local parkingindex={} + local parkingspots = {} + local parkingindex = {} local spots - + -- Spawn happens on ground, i.e. at an airbase, a FARP or a ship. if spawnonground and not SpawnTemplate.parked then - - + -- Number of free parking spots. - local nfree=0 - + local nfree = 0 + -- Set terminal type. - local termtype=TerminalType - if spawnonrunway then + local termtype = TerminalType + if spawnonrunway then if spawnonship then -- Looks like there are no runway spawn spots on the stennis! if ishelo then - termtype=AIRBASE.TerminalType.HelicopterUsable + termtype = AIRBASE.TerminalType.HelicopterUsable else - termtype=AIRBASE.TerminalType.OpenMedOrBig + termtype = AIRBASE.TerminalType.OpenMedOrBig end else - termtype=AIRBASE.TerminalType.Runway - end + termtype = AIRBASE.TerminalType.Runway + end end - + -- Scan options. Might make that input somehow. - local scanradius=50 - local scanunits=true - local scanstatics=true - local scanscenery=false - local verysafe=false - + local scanradius = 50 + local scanunits = true + local scanstatics = true + local scanscenery = false + local verysafe = false + -- Number of free parking spots at the airbase. if spawnonship or spawnonfarp or spawnonrunway then -- These places work procedural and have some kind of build in queue ==> Less effort. - self:T(string.format("Group %s is spawned on farp/ship/runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName())) - nfree=SpawnAirbase:GetFreeParkingSpotsNumber(termtype, true) - spots=SpawnAirbase:GetFreeParkingSpotsTable(termtype, true) - --[[ + self:T( string.format( "Group %s is spawned on farp/ship/runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) + nfree = SpawnAirbase:GetFreeParkingSpotsNumber( termtype, true ) + spots = SpawnAirbase:GetFreeParkingSpotsTable( termtype, true ) + --[[ elseif Parkingdata~=nil then -- Parking data explicitly set by user as input parameter. nfree=#Parkingdata @@ -1726,146 +1708,146 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT ]] else if ishelo then - if termtype==nil then + if termtype == nil then -- Helo is spawned. Try exclusive helo spots first. - self:T(string.format("Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.HelicopterOnly)) - spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.HelicopterOnly, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata) - nfree=#spots - if nfree=1 then - + + -- On free spot required in these cases. + if nfree >= 1 then + -- All units get the same spot. DCS takes care of the rest. - for i=1,nunits do - table.insert(parkingspots, spots[1].Coordinate) - table.insert(parkingindex, spots[1].TerminalID) + for i = 1, nunits do + table.insert( parkingspots, spots[1].Coordinate ) + table.insert( parkingindex, spots[1].TerminalID ) end -- This is actually used... - PointVec3=spots[1].Coordinate - + PointVec3 = spots[1].Coordinate + else -- If there is absolutely no spot ==> air start! - _notenough=true + _notenough = true end - + elseif spawnonairport then - - if nfree>=nunits then - - for i=1,nunits do - table.insert(parkingspots, spots[i].Coordinate) - table.insert(parkingindex, spots[i].TerminalID) + + if nfree >= nunits then + + for i = 1, nunits do + table.insert( parkingspots, spots[i].Coordinate ) + table.insert( parkingindex, spots[i].TerminalID ) end - + else -- Not enough spots for the whole group ==> air start! - _notenough=true - end + _notenough = true + end end - + -- Not enough spots ==> Prepare airstart. if _notenough then - - if EmergencyAirSpawn and not self.SpawnUnControlled then - self:E(string.format("WARNING: Group %s has no parking spots at %s ==> air start!", self.SpawnTemplatePrefix, SpawnAirbase:GetName())) - + + if EmergencyAirSpawn and not self.SpawnUnControlled then + self:E( string.format( "WARNING: Group %s has no parking spots at %s ==> air start!", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) + -- Not enough parking spots at the airport ==> Spawn in air. - spawnonground=false - spawnonship=false - spawnonfarp=false - spawnonrunway=false - + spawnonground = false + spawnonship = false + spawnonfarp = false + spawnonrunway = false + -- Set waypoint type/action to turning point. - SpawnPoint.type = GROUPTEMPLATE.Takeoff[GROUP.Takeoff.Air][1] -- type = Turning Point + SpawnPoint.type = GROUPTEMPLATE.Takeoff[GROUP.Takeoff.Air][1] -- type = Turning Point SpawnPoint.action = GROUPTEMPLATE.Takeoff[GROUP.Takeoff.Air][2] -- action = Turning Point - + -- Adjust altitude to be 500-1000 m above the airbase. - PointVec3.x=PointVec3.x+math.random(-500,500) - PointVec3.z=PointVec3.z+math.random(-500,500) + PointVec3.x = PointVec3.x + math.random( -500, 500 ) + PointVec3.z = PointVec3.z + math.random( -500, 500 ) if ishelo then - PointVec3.y=PointVec3:GetLandHeight()+math.random(100,1000) + PointVec3.y = PointVec3:GetLandHeight() + math.random( 100, 1000 ) else -- Randomize position so that multiple AC wont be spawned on top even in air. - PointVec3.y=PointVec3:GetLandHeight()+math.random(500,2500) + PointVec3.y = PointVec3:GetLandHeight() + math.random( 500, 2500 ) end - - Takeoff=GROUP.Takeoff.Air + + Takeoff = GROUP.Takeoff.Air else - self:E(string.format("WARNING: Group %s has no parking spots at %s ==> No emergency air start or uncontrolled spawning ==> No spawn!", self.SpawnTemplatePrefix, SpawnAirbase:GetName())) + self:E( string.format( "WARNING: Group %s has no parking spots at %s ==> No emergency air start or uncontrolled spawning ==> No spawn!", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) return nil end end - + else - + -- Air start requested initially ==> Set altitude. if TakeoffAltitude then - PointVec3.y=TakeoffAltitude + PointVec3.y = TakeoffAltitude else if ishelo then - PointVec3.y=PointVec3:GetLandHeight()+math.random(100,1000) + PointVec3.y = PointVec3:GetLandHeight() + math.random( 100, 1000 ) else -- Randomize position so that multiple AC wont be spawned on top even in air. - PointVec3.y=PointVec3:GetLandHeight()+math.random(500,2500) + PointVec3.y = PointVec3:GetLandHeight() + math.random( 500, 2500 ) end end - + end if not SpawnTemplate.parked then @@ -1874,163 +1856,162 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT SpawnTemplate.parked = true for UnitID = 1, nunits do - self:T2('Before Translation SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y) - + self:T2( 'Before Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. SpawnTemplate.units[UnitID].y ) + -- Template of the current unit. local UnitTemplate = SpawnTemplate.units[UnitID] - + -- Tranlate position and preserve the relative position/formation of all aircraft. local SX = UnitTemplate.x - local SY = UnitTemplate.y + local SY = UnitTemplate.y local BX = SpawnTemplate.route.points[1].x local BY = SpawnTemplate.route.points[1].y - local TX = PointVec3.x + (SX-BX) - local TY = PointVec3.z + (SY-BY) - + local TX = PointVec3.x + (SX - BX) + local TY = PointVec3.z + (SY - BY) + if spawnonground then - + -- Ships and FARPS seem to have a build in queue. if spawnonship or spawnonfarp or spawnonrunway then - - self:T(string.format("Group %s spawning at farp, ship or runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName())) - + + self:T( string.format( "Group %s spawning at farp, ship or runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) + -- Spawn on ship. We take only the position of the ship. - SpawnTemplate.units[UnitID].x = PointVec3.x --TX - SpawnTemplate.units[UnitID].y = PointVec3.z --TY + SpawnTemplate.units[UnitID].x = PointVec3.x -- TX + SpawnTemplate.units[UnitID].y = PointVec3.z -- TY SpawnTemplate.units[UnitID].alt = PointVec3.y - + else - - self:T(string.format("Group %s spawning at airbase %s on parking spot id %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), parkingindex[UnitID])) - + + self:T( string.format( "Group %s spawning at airbase %s on parking spot id %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), parkingindex[UnitID] ) ) + -- Get coordinates of parking spot. - SpawnTemplate.units[UnitID].x = parkingspots[UnitID].x - SpawnTemplate.units[UnitID].y = parkingspots[UnitID].z + SpawnTemplate.units[UnitID].x = parkingspots[UnitID].x + SpawnTemplate.units[UnitID].y = parkingspots[UnitID].z SpawnTemplate.units[UnitID].alt = parkingspots[UnitID].y - - --parkingspots[UnitID]:MarkToAll(string.format("Group %s spawning at airbase %s on parking spot id %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), parkingindex[UnitID])) + + -- parkingspots[UnitID]:MarkToAll(string.format("Group %s spawning at airbase %s on parking spot id %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), parkingindex[UnitID])) end - + else - - self:T(string.format("Group %s spawning in air at %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName())) - + + self:T( string.format( "Group %s spawning in air at %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) + -- Spawn in air as requested initially. Original template orientation is perserved, altitude is already correctly set. - SpawnTemplate.units[UnitID].x = TX - SpawnTemplate.units[UnitID].y = TY + SpawnTemplate.units[UnitID].x = TX + SpawnTemplate.units[UnitID].y = TY SpawnTemplate.units[UnitID].alt = PointVec3.y - + end - + -- Parking spot id. UnitTemplate.parking = nil UnitTemplate.parking_id = nil if parkingindex[UnitID] then UnitTemplate.parking = parkingindex[UnitID] end - + -- Debug output. - self:T(string.format("Group %s unit number %d: Parking = %s",self.SpawnTemplatePrefix, UnitID, tostring(UnitTemplate.parking))) - self:T(string.format("Group %s unit number %d: Parking ID = %s",self.SpawnTemplatePrefix, UnitID, tostring(UnitTemplate.parking_id))) - self:T2('After Translation SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y) + self:T( string.format( "Group %s unit number %d: Parking = %s", self.SpawnTemplatePrefix, UnitID, tostring( UnitTemplate.parking ) ) ) + self:T( string.format( "Group %s unit number %d: Parking ID = %s", self.SpawnTemplatePrefix, UnitID, tostring( UnitTemplate.parking_id ) ) ) + self:T2( 'After Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. SpawnTemplate.units[UnitID].y ) end end - + -- Set gereral spawnpoint position. - SpawnPoint.x = PointVec3.x - SpawnPoint.y = PointVec3.z + SpawnPoint.x = PointVec3.x + SpawnPoint.y = PointVec3.z SpawnPoint.alt = PointVec3.y - + SpawnTemplate.x = PointVec3.x SpawnTemplate.y = PointVec3.z - + SpawnTemplate.uncontrolled = self.SpawnUnControlled - + -- Spawn group. local GroupSpawned = self:SpawnWithIndex( self.SpawnIndex ) - + -- When spawned in the air, we need to generate a Takeoff Event. if Takeoff == GROUP.Takeoff.Air then for UnitID, UnitSpawned in pairs( GroupSpawned:GetUnits() ) do - SCHEDULER:New( nil, BASE.CreateEventTakeoff, { GroupSpawned, timer.getTime(), UnitSpawned:GetDCSObject() } , 5 ) + SCHEDULER:New( nil, BASE.CreateEventTakeoff, { GroupSpawned, timer.getTime(), UnitSpawned:GetDCSObject() }, 5 ) end end - + -- Check if we accidentally spawned on the runway. Needs to be schedules, because group is not immidiately alive. - if Takeoff~=SPAWN.Takeoff.Runway and Takeoff~=SPAWN.Takeoff.Air and spawnonairport then - SCHEDULER:New(nil, AIRBASE.CheckOnRunWay, {SpawnAirbase, GroupSpawned, 75, true} , 1.0) + if Takeoff ~= SPAWN.Takeoff.Runway and Takeoff ~= SPAWN.Takeoff.Air and spawnonairport then + SCHEDULER:New( nil, AIRBASE.CheckOnRunWay, { SpawnAirbase, GroupSpawned, 75, true }, 1.0 ) end - - return GroupSpawned + + return GroupSpawned end end - + return nil end ---- Spawn a group on an @{Wrapper.Airbase} at a specific parking spot. +--- Spawn a group on an @{Wrapper.Airbase} at a specific parking spot. -- @param #SPAWN self -- @param Wrapper.Airbase#AIRBASE Airbase The @{Wrapper.Airbase} where to spawn the group. -- @param #table Spots Table of parking spot IDs. Note that these in general are different from the numbering in the mission editor! -- @param #SPAWN.Takeoff Takeoff (Optional) Takeoff type, i.e. either SPAWN.Takeoff.Cold or SPAWN.Takeoff.Hot. Default is Hot. -- @return Wrapper.Group#GROUP The group that was spawned or nil when nothing was spawned. -function SPAWN:SpawnAtParkingSpot(Airbase, Spots, Takeoff) -- R2.5 - self:F({Airbase=Airbase, Spots=Spots, Takeoff=Takeoff}) +function SPAWN:SpawnAtParkingSpot( Airbase, Spots, Takeoff ) -- R2.5 + self:F( { Airbase = Airbase, Spots = Spots, Takeoff = Takeoff } ) -- Ensure that Spots parameter is a table. - if type(Spots)~="table" then - Spots={Spots} + if type( Spots ) ~= "table" then + Spots = { Spots } end -- Get template group. - local group=GROUP:FindByName(self.SpawnTemplatePrefix) - + local group = GROUP:FindByName( self.SpawnTemplatePrefix ) + -- Get number of units in group. - local nunits=self.SpawnGrouping or #group:GetUnits() + local nunits = self.SpawnGrouping or #group:GetUnits() -- Quick check. if nunits then - + -- Check that number of provided parking spots is large enough. - if #Spots=nunits then - return self:SpawnAtAirbase(Airbase, Takeoff, nil, nil, nil, Parkingdata) + + if #Parkingdata >= nunits then + return self:SpawnAtAirbase( Airbase, Takeoff, nil, nil, nil, Parkingdata ) else - self:E("ERROR: Could not find enough free parking spots!") + self:E( "ERROR: Could not find enough free parking spots!" ) end - - + else - self:E("ERROR: Could not get number of units in group!") + self:E( "ERROR: Could not get number of units in group!" ) end return nil end ---- Will park a group at an @{Wrapper.Airbase}. --- +--- Will park a group at an @{Wrapper.Airbase}. +-- -- @param #SPAWN self -- @param Wrapper.Airbase#AIRBASE SpawnAirbase The @{Wrapper.Airbase} where to spawn the group. -- @param Wrapper.Airbase#AIRBASE.TerminalType TerminalType (optional) The terminal type the aircraft should be spawned at. See @{Wrapper.Airbase#AIRBASE.TerminalType}. @@ -2042,34 +2023,34 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex -- Get position of airbase. local PointVec3 = SpawnAirbase:GetCoordinate() - self:T2(PointVec3) + self:T2( PointVec3 ) -- Set take off type. Default is hot. local Takeoff = SPAWN.Takeoff.Cold - + -- Get group template. local SpawnTemplate = self.SpawnGroups[SpawnIndex].SpawnTemplate if SpawnTemplate then - + -- Check if the aircraft with the specified SpawnIndex is already spawned. -- If yes, ensure that the aircraft is spawned at the same aircraft spot. - + local GroupAlive = self:GetGroupFromIndex( SpawnIndex ) -- Debug output self:T( { "Current point of ", self.SpawnTemplatePrefix, SpawnAirbase } ) - + -- Template group, unit and its attributes. - local TemplateGroup = GROUP:FindByName(self.SpawnTemplatePrefix) - local TemplateUnit=TemplateGroup:GetUnit(1) - local ishelo=TemplateUnit:HasAttribute("Helicopters") - local isbomber=TemplateUnit:HasAttribute("Bombers") - local istransport=TemplateUnit:HasAttribute("Transports") - local isfighter=TemplateUnit:HasAttribute("Battleplanes") - + local TemplateGroup = GROUP:FindByName( self.SpawnTemplatePrefix ) + local TemplateUnit = TemplateGroup:GetUnit( 1 ) + local ishelo = TemplateUnit:HasAttribute( "Helicopters" ) + local isbomber = TemplateUnit:HasAttribute( "Bombers" ) + local istransport = TemplateUnit:HasAttribute( "Transports" ) + local isfighter = TemplateUnit:HasAttribute( "Battleplanes" ) + -- Number of units in the group. With grouping this can actually differ from the template group size! - local nunits=#SpawnTemplate.units + local nunits = #SpawnTemplate.units -- First waypoint of the group. local SpawnPoint = SpawnTemplate.route.points[1] @@ -2083,7 +2064,7 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex local AirbaseID = SpawnAirbase:GetID() local AirbaseCategory = SpawnAirbase:GetAirbaseCategory() self:F( { AirbaseCategory = AirbaseCategory } ) - + -- Set airdromeId. if AirbaseCategory == Airbase.Category.SHIP then SpawnPoint.linkUnit = AirbaseID @@ -2096,59 +2077,58 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex end -- Set waypoint type/action. - SpawnPoint.alt = 0 - SpawnPoint.type = GROUPTEMPLATE.Takeoff[Takeoff][1] -- type + SpawnPoint.alt = 0 + SpawnPoint.type = GROUPTEMPLATE.Takeoff[Takeoff][1] -- type SpawnPoint.action = GROUPTEMPLATE.Takeoff[Takeoff][2] -- action - + -- Check if we spawn on ground. - local spawnonground=not (Takeoff==SPAWN.Takeoff.Air) - self:T({spawnonground=spawnonground, TOtype=Takeoff, TOair=Takeoff==SPAWN.Takeoff.Air}) - + local spawnonground = not (Takeoff == SPAWN.Takeoff.Air) + self:T( { spawnonground = spawnonground, TOtype = Takeoff, TOair = Takeoff == SPAWN.Takeoff.Air } ) + -- Check where we actually spawn if we spawn on ground. - local spawnonship=false - local spawnonfarp=false - local spawnonrunway=false - local spawnonairport=false - if spawnonground then + local spawnonship = false + local spawnonfarp = false + local spawnonrunway = false + local spawnonairport = false + if spawnonground then if AirbaseCategory == Airbase.Category.SHIP then - spawnonship=true + spawnonship = true elseif AirbaseCategory == Airbase.Category.HELIPAD then - spawnonfarp=true + spawnonfarp = true elseif AirbaseCategory == Airbase.Category.AIRDROME then - spawnonairport=true + spawnonairport = true end - spawnonrunway=Takeoff==SPAWN.Takeoff.Runway + spawnonrunway = Takeoff == SPAWN.Takeoff.Runway end - + -- Array with parking spots coordinates. - local parkingspots={} - local parkingindex={} + local parkingspots = {} + local parkingindex = {} local spots - + -- Spawn happens on ground, i.e. at an airbase, a FARP or a ship. if spawnonground and not SpawnTemplate.parked then - - + -- Number of free parking spots. - local nfree=0 - + local nfree = 0 + -- Set terminal type. - local termtype=TerminalType + local termtype = TerminalType -- Scan options. Might make that input somehow. - local scanradius=50 - local scanunits=true - local scanstatics=true - local scanscenery=false - local verysafe=false - + local scanradius = 50 + local scanunits = true + local scanstatics = true + local scanscenery = false + local verysafe = false + -- Number of free parking spots at the airbase. if spawnonship or spawnonfarp or spawnonrunway then -- These places work procedural and have some kind of build in queue ==> Less effort. - self:T(string.format("Group %s is spawned on farp/ship/runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName())) - nfree=SpawnAirbase:GetFreeParkingSpotsNumber(termtype, true) - spots=SpawnAirbase:GetFreeParkingSpotsTable(termtype, true) - --[[ + self:T( string.format( "Group %s is spawned on farp/ship/runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) + nfree = SpawnAirbase:GetFreeParkingSpotsNumber( termtype, true ) + spots = SpawnAirbase:GetFreeParkingSpotsTable( termtype, true ) + --[[ elseif Parkingdata~=nil then -- Parking data explicitly set by user as input parameter. nfree=#Parkingdata @@ -2156,114 +2136,114 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex ]] else if ishelo then - if termtype==nil then + if termtype == nil then -- Helo is spawned. Try exclusive helo spots first. - self:T(string.format("Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.HelicopterOnly)) - spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.HelicopterOnly, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata) - nfree=#spots - if nfree=1 then - + if nfree >= 1 then + -- All units get the same spot. DCS takes care of the rest. - for i=1,nunits do - table.insert(parkingspots, spots[1].Coordinate) - table.insert(parkingindex, spots[1].TerminalID) + for i = 1, nunits do + table.insert( parkingspots, spots[1].Coordinate ) + table.insert( parkingindex, spots[1].TerminalID ) end -- This is actually used... - PointVec3=spots[1].Coordinate - + PointVec3 = spots[1].Coordinate + else -- If there is absolutely no spot ==> air start! - _notenough=true + _notenough = true end - + elseif spawnonairport then - - if nfree>=nunits then - - for i=1,nunits do - table.insert(parkingspots, spots[i].Coordinate) - table.insert(parkingindex, spots[i].TerminalID) + + if nfree >= nunits then + + for i = 1, nunits do + table.insert( parkingspots, spots[i].Coordinate ) + table.insert( parkingindex, spots[i].TerminalID ) end - + else -- Not enough spots for the whole group ==> air start! - _notenough=true - end + _notenough = true + end end - + -- Not enough spots ==> Prepare airstart. if _notenough then - - if not self.SpawnUnControlled then + + if not self.SpawnUnControlled then else - self:E(string.format("WARNING: Group %s has no parking spots at %s ==> No emergency air start or uncontrolled spawning ==> No spawn!", self.SpawnTemplatePrefix, SpawnAirbase:GetName())) + self:E( string.format( "WARNING: Group %s has no parking spots at %s ==> No emergency air start or uncontrolled spawning ==> No spawn!", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) return nil end end - + else - + end if not SpawnTemplate.parked then @@ -2272,118 +2252,118 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex SpawnTemplate.parked = true for UnitID = 1, nunits do - self:F('Before Translation SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y) - + self:F( 'Before Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. SpawnTemplate.units[UnitID].y ) + -- Template of the current unit. local UnitTemplate = SpawnTemplate.units[UnitID] - + -- Tranlate position and preserve the relative position/formation of all aircraft. local SX = UnitTemplate.x - local SY = UnitTemplate.y + local SY = UnitTemplate.y local BX = SpawnTemplate.route.points[1].x local BY = SpawnTemplate.route.points[1].y - local TX = PointVec3.x + (SX-BX) - local TY = PointVec3.z + (SY-BY) - + local TX = PointVec3.x + (SX - BX) + local TY = PointVec3.z + (SY - BY) + if spawnonground then - + -- Ships and FARPS seem to have a build in queue. if spawnonship or spawnonfarp or spawnonrunway then - - self:T(string.format("Group %s spawning at farp, ship or runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName())) + + self:T( string.format( "Group %s spawning at farp, ship or runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) -- Spawn on ship. We take only the position of the ship. - SpawnTemplate.units[UnitID].x = PointVec3.x --TX - SpawnTemplate.units[UnitID].y = PointVec3.z --TY + SpawnTemplate.units[UnitID].x = PointVec3.x -- TX + SpawnTemplate.units[UnitID].y = PointVec3.z -- TY SpawnTemplate.units[UnitID].alt = PointVec3.y - + else - self:T(string.format("Group %s spawning at airbase %s on parking spot id %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), parkingindex[UnitID])) - + self:T( string.format( "Group %s spawning at airbase %s on parking spot id %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), parkingindex[UnitID] ) ) + -- Get coordinates of parking spot. - SpawnTemplate.units[UnitID].x = parkingspots[UnitID].x - SpawnTemplate.units[UnitID].y = parkingspots[UnitID].z + SpawnTemplate.units[UnitID].x = parkingspots[UnitID].x + SpawnTemplate.units[UnitID].y = parkingspots[UnitID].z SpawnTemplate.units[UnitID].alt = parkingspots[UnitID].y - - --parkingspots[UnitID]:MarkToAll(string.format("Group %s spawning at airbase %s on parking spot id %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), parkingindex[UnitID])) + + -- parkingspots[UnitID]:MarkToAll(string.format("Group %s spawning at airbase %s on parking spot id %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), parkingindex[UnitID])) end - + else - - self:T(string.format("Group %s spawning in air at %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName())) - + + self:T( string.format( "Group %s spawning in air at %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) + -- Spawn in air as requested initially. Original template orientation is perserved, altitude is already correctly set. - SpawnTemplate.units[UnitID].x = TX - SpawnTemplate.units[UnitID].y = TY + SpawnTemplate.units[UnitID].x = TX + SpawnTemplate.units[UnitID].y = TY SpawnTemplate.units[UnitID].alt = PointVec3.y - + end - + -- Parking spot id. UnitTemplate.parking = nil UnitTemplate.parking_id = nil if parkingindex[UnitID] then UnitTemplate.parking = parkingindex[UnitID] end - + -- Debug output. - self:T2(string.format("Group %s unit number %d: Parking = %s",self.SpawnTemplatePrefix, UnitID, tostring(UnitTemplate.parking))) - self:T2(string.format("Group %s unit number %d: Parking ID = %s",self.SpawnTemplatePrefix, UnitID, tostring(UnitTemplate.parking_id))) - self:T2('After Translation SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y) + self:T2( string.format( "Group %s unit number %d: Parking = %s", self.SpawnTemplatePrefix, UnitID, tostring( UnitTemplate.parking ) ) ) + self:T2( string.format( "Group %s unit number %d: Parking ID = %s", self.SpawnTemplatePrefix, UnitID, tostring( UnitTemplate.parking_id ) ) ) + self:T2( 'After Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. SpawnTemplate.units[UnitID].y ) end end - + -- Set general spawnpoint position. - SpawnPoint.x = PointVec3.x - SpawnPoint.y = PointVec3.z + SpawnPoint.x = PointVec3.x + SpawnPoint.y = PointVec3.z SpawnPoint.alt = PointVec3.y - + SpawnTemplate.x = PointVec3.x SpawnTemplate.y = PointVec3.z - + SpawnTemplate.uncontrolled = true - + -- Spawn group. local GroupSpawned = self:SpawnWithIndex( SpawnIndex, true ) - + -- When spawned in the air, we need to generate a Takeoff Event. if Takeoff == GROUP.Takeoff.Air then for UnitID, UnitSpawned in pairs( GroupSpawned:GetUnits() ) do - SCHEDULER:New( nil, BASE.CreateEventTakeoff, { GroupSpawned, timer.getTime(), UnitSpawned:GetDCSObject() } , 5 ) + SCHEDULER:New( nil, BASE.CreateEventTakeoff, { GroupSpawned, timer.getTime(), UnitSpawned:GetDCSObject() }, 5 ) end end - + -- Check if we accidentally spawned on the runway. Needs to be schedules, because group is not immidiately alive. - if Takeoff~=SPAWN.Takeoff.Runway and Takeoff~=SPAWN.Takeoff.Air and spawnonairport then - SCHEDULER:New(nil, AIRBASE.CheckOnRunWay, {SpawnAirbase, GroupSpawned, 75, true} , 1.0) + if Takeoff ~= SPAWN.Takeoff.Runway and Takeoff ~= SPAWN.Takeoff.Air and spawnonairport then + SCHEDULER:New( nil, AIRBASE.CheckOnRunWay, { SpawnAirbase, GroupSpawned, 75, true }, 1.0 ) end - + end end ---- Will park a group at an @{Wrapper.Airbase}. +--- Will park a group at an @{Wrapper.Airbase}. -- This method is mostly advisable to be used if you want to simulate parking units at an airbase and be visible. -- Note that each point in the route assigned to the spawning group is reset to the point of the spawn. --- +-- -- All groups that are in the spawn collection and that are alive, and not in the air, are parked. --- +-- -- The @{Wrapper.Airbase#AIRBASE} object must refer to a valid airbase known in the sim. -- You can use the following enumerations to search for the pre-defined airbases on the current known maps of DCS: --- --- * @{Wrapper.Airbase#AIRBASE.Caucasus}: The airbases on the Caucasus map. --- * @{Wrapper.Airbase#AIRBASE.Nevada}: The airbases on the Nevada (NTTR) map. --- * @{Wrapper.Airbase#AIRBASE.Normandy}: The airbases on the Normandy map. --- --- Use the method @{Wrapper.Airbase#AIRBASE.FindByName}() to retrieve the airbase object. +-- +-- * @{Wrapper.Airbase#AIRBASE.Caucasus}: The airbases on the Caucasus map. +-- * @{Wrapper.Airbase#AIRBASE.Nevada}: The airbases on the Nevada (NTTR) map. +-- * @{Wrapper.Airbase#AIRBASE.Normandy}: The airbases on the Normandy map. +-- +-- Use the method @{Wrapper.Airbase#AIRBASE.FindByName}() to retrieve the airbase object. -- The known AIRBASE objects are automatically imported at mission start by MOOSE. -- Therefore, there isn't any New() constructor defined for AIRBASE objects. --- +-- -- Ships and Farps are added within the mission, and are therefore not known. -- For these AIRBASE objects, there isn't an @{Wrapper.Airbase#AIRBASE} enumeration defined. -- You need to provide the **exact name** of the airbase as the parameter to the @{Wrapper.Airbase#AIRBASE.FindByName}() method! --- +-- -- @param #SPAWN self -- @param Wrapper.Airbase#AIRBASE SpawnAirbase The @{Wrapper.Airbase} where to spawn the group. -- @param Wrapper.Airbase#AIRBASE.TerminalType TerminalType (optional) The terminal type the aircraft should be spawned at. See @{Wrapper.Airbase#AIRBASE.TerminalType}. @@ -2392,15 +2372,15 @@ end -- @usage -- Spawn_Plane = SPAWN:New( "Plane" ) -- Spawn_Plane:ParkAtAirbase( AIRBASE:FindByName( AIRBASE.Caucasus.Krymsk ) ) --- +-- -- Spawn_Heli = SPAWN:New( "Heli") --- +-- -- Spawn_Heli:ParkAtAirbase( AIRBASE:FindByName( "FARP Cold" ) ) --- +-- -- Spawn_Heli:ParkAtAirbase( AIRBASE:FindByName( "Carrier" ) ) --- +-- -- Spawn_Plane:ParkAtAirbase( AIRBASE:FindByName( AIRBASE.Caucasus.Krymsk ), AIRBASE.TerminalType.OpenBig ) --- +-- function SPAWN:ParkAtAirbase( SpawnAirbase, TerminalType, Parkingdata ) -- R2.2, R2.4, R2.5 self:F( { self.SpawnTemplatePrefix, SpawnAirbase, TerminalType } ) @@ -2408,15 +2388,15 @@ function SPAWN:ParkAtAirbase( SpawnAirbase, TerminalType, Parkingdata ) -- R2.2, for SpawnIndex = 2, self.SpawnMaxGroups do self:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex ) - --self:ScheduleOnce( SpawnIndex * 0.1, SPAWN.ParkAircraft, self, SpawnAirbase, TerminalType, Parkingdata, SpawnIndex ) + -- self:ScheduleOnce( SpawnIndex * 0.1, SPAWN.ParkAircraft, self, SpawnAirbase, TerminalType, Parkingdata, SpawnIndex ) end - + self:SetSpawnIndex( 0 ) - + return nil end ---- Will spawn a group from a Vec3 in 3D space. +--- Will spawn a group from a Vec3 in 3D space. -- This method is mostly advisable to be used if you want to simulate spawning units in the air, like helicopters or airplanes. -- Note that each point in the route assigned to the spawning group is reset to the point of the spawn. -- You can use the returned group to further define the route to be followed. @@ -2429,21 +2409,21 @@ function SPAWN:SpawnFromVec3( Vec3, SpawnIndex ) self:F( { self.SpawnTemplatePrefix, Vec3, SpawnIndex } ) local PointVec3 = POINT_VEC3:NewFromVec3( Vec3 ) - self:T2(PointVec3) + 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 } ) - + local TemplateHeight = SpawnTemplate.route and SpawnTemplate.route.points[1].alt or nil SpawnTemplate.route = SpawnTemplate.route or {} @@ -2454,20 +2434,20 @@ function SPAWN:SpawnFromVec3( Vec3, SpawnIndex ) -- Translate the position of the Group Template to the Vec3. for UnitID = 1, #SpawnTemplate.units do - --self:T( 'Before Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y ) + -- self:T( 'Before Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y ) local UnitTemplate = SpawnTemplate.units[UnitID] local SX = UnitTemplate.x or 0 - local SY = UnitTemplate.y or 0 + local SY = UnitTemplate.y or 0 local BX = SpawnTemplate.route.points[1].x local BY = SpawnTemplate.route.points[1].y - local TX = Vec3.x + ( SX - BX ) - local TY = Vec3.z + ( SY - BY ) + local TX = Vec3.x + (SX - BX) + local TY = Vec3.z + (SY - BY) SpawnTemplate.units[UnitID].x = TX SpawnTemplate.units[UnitID].y = TY if SpawnTemplate.CategoryID ~= Group.Category.SHIP then SpawnTemplate.units[UnitID].alt = Vec3.y or TemplateHeight end - self:T( 'After Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y ) + self:T( 'After Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. SpawnTemplate.units[UnitID].y ) end SpawnTemplate.route.points[1].x = Vec3.x SpawnTemplate.route.points[1].y = Vec3.z @@ -2477,16 +2457,15 @@ function SPAWN:SpawnFromVec3( Vec3, SpawnIndex ) SpawnTemplate.x = Vec3.x SpawnTemplate.y = Vec3.z SpawnTemplate.alt = Vec3.y or TemplateHeight - + return self:SpawnWithIndex( self.SpawnIndex ) end end - + return nil end - ---- Will spawn a group from a Coordinate in 3D space. +--- Will spawn a group from a Coordinate in 3D space. -- This method is mostly advisable to be used if you want to simulate spawning units in the air, like helicopters or airplanes. -- Note that each point in the route assigned to the spawning group is reset to the point of the spawn. -- You can use the returned group to further define the route to be followed. @@ -2501,8 +2480,6 @@ function SPAWN:SpawnFromCoordinate( Coordinate, SpawnIndex ) return self:SpawnFromVec3( Coordinate:GetVec3(), SpawnIndex ) end - - --- Will spawn a group from a PointVec3 in 3D space. -- This method is mostly advisable to be used if you want to simulate spawning units in the air, like helicopters or airplanes. -- Note that each point in the route assigned to the spawning group is reset to the point of the spawn. @@ -2513,19 +2490,18 @@ end -- @return Wrapper.Group#GROUP that was spawned. -- @return #nil Nothing was spawned. -- @usage --- +-- -- local SpawnPointVec3 = ZONE:New( ZoneName ):GetPointVec3( 2000 ) -- Get the center of the ZONE object at 2000 meters from the ground. --- +-- -- -- Spawn at the zone center position at 2000 meters from the ground! --- SpawnAirplanes:SpawnFromPointVec3( SpawnPointVec3 ) --- +-- SpawnAirplanes:SpawnFromPointVec3( SpawnPointVec3 ) +-- function SPAWN:SpawnFromPointVec3( PointVec3, SpawnIndex ) self:F( { self.SpawnTemplatePrefix, SpawnIndex } ) return self:SpawnFromVec3( PointVec3:GetVec3(), SpawnIndex ) end - --- Will spawn a group from a Vec2 in 3D space. -- This method is mostly advisable to be used if you want to simulate spawning groups on the ground from air units, like vehicles. -- Note that each point in the route assigned to the spawning group is reset to the point of the spawn. @@ -2538,29 +2514,28 @@ end -- @return Wrapper.Group#GROUP that was spawned. -- @return #nil Nothing was spawned. -- @usage --- +-- -- local SpawnVec2 = ZONE:New( ZoneName ):GetVec2() --- +-- -- -- Spawn at the zone center position at the height specified in the ME of the group template! --- SpawnAirplanes:SpawnFromVec2( SpawnVec2 ) --- +-- SpawnAirplanes:SpawnFromVec2( SpawnVec2 ) +-- -- -- Spawn from the static position at the height randomized between 2000 and 4000 meters. --- SpawnAirplanes:SpawnFromVec2( SpawnVec2, 2000, 4000 ) --- +-- SpawnAirplanes:SpawnFromVec2( SpawnVec2, 2000, 4000 ) +-- function SPAWN:SpawnFromVec2( Vec2, MinHeight, MaxHeight, SpawnIndex ) self:F( { self.SpawnTemplatePrefix, self.SpawnIndex, Vec2, MinHeight, MaxHeight, SpawnIndex } ) local Height = nil if MinHeight and MaxHeight then - Height = math.random( MinHeight, MaxHeight) + Height = math.random( MinHeight, MaxHeight ) end - + return self:SpawnFromVec3( { x = Vec2.x, y = Height, z = Vec2.y }, SpawnIndex ) -- y can be nil. In this case, spawn on the ground for vehicles, and in the template altitude for air. end - ---- Will spawn a group from a POINT_VEC2 in 3D space. +--- Will spawn a group from a POINT_VEC2 in 3D space. -- This method is mostly advisable to be used if you want to simulate spawning groups on the ground from air units, like vehicles. -- Note that each point in the route assigned to the spawning group is reset to the point of the spawn. -- You can use the returned group to further define the route to be followed. @@ -2572,23 +2547,21 @@ end -- @return Wrapper.Group#GROUP that was spawned. -- @return #nil Nothing was spawned. -- @usage --- +-- -- local SpawnPointVec2 = ZONE:New( ZoneName ):GetPointVec2() --- +-- -- -- Spawn at the zone center position at the height specified in the ME of the group template! --- SpawnAirplanes:SpawnFromPointVec2( SpawnPointVec2 ) --- +-- SpawnAirplanes:SpawnFromPointVec2( SpawnPointVec2 ) +-- -- -- Spawn from the static position at the height randomized between 2000 and 4000 meters. --- SpawnAirplanes:SpawnFromPointVec2( SpawnPointVec2, 2000, 4000 ) --- +-- SpawnAirplanes:SpawnFromPointVec2( SpawnPointVec2, 2000, 4000 ) +-- function SPAWN:SpawnFromPointVec2( PointVec2, MinHeight, MaxHeight, SpawnIndex ) self:F( { self.SpawnTemplatePrefix, self.SpawnIndex } ) return self:SpawnFromVec2( PointVec2:GetVec2(), MinHeight, MaxHeight, SpawnIndex ) end - - --- Will spawn a group from a hosting unit. This method is mostly advisable to be used if you want to simulate spawning from air units, like helicopters, which are dropping infantry into a defined Landing Zone. -- Note that each point in the route assigned to the spawning group is reset to the point of the spawn. -- You can use the returned group to further define the route to be followed. @@ -2600,22 +2573,22 @@ end -- @return Wrapper.Group#GROUP that was spawned. -- @return #nil Nothing was spawned. -- @usage --- +-- -- local SpawnStatic = STATIC:FindByName( StaticName ) --- +-- -- -- Spawn from the static position at the height specified in the ME of the group template! --- SpawnAirplanes:SpawnFromUnit( SpawnStatic ) --- +-- SpawnAirplanes:SpawnFromUnit( SpawnStatic ) +-- -- -- Spawn from the static position at the height randomized between 2000 and 4000 meters. --- SpawnAirplanes:SpawnFromUnit( SpawnStatic, 2000, 4000 ) --- +-- SpawnAirplanes:SpawnFromUnit( SpawnStatic, 2000, 4000 ) +-- function SPAWN:SpawnFromUnit( HostUnit, MinHeight, MaxHeight, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, HostUnit, MinHeight, MaxHeight, SpawnIndex } ) + self:F( { self.SpawnTemplatePrefix, HostUnit, MinHeight, MaxHeight, SpawnIndex } ) if HostUnit and HostUnit:IsAlive() ~= nil then -- and HostUnit:getUnit(1):inAir() == false then return self:SpawnFromVec2( HostUnit:GetVec2(), MinHeight, MaxHeight, SpawnIndex ) end - + return nil end @@ -2629,22 +2602,22 @@ end -- @return Wrapper.Group#GROUP that was spawned. -- @return #nil Nothing was spawned. -- @usage --- +-- -- local SpawnStatic = STATIC:FindByName( StaticName ) --- +-- -- -- Spawn from the static position at the height specified in the ME of the group template! --- SpawnAirplanes:SpawnFromStatic( SpawnStatic ) --- +-- SpawnAirplanes:SpawnFromStatic( SpawnStatic ) +-- -- -- Spawn from the static position at the height randomized between 2000 and 4000 meters. --- SpawnAirplanes:SpawnFromStatic( SpawnStatic, 2000, 4000 ) --- +-- SpawnAirplanes:SpawnFromStatic( SpawnStatic, 2000, 4000 ) +-- function SPAWN:SpawnFromStatic( HostStatic, MinHeight, MaxHeight, SpawnIndex ) self:F( { self.SpawnTemplatePrefix, HostStatic, MinHeight, MaxHeight, SpawnIndex } ) if HostStatic and HostStatic:IsAlive() then return self:SpawnFromVec2( HostStatic:GetVec2(), MinHeight, MaxHeight, SpawnIndex ) end - + return nil end @@ -2661,27 +2634,27 @@ end -- @return Wrapper.Group#GROUP that was spawned. -- @return #nil when nothing was spawned. -- @usage --- +-- -- local SpawnZone = ZONE:New( ZoneName ) --- +-- -- -- Spawn at the zone center position at the height specified in the ME of the group template! --- SpawnAirplanes:SpawnInZone( SpawnZone ) --- +-- SpawnAirplanes:SpawnInZone( SpawnZone ) +-- -- -- Spawn in the zone at a random position at the height specified in the Me of the group template. --- SpawnAirplanes:SpawnInZone( SpawnZone, true ) --- +-- SpawnAirplanes:SpawnInZone( SpawnZone, true ) +-- -- -- Spawn in the zone at a random position at the height randomized between 2000 and 4000 meters. --- SpawnAirplanes:SpawnInZone( SpawnZone, true, 2000, 4000 ) --- +-- SpawnAirplanes:SpawnInZone( SpawnZone, true, 2000, 4000 ) +-- -- -- Spawn at the zone center position at the height randomized between 2000 and 4000 meters. --- SpawnAirplanes:SpawnInZone( SpawnZone, false, 2000, 4000 ) --- +-- SpawnAirplanes:SpawnInZone( SpawnZone, false, 2000, 4000 ) +-- -- -- Spawn at the zone center position at the height randomized between 2000 and 4000 meters. --- SpawnAirplanes:SpawnInZone( SpawnZone, nil, 2000, 4000 ) --- +-- SpawnAirplanes:SpawnInZone( SpawnZone, nil, 2000, 4000 ) +-- function SPAWN:SpawnInZone( Zone, RandomizeGroup, MinHeight, MaxHeight, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, Zone, RandomizeGroup, MinHeight, MaxHeight, SpawnIndex } ) - + self:F( { self.SpawnTemplatePrefix, Zone, RandomizeGroup, MinHeight, MaxHeight, SpawnIndex } ) + if Zone then if RandomizeGroup then return self:SpawnFromVec2( Zone:GetRandomVec2(), MinHeight, MaxHeight, SpawnIndex ) @@ -2689,11 +2662,11 @@ function SPAWN:SpawnInZone( Zone, RandomizeGroup, MinHeight, MaxHeight, SpawnInd return self:SpawnFromVec2( Zone:GetVec2(), MinHeight, MaxHeight, SpawnIndex ) end end - + return nil end ---- (**AIR**) Will spawn a plane group in UnControlled or Controlled mode... +--- (**AIR**) Will spawn a plane group in UnControlled or Controlled mode... -- This will be similar to the uncontrolled flag setting in the ME. -- You can use UnControlled mode to simulate planes startup and ready for take-off but aren't moving (yet). -- ReSpawn the plane in Controlled mode, and the plane will move... @@ -2701,17 +2674,16 @@ end -- @param #boolean UnControlled true if UnControlled, false if Controlled. -- @return #SPAWN self function SPAWN:InitUnControlled( UnControlled ) - self:F2( { self.SpawnTemplatePrefix, UnControlled } ) - - self.SpawnUnControlled = ( UnControlled == true ) and true or nil - - for SpawnGroupID = 1, self.SpawnMaxGroups do - self.SpawnGroups[SpawnGroupID].UnControlled = self.SpawnUnControlled - end - - return self -end + self:F2( { self.SpawnTemplatePrefix, UnControlled } ) + self.SpawnUnControlled = (UnControlled == true) and true or nil + + for SpawnGroupID = 1, self.SpawnMaxGroups do + self.SpawnGroups[SpawnGroupID].UnControlled = self.SpawnUnControlled + end + + return self +end --- Get the Coordinate of the Group that is Late Activated as the template for the SPAWN object. -- @param #SPAWN self @@ -2722,32 +2694,31 @@ function SPAWN:GetCoordinate() if LateGroup then return LateGroup:GetCoordinate() end - + return nil 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 } ) + self:F( { self.SpawnTemplatePrefix, SpawnIndex } ) - local SpawnPrefix = self.SpawnTemplatePrefix - if self.SpawnAliasPrefix then - SpawnPrefix = self.SpawnAliasPrefix - end + 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 - 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 @{Wrapper.Group} it has spawned, and return the alive @{Wrapper.Group} object and the first Index where the first alive @{Wrapper.Group} object has been found. @@ -2755,14 +2726,16 @@ end -- @return Wrapper.Group#GROUP, #number The @{Wrapper.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 @{Wrapper.Group} object of the SpawnPlanes SPAWN object @{Wrapper.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 +-- +-- -- Find the first alive @{Wrapper.Group} object of the SpawnPlanes SPAWN object @{Wrapper.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 } ) + self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) for SpawnIndex = 1, self.SpawnCount do local SpawnGroup = self:GetGroupFromIndex( SpawnIndex ) @@ -2770,25 +2743,26 @@ function SPAWN:GetFirstAliveGroup() return SpawnGroup, SpawnIndex end end - + return nil, nil end - --- Will find the next alive @{Wrapper.Group} object from a given Index, and return a reference to the alive @{Wrapper.Group} object and the next Index where the alive @{Wrapper.Group} has been found. -- @param #SPAWN self -- @param #number SpawnIndexStart A Index holding the start position to search from. This method can also be used to find the first alive @{Wrapper.Group} object from the given Index. -- @return Wrapper.Group#GROUP, #number The next alive @{Wrapper.Group} object found, the next Index where the next alive @{Wrapper.Group} object was found. -- @return #nil, #nil When no alive @{Wrapper.Group} object is found from the start Index position, #nil is returned. -- @usage --- -- Find the first alive @{Wrapper.Group} object of the SpawnPlanes SPAWN object @{Wrapper.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 +-- +-- -- Find the first alive @{Wrapper.Group} object of the SpawnPlanes SPAWN object @{Wrapper.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 } ) + self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnIndexStart } ) SpawnIndexStart = SpawnIndexStart + 1 for SpawnIndex = SpawnIndexStart, self.SpawnCount do @@ -2797,7 +2771,7 @@ function SPAWN:GetNextAliveGroup( SpawnIndexStart ) return SpawnGroup, SpawnIndex end end - + return nil, nil end @@ -2806,14 +2780,16 @@ end -- @return Wrapper.Group#GROUP, #number The last alive @{Wrapper.Group} object found, the last Index where the last alive @{Wrapper.Group} object was found. -- @return #nil, #nil When no alive @{Wrapper.Group} object is found, #nil is returned. -- @usage --- -- Find the last alive @{Wrapper.Group} object of the SpawnPlanes SPAWN object @{Wrapper.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 +-- +-- -- Find the last alive @{Wrapper.Group} object of the SpawnPlanes SPAWN object @{Wrapper.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.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) - + self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) + for SpawnIndex = self.SpawnCount, 1, -1 do -- Added local SpawnGroup = self:GetGroupFromIndex( SpawnIndex ) if SpawnGroup and SpawnGroup:IsAlive() then @@ -2826,8 +2802,6 @@ function SPAWN:GetLastAliveGroup() 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. @@ -2835,20 +2809,19 @@ end -- @param #number SpawnIndex The index of the group to return. -- @return Wrapper.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 + 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 --- Return the prefix of a SpawnUnit. -- The method will search for a #-mark, and will return the text before the #-mark. @@ -2868,90 +2841,87 @@ function SPAWN:_GetPrefixFromGroup( SpawnGroup ) end return SpawnPrefix 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:F2( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnGroup } ) - - local IndexString = string.match( SpawnGroup:GetName(), "#(%d*)$" ):sub( 2 ) - local Index = tonumber( IndexString ) - - self:T3( IndexString, Index ) - return Index - + self:F2( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnGroup } ) + + local IndexString = string.match( SpawnGroup:GetName(), "#(%d*)$" ):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 } ) + self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) - return self.SpawnMaxGroups + return self.SpawnMaxGroups end --- Initalize the SpawnGroups collection. -- @param #SPAWN self function SPAWN:_InitializeSpawnGroups( SpawnIndex ) - self:F3( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, 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] + 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 + 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 + 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 + 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. @@ -2960,25 +2930,25 @@ end -- @param #string SpawnTemplatePrefix -- @return @SPAWN self function SPAWN:_GetTemplate( SpawnTemplatePrefix ) - self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnTemplatePrefix } ) + self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnTemplatePrefix } ) - local SpawnTemplate = nil + local SpawnTemplate = nil local Template = _DATABASE.Templates.Groups[SpawnTemplatePrefix].Template self:F( { Template = Template } ) - SpawnTemplate = UTILS.DeepCopy( _DATABASE.Templates.Groups[SpawnTemplatePrefix].Template ) - - if SpawnTemplate == nil then - error( 'No Template returned for SpawnTemplatePrefix = ' .. SpawnTemplatePrefix ) - end + SpawnTemplate = UTILS.DeepCopy( _DATABASE.Templates.Groups[SpawnTemplatePrefix].Template ) - --SpawnTemplate.SpawnCoalitionID = self:_GetGroupCoalitionID( SpawnTemplatePrefix ) - --SpawnTemplate.SpawnCategoryID = self:_GetGroupCategoryID( SpawnTemplatePrefix ) - --SpawnTemplate.SpawnCountryID = self:_GetGroupCountryID( SpawnTemplatePrefix ) - - self:T3( { SpawnTemplate } ) - return SpawnTemplate + 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. @@ -2986,36 +2956,35 @@ end -- @param #string SpawnTemplatePrefix -- @param #number SpawnIndex -- @return #SPAWN self -function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) --R2.2 - self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) - --- if not self.SpawnTemplate then --- self.SpawnTemplate = self:_GetTemplate( SpawnTemplatePrefix ) --- end - +function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) -- R2.2 + self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) + + -- if not self.SpawnTemplate then + -- self.SpawnTemplate = self:_GetTemplate( SpawnTemplatePrefix ) + -- end + local SpawnTemplate if self.TweakedTemplate ~= nil and self.TweakedTemplate == true then - BASE:I("WARNING: You are using a tweaked template.") + BASE:I( "WARNING: You are using a tweaked template." ) SpawnTemplate = self.SpawnTemplate else SpawnTemplate = self:_GetTemplate( SpawnTemplatePrefix ) SpawnTemplate.name = self:SpawnGroupName( SpawnIndex ) end - - SpawnTemplate.groupId = nil - --SpawnTemplate.lateActivation = false - SpawnTemplate.lateActivation = self.LateActivated or false + SpawnTemplate.groupId = nil + -- SpawnTemplate.lateActivation = false + SpawnTemplate.lateActivation = self.LateActivated or false - if SpawnTemplate.CategoryID == Group.Category.GROUND then - self:T3( "For ground units, visible needs to be false..." ) - SpawnTemplate.visible = false - end - - if self.SpawnGrouping then - local UnitAmount = #SpawnTemplate.units - self:F( { UnitAmount = UnitAmount, SpawnGrouping = self.SpawnGrouping } ) - if UnitAmount > self.SpawnGrouping then + if SpawnTemplate.CategoryID == Group.Category.GROUND then + self:T3( "For ground units, visible needs to be false..." ) + SpawnTemplate.visible = false + end + + if self.SpawnGrouping then + local UnitAmount = #SpawnTemplate.units + self:F( { UnitAmount = UnitAmount, SpawnGrouping = self.SpawnGrouping } ) + if UnitAmount > self.SpawnGrouping then for UnitID = self.SpawnGrouping + 1, UnitAmount do SpawnTemplate.units[UnitID] = nil end @@ -3028,17 +2997,17 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) --R2.2 end end end - + if self.SpawnInitKeepUnitNames == false then - for UnitID = 1, #SpawnTemplate.units do - SpawnTemplate.units[UnitID].name = string.format( SpawnTemplate.name .. '-%02d', UnitID ) - SpawnTemplate.units[UnitID].unitId = nil - end + for UnitID = 1, #SpawnTemplate.units do + SpawnTemplate.units[UnitID].name = string.format( SpawnTemplate.name .. '-%02d', UnitID ) + SpawnTemplate.units[UnitID].unitId = nil + end else for UnitID = 1, #SpawnTemplate.units do local UnitPrefix, Rest = string.match( SpawnTemplate.units[UnitID].name, "^([^#]+)#?" ):gsub( "^%s*(.-)%s*$", "%1" ) self:T( { UnitPrefix, Rest } ) - + SpawnTemplate.units[UnitID].name = string.format( '%s#%03d-%02d', UnitPrefix, SpawnIndex, UnitID ) SpawnTemplate.units[UnitID].unitId = nil end @@ -3048,20 +3017,20 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) --R2.2 for UnitID = 1, #SpawnTemplate.units do local Callsign = SpawnTemplate.units[UnitID].callsign if Callsign then - if type(Callsign) ~= "number" then -- blue callsign - Callsign[2] = ( ( SpawnIndex - 1 ) % 10 ) + 1 + if type( Callsign ) ~= "number" then -- blue callsign + Callsign[2] = ((SpawnIndex - 1) % 10) + 1 local CallsignName = SpawnTemplate.units[UnitID].callsign["name"] -- #string local CallsignLen = CallsignName:len() - SpawnTemplate.units[UnitID].callsign["name"] = CallsignName:sub(1,CallsignLen) .. SpawnTemplate.units[UnitID].callsign[2] .. SpawnTemplate.units[UnitID].callsign[3] + SpawnTemplate.units[UnitID].callsign["name"] = CallsignName:sub( 1, CallsignLen ) .. SpawnTemplate.units[UnitID].callsign[2] .. SpawnTemplate.units[UnitID].callsign[3] else SpawnTemplate.units[UnitID].callsign = Callsign + SpawnIndex end end end - - self:T3( { "Template:", SpawnTemplate } ) - return SpawnTemplate - + + self:T3( { "Template:", SpawnTemplate } ) + return SpawnTemplate + end --- Private method randomizing the routes. @@ -3069,17 +3038,17 @@ end -- @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 } ) + 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 - + + 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 @@ -3088,13 +3057,13 @@ function SPAWN:_RandomizeRoute( SpawnIndex ) 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 - + self:_RandomizeZones( SpawnIndex ) - + return self end @@ -3103,10 +3072,10 @@ end -- @param #number SpawnIndex -- @return #SPAWN self function SPAWN:_RandomizeTemplate( SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeTemplate } ) + 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].SpawnTemplatePrefix = self.SpawnTemplatePrefixTable[math.random( 1, #self.SpawnTemplatePrefixTable )] self.SpawnGroups[SpawnIndex].SpawnTemplate = self:_Prepare( self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix, SpawnIndex ) self.SpawnGroups[SpawnIndex].SpawnTemplate.route = UTILS.DeepCopy( self.SpawnTemplate.route ) self.SpawnGroups[SpawnIndex].SpawnTemplate.x = self.SpawnTemplate.x @@ -3116,14 +3085,14 @@ function SPAWN:_RandomizeTemplate( SpawnIndex ) local OldY = self.SpawnGroups[SpawnIndex].SpawnTemplate.units[1].y 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].x - OldX ) - self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].y = self.SpawnTemplate.units[1].y + ( self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].y - OldY ) + self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].x = self.SpawnTemplate.units[1].x + (self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].x - OldX) + self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].y = self.SpawnTemplate.units[1].y + (self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].y - OldY) self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].alt = self.SpawnTemplate.units[1].alt end end - + self:_RandomizeRoute( SpawnIndex ) - + return self end @@ -3140,84 +3109,79 @@ function SPAWN:_RandomizeZones( SpawnIndex ) self:T( { SpawnZoneTableCount = #self.SpawnZoneTable, self.SpawnZoneTable } ) local ZoneID = math.random( #self.SpawnZoneTable ) self:T( ZoneID ) - SpawnZone = self.SpawnZoneTable[ ZoneID ]:GetZoneMaybe() + SpawnZone = self.SpawnZoneTable[ZoneID]:GetZoneMaybe() end - + self:T( "Preparing Spawn in Zone", SpawnZone:GetName() ) - + local SpawnVec2 = SpawnZone:GetRandomVec2() - + self:T( { SpawnVec2 = SpawnVec2 } ) - + local SpawnTemplate = self.SpawnGroups[SpawnIndex].SpawnTemplate - + self:T( { Route = SpawnTemplate.route } ) - + for UnitID = 1, #SpawnTemplate.units do local UnitTemplate = SpawnTemplate.units[UnitID] - self:T( 'Before Translation SpawnTemplate.units['..UnitID..'].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. UnitTemplate.y ) + self:T( 'Before Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. UnitTemplate.y ) local SX = UnitTemplate.x - local SY = UnitTemplate.y + local SY = UnitTemplate.y local BX = SpawnTemplate.route.points[1].x local BY = SpawnTemplate.route.points[1].y - local TX = SpawnVec2.x + ( SX - BX ) - local TY = SpawnVec2.y + ( SY - BY ) + local TX = SpawnVec2.x + (SX - BX) + local TY = SpawnVec2.y + (SY - BY) UnitTemplate.x = TX UnitTemplate.y = TY -- TODO: Manage altitude based on landheight... - --SpawnTemplate.units[UnitID].alt = SpawnVec2: - self:T( 'After Translation SpawnTemplate.units['..UnitID..'].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. UnitTemplate.y ) + -- SpawnTemplate.units[UnitID].alt = SpawnVec2: + self:T( 'After Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. UnitTemplate.y ) end SpawnTemplate.x = SpawnVec2.x SpawnTemplate.y = SpawnVec2.y SpawnTemplate.route.points[1].x = SpawnVec2.x SpawnTemplate.route.points[1].y = SpawnVec2.y end - + return self - + end function SPAWN:_TranslateRotate( SpawnIndex, SpawnRootX, SpawnRootY, SpawnX, SpawnY, SpawnAngle ) - self:F( { self.SpawnTemplatePrefix, SpawnIndex, SpawnRootX, SpawnRootY, SpawnX, SpawnY, SpawnAngle } ) - + 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 ) ) - + 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 ) - + 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 ) ) - + 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 @@ -3226,10 +3190,10 @@ end -- @param #number SpawnIndex Spawn index. -- @return #number self.SpawnIndex 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 + 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 self:F( { SpawnCount = self.SpawnCount, SpawnIndex = SpawnIndex } ) if SpawnIndex and SpawnIndex >= self.SpawnCount + 1 then self.SpawnCount = self.SpawnCount + 1 @@ -3245,11 +3209,10 @@ function SPAWN:_GetSpawnIndex( SpawnIndex ) else return nil end - + return self.SpawnIndex end - -- TODO Need to delete this... _DATABASE does this now ... --- @param #SPAWN self @@ -3258,17 +3221,17 @@ function SPAWN:_OnBirth( EventData ) self:F( self.SpawnTemplatePrefix ) local SpawnGroup = EventData.IniGroup - + if SpawnGroup then local EventPrefix = self:_GetPrefixFromGroup( SpawnGroup ) if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group! - 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 + 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 end @@ -3281,17 +3244,17 @@ function SPAWN:_OnDeadOrCrash( EventData ) self:F( self.SpawnTemplatePrefix ) local SpawnGroup = EventData.IniGroup - - if SpawnGroup then - local EventPrefix = self:_GetPrefixFromGroup( SpawnGroup ) - if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group! + + if SpawnGroup then + local EventPrefix = self:_GetPrefixFromGroup( SpawnGroup ) + if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group! self:T( { "Dead event: " .. EventPrefix } ) - if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) then - self.AliveUnits = self.AliveUnits - 1 - self:T( "Alive Units: " .. self.AliveUnits ) - end + 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 end --- Will detect AIR Units taking off... When the event takes place, the spawned Group is registered as airborne... @@ -3306,12 +3269,12 @@ function SPAWN:_OnTakeOff( EventData ) local EventPrefix = self:_GetPrefixFromGroup( SpawnGroup ) if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group! self:T( { "TakeOff event: " .. EventPrefix } ) - if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) then - self:T( "self.Landed = false" ) - SpawnGroup:SetState( SpawnGroup, "Spawn_Landed", false ) + if EventPrefix == self.SpawnTemplatePrefix or (self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix) then + self:T( "self.Landed = false" ) + SpawnGroup:SetState( SpawnGroup, "Spawn_Landed", false ) end end - end + end end --- Will detect AIR Units landing... When the event takes place, the spawned Group is registered as landed. @@ -3326,20 +3289,20 @@ function SPAWN:_OnLand( EventData ) local EventPrefix = self:_GetPrefixFromGroup( SpawnGroup ) if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group! self:T( { "Land event: " .. EventPrefix } ) - if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) then - -- TODO: Check if this is the last unit of the group that lands. - SpawnGroup:SetState( SpawnGroup, "Spawn_Landed", true ) - if self.RepeatOnLanding then - local SpawnGroupIndex = self:GetSpawnIndexFromGroup( SpawnGroup ) - self:T( { "Landed:", "ReSpawn:", SpawnGroup:GetName(), SpawnGroupIndex } ) - --self:ReSpawn( SpawnGroupIndex ) - -- Delay respawn by three seconds due to DCS 2.5.4.26368 OB bug https://github.com/FlightControl-Master/MOOSE/issues/1076 - -- Bug was initially only for engine shutdown event but after ED "fixed" it, it now happens on landing events. - SCHEDULER:New(nil, self.ReSpawn, {self, SpawnGroupIndex}, 3) - end - end + if EventPrefix == self.SpawnTemplatePrefix or (self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix) then + -- TODO: Check if this is the last unit of the group that lands. + SpawnGroup:SetState( SpawnGroup, "Spawn_Landed", true ) + if self.RepeatOnLanding then + local SpawnGroupIndex = self:GetSpawnIndexFromGroup( SpawnGroup ) + self:T( { "Landed:", "ReSpawn:", SpawnGroup:GetName(), SpawnGroupIndex } ) + -- self:ReSpawn( SpawnGroupIndex ) + -- Delay respawn by three seconds due to DCS 2.5.4.26368 OB bug https://github.com/FlightControl-Master/MOOSE/issues/1076 + -- Bug was initially only for engine shutdown event but after ED "fixed" it, it now happens on landing events. + SCHEDULER:New( nil, self.ReSpawn, { self, SpawnGroupIndex }, 3 ) + end + end end - end + end end --- Will detect AIR Units shutting down their engines ... @@ -3355,76 +3318,75 @@ function SPAWN:_OnEngineShutDown( EventData ) local EventPrefix = self:_GetPrefixFromGroup( SpawnGroup ) if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group! self:T( { "EngineShutdown event: " .. EventPrefix } ) - if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) then - -- todo: test if on the runway - local Landed = SpawnGroup:GetState( SpawnGroup, "Spawn_Landed" ) - if Landed and self.RepeatOnEngineShutDown then - local SpawnGroupIndex = self:GetSpawnIndexFromGroup( SpawnGroup ) - self:T( { "EngineShutDown: ", "ReSpawn:", SpawnGroup:GetName(), SpawnGroupIndex } ) - --self:ReSpawn( SpawnGroupIndex ) - -- Delay respawn by three seconds due to DCS 2.5.4 OB bug https://github.com/FlightControl-Master/MOOSE/issues/1076 - SCHEDULER:New(nil, self.ReSpawn, {self, SpawnGroupIndex}, 3) - end - end + if EventPrefix == self.SpawnTemplatePrefix or (self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix) then + -- todo: test if on the runway + local Landed = SpawnGroup:GetState( SpawnGroup, "Spawn_Landed" ) + if Landed and self.RepeatOnEngineShutDown then + local SpawnGroupIndex = self:GetSpawnIndexFromGroup( SpawnGroup ) + self:T( { "EngineShutDown: ", "ReSpawn:", SpawnGroup:GetName(), SpawnGroupIndex } ) + -- self:ReSpawn( SpawnGroupIndex ) + -- Delay respawn by three seconds due to DCS 2.5.4 OB bug https://github.com/FlightControl-Master/MOOSE/issues/1076 + SCHEDULER:New( nil, self.ReSpawn, { self, SpawnGroupIndex }, 3 ) + end + end 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. -- @param #SPAWN self 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 + 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 --- Schedules the CleanUp of Groups -- @param #SPAWN self -- @return #boolean True = Continue Scheduler function SPAWN:_SpawnCleanUpScheduler() - self:F( { "CleanUp Scheduler:", self.SpawnTemplatePrefix } ) + self:F( { "CleanUp Scheduler:", self.SpawnTemplatePrefix } ) - local SpawnGroup, SpawnCursor = self:GetFirstAliveGroup() - self:T( { "CleanUp Scheduler:", SpawnGroup, SpawnCursor } ) + local SpawnGroup, SpawnCursor = self:GetFirstAliveGroup() + self:T( { "CleanUp Scheduler:", SpawnGroup, SpawnCursor } ) - while SpawnGroup do + while SpawnGroup do local SpawnUnits = SpawnGroup:GetUnits() - - for UnitID, UnitData in pairs( SpawnUnits ) do - - local SpawnUnit = UnitData -- Wrapper.Unit#UNIT - local SpawnUnitName = SpawnUnit:GetName() - - - self.SpawnCleanUpTimeStamps[SpawnUnitName] = self.SpawnCleanUpTimeStamps[SpawnUnitName] or {} - local Stamp = self.SpawnCleanUpTimeStamps[SpawnUnitName] + + for UnitID, UnitData in pairs( SpawnUnits ) do + + local SpawnUnit = UnitData -- Wrapper.Unit#UNIT + local SpawnUnitName = SpawnUnit:GetName() + + self.SpawnCleanUpTimeStamps[SpawnUnitName] = self.SpawnCleanUpTimeStamps[SpawnUnitName] or {} + local Stamp = self.SpawnCleanUpTimeStamps[SpawnUnitName] self:T( { SpawnUnitName, Stamp } ) - - if Stamp.Vec2 then - if SpawnUnit:InAir() == false and SpawnUnit:GetVelocityKMH() < 1 then - local NewVec2 = SpawnUnit:GetVec2() - if (Stamp.Vec2.x == NewVec2.x and Stamp.Vec2.y == NewVec2.y) or (SpawnUnit:GetLife() <= 1) then - -- If the plane is not moving or dead , and is on the ground, assign it with a timestamp... - if Stamp.Time + self.SpawnCleanUpInterval < timer.getTime() then - self:T( { "CleanUp Scheduler:", "ReSpawning:", SpawnGroup:GetName() } ) - self:ReSpawn( SpawnCursor ) + + if Stamp.Vec2 then + if SpawnUnit:InAir() == false and SpawnUnit:GetVelocityKMH() < 1 then + local NewVec2 = SpawnUnit:GetVec2() + if (Stamp.Vec2.x == NewVec2.x and Stamp.Vec2.y == NewVec2.y) or (SpawnUnit:GetLife() <= 1) then + -- If the plane is not moving or dead , and is on the ground, assign it with a timestamp... + if Stamp.Time + self.SpawnCleanUpInterval < timer.getTime() then + self:T( { "CleanUp Scheduler:", "ReSpawning:", SpawnGroup:GetName() } ) + self:ReSpawn( SpawnCursor ) Stamp.Vec2 = nil Stamp.Time = nil - end - else - Stamp.Time = timer.getTime() + end + else + Stamp.Time = timer.getTime() Stamp.Vec2 = SpawnUnit:GetVec2() - end - else - Stamp.Vec2 = nil - Stamp.Time = nil - end - else + end + else + Stamp.Vec2 = nil + Stamp.Time = nil + end + else if SpawnUnit:InAir() == false then Stamp.Vec2 = SpawnUnit:GetVec2() if (SpawnUnit:GetVelocityKMH() < 1) then @@ -3436,13 +3398,13 @@ function SPAWN:_SpawnCleanUpScheduler() end end end - - SpawnGroup, SpawnCursor = self:GetNextAliveGroup( SpawnCursor ) - - self:T( { "CleanUp Scheduler:", SpawnGroup, SpawnCursor } ) - - end - - return true -- Repeat - + + SpawnGroup, SpawnCursor = self:GetNextAliveGroup( SpawnCursor ) + + self:T( { "CleanUp Scheduler:", SpawnGroup, SpawnCursor } ) + + end + + return true -- Repeat + end